UNPKG

oidc-provider

Version:

OAuth 2.0 Authorization Server implementation for Node.js with OpenID Connect

199 lines (171 loc) 6.21 kB
import * as crypto from 'node:crypto'; import * as JWT from '../../helpers/jwt.js'; import instance from '../../helpers/weak_cache.js'; import nanoid from '../../helpers/nanoid.js'; import als from '../../helpers/als.js'; export default (provider, { opaque }) => { async function getResourceServerConfig(token) { const { keystore, configuration } = instance(provider); const { clientDefaults: { id_token_signed_response_alg: defaultAlg } } = configuration; let sign; let encrypt; { let alg; let key; let kid; if (token.resourceServer) { if (token.resourceServer.jwt?.sign) { ({ alg = defaultAlg, key, kid } = token.resourceServer.jwt.sign); } else if ( !token.resourceServer.jwt || (!token.resourceServer.jwt.sign && !token.resourceServer.jwt.encrypt) ) { alg = defaultAlg; } } if (alg === 'none') { throw new Error('JWT Access Tokens may not use JWS algorithm "none"'); } else if (alg) { if (alg.startsWith('HS')) { if (!key) { throw new Error('missing jwt.sign.key Resource Server configuration'); } if (!(key instanceof crypto.KeyObject || key instanceof CryptoKey)) { key = crypto.createSecretKey(key); } if (key.type !== 'secret') { throw new Error('jwt.sign.key Resource Server configuration must be a secret (symmetric) key'); } } else { [key] = keystore.selectForVerify({ alg, use: 'sig', kid }); if (!key) { throw new Error('resolved Resource Server jwt configuration has no corresponding key in the provider\'s keystore'); } kid = key.kid; key = keystore.getKeyObject(key); } if (kid !== undefined && typeof kid !== 'string') { throw new Error('jwt.sign.kid must be a string when provided'); } sign = { alg, key, kid }; } } if (token.resourceServer?.jwt?.encrypt) { const { alg, enc, kid } = token.resourceServer.jwt.encrypt; let { key } = token.resourceServer.jwt.encrypt; if (!alg) { throw new Error('missing jwt.encrypt.alg Resource Server configuration'); } if (!enc) { throw new Error('missing jwt.encrypt.enc Resource Server configuration'); } if (!key) { throw new Error('missing jwt.encrypt.key Resource Server configuration'); } if (!(key instanceof crypto.KeyObject || key instanceof CryptoKey) && /^(A|dir$)/.test(alg)) { key = crypto.createSecretKey(key); } if (key.type === 'private') throw new Error('jwt.encrypt.key Resource Server configuration must be a secret (symmetric) or a public key'); if (key.type === 'public' && !sign) throw new Error('missing jwt.sign Resource Server configuration'); if (kid !== undefined && typeof kid !== 'string') { throw new Error('jwt.encrypt.kid must be a string when provided'); } encrypt = { alg, enc, key, kid, }; } return { sign, encrypt }; } return { generateTokenId() { return nanoid(); }, async getValueAndPayload() { const { payload } = await opaque.getValueAndPayload.call(this); const { aud, jti, iat, exp, scope, clientId, 'x5t#S256': x5t, jkt, extra, rar, } = payload; let { accountId: sub } = payload; if (sub) { const { client } = this; if (client?.clientId !== clientId) { throw new TypeError('clientId and client mismatch'); } if (client.subjectType === 'pairwise') { const { pairwiseIdentifier } = instance(provider).configuration; sub = await pairwiseIdentifier(als.getStore(), sub, client); } } const tokenPayload = { ...extra, jti, sub: sub || clientId, iat, exp, authorization_details: rar, scope: scope || undefined, client_id: clientId, iss: provider.issuer, aud, ...(x5t || jkt ? { cnf: {} } : undefined), }; if (x5t) { tokenPayload.cnf['x5t#S256'] = x5t; } if (jkt) { tokenPayload.cnf.jkt = jkt; } const structuredToken = { header: undefined, payload: tokenPayload, }; const customizer = instance(provider).configuration.formats.customizers.jwt; if (customizer) { await customizer(als.getStore(), this, structuredToken); } if (!structuredToken.payload.aud) { throw new Error('JWT Access Tokens must contain an audience, for Access Tokens without audience (only usable at the userinfo_endpoint) use an opaque format'); } const config = await getResourceServerConfig(this); let signed; if (config.sign) { signed = await JWT.sign(structuredToken.payload, config.sign.key, config.sign.alg, { typ: 'at+jwt', fields: { kid: config.sign.kid, ...structuredToken.header }, }); } if (config.sign && config.encrypt) { const encrypted = await JWT.encrypt(signed, config.encrypt.key, { fields: { kid: config.encrypt.kid, iss: provider.issuer, aud: structuredToken.payload.aud, cty: 'at+jwt', }, enc: config.encrypt.enc, alg: config.encrypt.alg, }); return { value: encrypted }; } if (config.sign) { return { value: signed }; } if (config.encrypt) { const cleartext = JSON.stringify(structuredToken.payload); const encrypted = await JWT.encrypt(cleartext, config.encrypt.key, { fields: { kid: config.encrypt.kid, iss: provider.issuer, aud: structuredToken.payload.aud, typ: 'at+jwt', ...structuredToken.header, }, enc: config.encrypt.enc, alg: config.encrypt.alg, }); return { value: encrypted }; } throw new Error('invalid Resource Server jwt configuration'); }, }; };