@strongnguyen/oidc-provider
Version:
OAuth 2.0 Authorization Server implementation for Node.js with OpenID Connect
75 lines (61 loc) • 1.8 kB
JavaScript
const { createHash } = require('crypto');
const {
jwtVerify,
EmbeddedJWK,
calculateJwkThumbprint,
} = require('jose');
const { InvalidDpopProof } = require('./errors');
const instance = require('./weak_cache');
const base64url = require('./base64url');
module.exports = async (ctx, accessToken) => {
const {
clockTolerance,
features: { dPoP: dPoPConfig },
dPoPSigningAlgValues,
} = instance(ctx.oidc.provider).configuration();
if (!dPoPConfig.enabled) {
return undefined;
}
const token = ctx.get('DPoP');
if (!token) {
return undefined;
}
try {
let jwk;
const { payload } = await jwtVerify(
token,
(...args) => {
([{ jwk }] = args);
return EmbeddedJWK(...args);
},
{
maxTokenAge: dPoPConfig.iatTolerance,
clockTolerance,
algorithms: dPoPSigningAlgValues,
typ: 'dpop+jwt',
},
);
if (typeof payload.jti !== 'string' || !payload.jti) {
throw new InvalidDpopProof('DPoP Proof must have a jti string property');
}
if (payload.htm !== ctx.method) {
throw new InvalidDpopProof('DPoP Proof htm mismatch');
}
if (payload.htu !== ctx.oidc.urlFor(ctx.oidc.route)) {
throw new InvalidDpopProof('DPoP Proof htu mismatch');
}
if (accessToken) {
const ath = base64url.encode(createHash('sha256').update(accessToken).digest());
if (payload.ath !== ath) {
throw new InvalidDpopProof('DPoP Proof ath mismatch');
}
}
const thumbprint = await calculateJwkThumbprint(jwk);
return { thumbprint, jti: payload.jti, iat: payload.iat };
} catch (err) {
if (err instanceof InvalidDpopProof) {
throw err;
}
throw new InvalidDpopProof('invalid DPoP key binding', err.message);
}
};