Skip to content

Commit

Permalink
chore: update to openid-client v6
Browse files Browse the repository at this point in the history
  • Loading branch information
rdubigny committed Oct 25, 2024
1 parent 6d3ba08 commit 2e830dc
Show file tree
Hide file tree
Showing 2 changed files with 71 additions and 52 deletions.
121 changes: 70 additions & 51 deletions index.js
Original file line number Diff line number Diff line change
@@ -1,15 +1,11 @@
import "dotenv/config";
import express from "express";
import { Issuer } from "openid-client";
import * as client from "openid-client";
import session from "express-session";
import morgan from "morgan";
import * as crypto from "crypto";
import bodyParser from "body-parser";

const port = parseInt(process.env.PORT, 10) || 3000;
const origin = `${process.env.HOST}`;
const redirectUri = `${origin}${process.env.CALLBACK_URL}`;

const app = express();

app.set("view engine", "ejs");
Expand All @@ -22,31 +18,41 @@ app.use(
);
app.use(morgan("combined"));

const removeNullValues = (obj) => Object.entries(obj).reduce((a,[k,v]) => (v ? (a[k]=v, a) : a), {})

const getMcpClient = async () => {
const mcpIssuer = await Issuer.discover(process.env.PC_PROVIDER);

return new mcpIssuer.Client({
client_id: process.env.PC_CLIENT_ID,
client_secret: process.env.PC_CLIENT_SECRET,
redirect_uris: [redirectUri],
response_types: ["code"],
id_token_signed_response_alg: process.env.PC_ID_TOKEN_SIGNED_RESPONSE_ALG,
userinfo_signed_response_alg:
process.env.PC_USERINFO_SIGNED_RESPONSE_ALG || null,
});
const removeNullValues = (obj) =>
Object.entries(obj).reduce((a, [k, v]) => (v ? ((a[k] = v), a) : a), {});

const objToUrlParams = (obj) =>
new URLSearchParams(
Object.fromEntries(
Object.entries(obj).map(([k, v]) => [
k,
// stringify objects
typeof v === "object" && v !== null ? JSON.stringify(v) : v,
]),
),
);

const getCurrentUrl = (req) =>
new URL(`${req.protocol}://${req.get("host")}${req.originalUrl}`);

const getProviderConfig = async () => {
return await client.discovery(
new URL(process.env.PC_PROVIDER),
process.env.PC_CLIENT_ID,
{
client_secret: process.env.PC_CLIENT_SECRET,
id_token_signed_response_alg: process.env.PC_ID_TOKEN_SIGNED_RESPONSE_ALG,
userinfo_signed_response_alg:
process.env.PC_USERINFO_SIGNED_RESPONSE_ALG || null,
},
);
};

const acr_values = process.env.ACR_VALUES
? process.env.ACR_VALUES.split(",")
: null;
const login_hint = process.env.LOGIN_HINT || null;
const scope = process.env.PC_SCOPES;
const AUTHORIZATION_DEFAULT_PARAMS = {
scope,
login_hint,
acr_values,
redirect_uri: `${process.env.HOST}${process.env.CALLBACK_URL}`,
scope: process.env.PC_SCOPES,
login_hint: process.env.LOGIN_HINT || null,
acr_values: process.env.ACR_VALUES ? process.env.ACR_VALUES.split(",") : null,
claims: {
id_token: {
amr: {
Expand Down Expand Up @@ -75,19 +81,24 @@ app.get("/", async (req, res, next) => {
const getAuthorizationControllerFactory = (extraParams) => {
return async (req, res, next) => {
try {
const client = await getMcpClient();
const nonce = crypto.randomBytes(16).toString("hex");
const state = crypto.randomBytes(16).toString("hex");
const config = await getProviderConfig();
const nonce = client.randomNonce();
const state = client.randomState();

req.session.state = state;
req.session.nonce = nonce;

const redirectUrl = client.authorizationUrl(removeNullValues({
nonce,
state,
...AUTHORIZATION_DEFAULT_PARAMS,
...extraParams,
}));
const redirectUrl = client.buildAuthorizationUrl(
config,
objToUrlParams(
removeNullValues({
nonce,
state,
...AUTHORIZATION_DEFAULT_PARAMS,
...extraParams,
}),
),
);

res.redirect(redirectUrl);
} catch (e) {
Expand Down Expand Up @@ -143,27 +154,32 @@ app.post(
"/custom-connection",
bodyParser.urlencoded({ extended: false }),
(req, res, next) => {
const customParams = JSON.parse(req.body['custom-params'])
const customParams = JSON.parse(req.body["custom-params"]);

return getAuthorizationControllerFactory(customParams)(req, res, next);
},
);

app.get(process.env.CALLBACK_URL, async (req, res, next) => {
try {
const client = await getMcpClient();
const params = client.callbackParams(req);
const tokenSet = await client.callback(redirectUri, params, {
nonce: req.session.nonce,
state: req.session.state,
const config = await getProviderConfig();
const currentUrl = getCurrentUrl(req);
const tokens = await client.authorizationCodeGrant(config, currentUrl, {
expectedNonce: req.session.nonce,
expectedState: req.session.state,
});

req.session.nonce = null;
req.session.state = null;
req.session.userinfo = await client.userinfo(tokenSet.access_token);
req.session.idtoken = tokenSet.claims();
req.session.id_token_hint = tokenSet.id_token;
req.session.oauth2token = tokenSet;
const claims = tokens.claims();
req.session.userinfo = await client.fetchUserInfo(
config,
tokens.access_token,
claims.sub,
);
req.session.idtoken = claims;
req.session.id_token_hint = tokens.id_token;
req.session.oauth2token = tokens;
res.redirect("/");
} catch (e) {
next(e);
Expand All @@ -174,11 +190,14 @@ app.post("/logout", async (req, res, next) => {
try {
const id_token_hint = req.session.id_token_hint;
req.session.destroy();
const client = await getMcpClient();
const redirectUrl = client.endSessionUrl({
post_logout_redirect_uri: `${origin}/`,
id_token_hint,
});
const config = await getProviderConfig();
const redirectUrl = client.buildEndSessionUrl(
config,
objToUrlParams(removeNullValues({
post_logout_redirect_uri: `${process.env.HOST}/`,
id_token_hint,
})),
);

res.redirect(redirectUrl);
} catch (e) {
Expand Down
2 changes: 1 addition & 1 deletion views/index.ejs
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@
<p>
La liste des paramètres utilisables est disponible dans la
<a
href="https://github.com/panva/openid-client/blob/v5.x/docs/README.md#clientauthorizationurlparameters"
href="https://github.com/panva/openid-client/blob/v6.x/docs/README.md#clientauthorizationurlparameters"
target="_blank"
rel="noopener noreferrer"
>
Expand Down

0 comments on commit 2e830dc

Please sign in to comment.