Node.js
X-Guard tidak butuh SDK khusus untuk Node.js: pakai openid-client versi 6, pustaka OpenID Connect yang tersertifikasi. Contoh di bawah memakai Express dan express-session.
bash
npm install openid-client express express-sessionjs
import express from "express";
import session from "express-session";
import * as client from "openid-client";
const ISSUER = process.env.XGUARD_ISSUER; // https://nama-organisasi.x-guard.online
const REDIRECT = "https://app.contoh.id/auth/callback";
const SETELAH_LOGOUT = "https://app.contoh.id/";
// Membaca discovery issuer. Aplikasi Web: client secret dari konfigurasi server.
const config = await client.discovery(new URL(ISSUER), process.env.XGUARD_CLIENT_ID, process.env.XGUARD_CLIENT_SECRET);
const app = express();
app.use(session({ secret: process.env.SESSION_SECRET, resave: false, saveUninitialized: false,
cookie: { httpOnly: true, secure: true, sameSite: "lax" } }));
app.get("/auth/login", async (req, res) => {
const verifier = client.randomPKCECodeVerifier();
const state = client.randomState();
const nonce = client.randomNonce();
req.session.xguard = { verifier, state, nonce };
res.redirect(client.buildAuthorizationUrl(config, {
redirect_uri: REDIRECT, scope: "openid profile email", state, nonce,
code_challenge: await client.calculatePKCECodeChallenge(verifier), code_challenge_method: "S256",
}).href);
});
app.get("/auth/callback", async (req, res) => {
const alur = req.session.xguard;
delete req.session.xguard;
if (!alur) return res.status(400).send("Sesi login kedaluwarsa, ulangi login.");
try {
// Memeriksa state, menukar code dengan PKCE, dan memverifikasi ID token (tanda tangan, iss, aud, exp, nonce).
const tokens = await client.authorizationCodeGrant(config, new URL(req.originalUrl, REDIRECT), {
pkceCodeVerifier: alur.verifier, expectedState: alur.state, expectedNonce: alur.nonce,
});
const { sub, email, email_verified, name, sid } = tokens.claims();
// Tautkan akun lokal lewat sub (email hanya bila email_verified), lalu buat sesi baru.
req.session.regenerate(() => {
req.session.pengguna = { sub, email, name, sid, idToken: tokens.id_token };
res.redirect("/");
});
} catch (e) {
// e.error === "access_denied": pengguna tidak diberi akses ke aplikasi ini.
res.status(401).send(`Login gagal: ${e.error ?? e.message}`);
}
});
app.post("/auth/logout", (req, res) => {
const idToken = req.session.pengguna?.idToken;
req.session.destroy(() => {
res.redirect(client.buildEndSessionUrl(config, {
id_token_hint: idToken, post_logout_redirect_uri: SETELAH_LOGOUT,
}).href);
});
});SPA dan aplikasi tanpa secret
Aplikasi berjenis SPA atau native tidak punya client secret; PKCE wajib:
js
const config = await client.discovery(new URL(ISSUER), process.env.XGUARD_CLIENT_ID, undefined, client.None());Webhook
Pakai body mentah (express.raw), bukan JSON yang sudah di-parse. Fungsi verifikasiWebhook ada di Webhook.
js
app.post("/auth/webhook", express.raw({ type: "application/json" }), (req, res) => {
let ev;
try {
ev = verifikasiWebhook(process.env.XGUARD_WEBHOOK_SECRET, req.headers, req.body);
} catch {
return res.status(401).end();
}
// Idempoten berdasarkan ev.id. user.disabled / user.deleted / user.apps_changed (access "revoked"): cabut sesinya.
res.status(204).end();
});Back-channel logout
openid-client tidak menyediakan pemeriksaan logout token. Pakai pustaka JWT seperti jose: verifikasi dengan createRemoteJWKSet(new URL(ISSUER + "/keys")), periksa iss, aud, klaim events, dan tidak adanya nonce, lalu cabut sesi yang sid-nya sama. Rincian di Logout dan sesi.