From 437cbb190787281c4be6a86014b6adaff8caef34 Mon Sep 17 00:00:00 2001 From: hc Date: Fri, 13 Feb 2026 11:49:19 +0800 Subject: init --- server.js | 107 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 107 insertions(+) create mode 100644 server.js (limited to 'server.js') diff --git a/server.js b/server.js new file mode 100644 index 0000000..5b373da --- /dev/null +++ b/server.js @@ -0,0 +1,107 @@ +const express = require("express"); +const crypto = require("crypto"); +const querystring = require("querystring"); +const jwt = require("jsonwebtoken"); +const jwksClient = require("jwks-rsa"); + +const app = express(); +const CLIENT_ID = "ec014b23-edde-4a09-9a41-36bff5630829"; // appID +const CLIENT_SECRET = "Y.Q8Q~HuoEbCsICK18eG4oAtqjMe5eGyWSilLaZI"; // appid secret +const TENANT = "publicanub.onmicrosoft.com"; +const TENANT_ID = "23c95e59-28bd-472a-bbd4-4e310dd8f031"; // organisation id +const REDIRECT_URI = "http://localhost:3000/callback"; +const AUTH_URL = `https://login.microsoftonline.com/${TENANT}/oauth2/v2.0`; +const SCOPES = "openid profile email"; + +// Fetch Microsoft's public key by kid to verify JWT signatures +const keys = jwksClient({ jwksUri: "https://login.microsoftonline.com/common/discovery/v2.0/keys" }); +function getKey(header, cb) { + keys.getSigningKey(header.kid, (err, key) => cb(err, key?.getPublicKey())); +} + +function verifyJwt(token) { + return new Promise((resolve, reject) => { + jwt.verify(token, getKey, { + audience: CLIENT_ID, + // Multi-tenant: accept tokens from any Azure AD tenant + issuer: (iss) => iss.startsWith("https://login.microsoftonline.com/") && iss.endsWith("/v2.0"), + algorithms: ["RS256"], + }, (err, decoded) => err ? reject(err) : resolve(decoded)); + }); +} + +// POST to Microsoft's token endpoint +function fetchTokens(code) { + return new Promise((resolve, reject) => { + const body = querystring.stringify({ + client_id: CLIENT_ID, client_secret: CLIENT_SECRET, + grant_type: "authorization_code", code, redirect_uri: REDIRECT_URI, scope: SCOPES, + }); + const req = require("https").request({ + hostname: "login.microsoftonline.com", + path: `/${TENANT}/oauth2/v2.0/token`, + method: "POST", + headers: { "Content-Type": "application/x-www-form-urlencoded", "Content-Length": Buffer.byteLength(body) }, + }, (res) => { + let data = ""; + res.on("data", (c) => data += c); + res.on("end", () => resolve(JSON.parse(data))); + }); + req.on("error", reject); + req.write(body); + req.end(); + }); +} + +app.get("/", (_, res) => res.send('

OIDC Demo

Sign In with Azure AD')); + +app.get("/login", (_, res) => { + const params = querystring.stringify({ + client_id: CLIENT_ID, response_type: "code", redirect_uri: REDIRECT_URI, + scope: SCOPES, state: crypto.randomBytes(16).toString("hex"), + nonce: crypto.randomBytes(16).toString("hex"), response_mode: "query", + }); + res.redirect(`${AUTH_URL}/authorize?${params}`); +}); + +// Microsoft redirects here with ?code=&state= +// The code is a one-time-use temporary key that we exchange + client_secret for JWT tokens +app.get("/callback", async (req, res) => { + const { code, error, error_description } = req.query; + if (error) return res.send(`
Error: ${error}: ${error_description}
`); + + try { + const tokens = await fetchTokens(code); + if (tokens.error) return res.send(`
Token error: ${JSON.stringify(tokens, null, 2)}
Home`); + + // Verify JWT signature against Microsoft's public key + check aud, iss, exp + const v = await verifyJwt(tokens.id_token); + + // // Only allow specific tenants + // const ALLOWED_TENANTS = ["23c95e59-28bd-472a-bbd4-4e310dd8f031"]; + // if (!ALLOWED_TENANTS.includes(v.tid)) { + // return res.status(403).send(`
Tenant not allowed: ${v.tid}
Home`); + // } + + res.send(` + +

JWT Verified

+

Callback received (req.query)

+
${JSON.stringify(req.query, null, 2)}
+

This code was exchanged + client_secret for the tokens below

+

Checks

+

Signature: VALID (RS256, Microsoft public key)

+

Audience: ${v.aud} | Issuer: ${v.iss} (tenant: ${v.tid})

+

Expires: ${new Date(v.exp * 1000).toISOString()}

+

User

+

oid: ${v.oid} | ${v.name} | ${v.preferred_username}

+

Claims

+
${JSON.stringify(v, null, 2)}
+ Start over + `); + } catch (err) { + res.status(401).send(`
JWT verification failed: ${err.message}
Try again`); + } +}); + +app.listen(3000, () => console.log("http://localhost:3000")); -- cgit