JavaScript / TypeScript
Contoh integrasi OAuth dengan Node.js + Express.
// oauth-client.ts
import express from "express";
import crypto from "crypto";
const app = express();
// Konfigurasi
const CONFIG = {
clientId: "YOUR_CLIENT_ID",
clientSecret: "YOUR_CLIENT_SECRET", // Hanya untuk confidential client
redirectUri: "http://localhost:3000/callback",
// Origin saja — path /api/auth/oauth2/... ditambahkan di bawah.
// Issuer OIDC (untuk validasi iss) = origin + "/api/auth"
authServerUrl: "https://bukutamu.tabalongkab.go.id",
issuer: "https://bukutamu.tabalongkab.go.id/api/auth",
scopes: ["openid", "profile", "email", "role", "organization"],
};
// Store untuk state dan code_verifier (gunakan Redis/session di produksi)
const authStore = new Map<string, { state: string; codeVerifier: string }>();
// Helper: Generate random string
function generateRandomString(length: number): string {
return crypto.randomBytes(length).toString("base64url").slice(0, length);
}
// Helper: Generate PKCE code challenge
function generateCodeChallenge(codeVerifier: string): string {
return crypto.createHash("sha256").update(codeVerifier).digest("base64url");
}
// Route: Mulai OAuth flow
app.get("/login", (req, res) => {
// Generate PKCE values
const codeVerifier = generateRandomString(64);
const codeChallenge = generateCodeChallenge(codeVerifier);
const state = generateRandomString(32);
// Simpan untuk verifikasi nanti
authStore.set(state, { state, codeVerifier });
// Buat authorization URL
const authUrl = new URL(`${CONFIG.authServerUrl}/api/auth/oauth2/authorize`);
authUrl.searchParams.set("client_id", CONFIG.clientId);
authUrl.searchParams.set("redirect_uri", CONFIG.redirectUri);
authUrl.searchParams.set("response_type", "code");
authUrl.searchParams.set("scope", CONFIG.scopes.join(" "));
authUrl.searchParams.set("state", state);
authUrl.searchParams.set("code_challenge", codeChallenge);
authUrl.searchParams.set("code_challenge_method", "S256");
// Redirect user ke SSO Tabalong
res.redirect(authUrl.toString());
});
// Route: OAuth callback
app.get("/callback", async (req, res) => {
const { code, state, error, error_description } = req.query;
// Handle error dari OAuth server
if (error) {
return res.status(400).json({
error,
error_description,
});
}
// Verifikasi state
const stored = authStore.get(state as string);
if (!stored) {
return res.status(400).json({ error: "Invalid state parameter" });
}
// Hapus dari store
authStore.delete(state as string);
try {
// Tukar code dengan token
const tokenResponse = await fetch(
`${CONFIG.authServerUrl}/api/auth/oauth2/token`,
{
method: "POST",
headers: {
"Content-Type": "application/x-www-form-urlencoded",
},
body: new URLSearchParams({
grant_type: "authorization_code",
code: code as string,
redirect_uri: CONFIG.redirectUri,
client_id: CONFIG.clientId,
client_secret: CONFIG.clientSecret,
code_verifier: stored.codeVerifier,
}),
},
);
if (!tokenResponse.ok) {
const errorData = await tokenResponse.json();
return res.status(400).json(errorData);
}
const tokens = await tokenResponse.json();
// Ambil user info
const userInfoResponse = await fetch(
`${CONFIG.authServerUrl}/api/auth/oauth2/userinfo`,
{
headers: {
Authorization: `Bearer ${tokens.access_token}`,
},
},
);
const userInfo = await userInfoResponse.json();
// Tampilkan hasil (di produksi: buat session, redirect ke dashboard, dll)
res.json({
tokens,
userInfo,
});
} catch (error) {
console.error("OAuth error:", error);
res.status(500).json({ error: "Internal server error" });
}
});
// Route: Refresh token
app.post("/refresh", async (req, res) => {
const { refreshToken } = req.body;
try {
const response = await fetch(
`${CONFIG.authServerUrl}/api/auth/oauth2/token`,
{
method: "POST",
headers: {
"Content-Type": "application/x-www-form-urlencoded",
},
body: new URLSearchParams({
grant_type: "refresh_token",
refresh_token: refreshToken,
client_id: CONFIG.clientId,
client_secret: CONFIG.clientSecret,
}),
},
);
const tokens = await response.json();
res.json(tokens);
} catch (error) {
res.status(500).json({ error: "Failed to refresh token" });
}
});
app.listen(3000, () => {
console.log("OAuth client running on http://localhost:3000");
});