Skip to content

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-session
js
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.