@smontero/fastify-az-jwt-verify
Version:
Azure JWT token verification plugin for Fastify
158 lines (136 loc) • 4.98 kB
JavaScript
/**
* Copyright (c) Microsoft Corporation
* All Rights Reserved
* MIT License
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of this
* software and associated documentation files (the 'Software'), to deal in the Software
* without restriction, including without limitation the rights to use, copy, modify,
* merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to the following
* conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
* OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT
* OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
const axios = require('axios')
const aadutils = require('./aadutils')
class Metadata {
static async fetch({
log,
url,
authtype,
options
}) {
try {
const { data } = await axios.get(url)
const metadata = new Metadata({
url,
authtype,
options,
metadata: data
})
await metadata.updateOidcMetadata(log)
return metadata
} catch (error) {
log.info(error)
throw new Error(`Cannot get AAD Federation metadata from: ${url}`)
}
}
constructor({
url,
authtype,
options,
metadata
}) {
if (!url) {
throw new Error('Metadata: url is a required argument')
}
if (!authtype || authtype !== 'oidc') {
throw new Error('Invalid authtype. authtype must be \'oidc\'')
}
this.url = url
this.metadata = metadata
this.authtype = authtype
this.loggingNoPII = options.loggingNoPII
}
async updateOidcMetadata(log) {
log.info('Request to update the Open ID Connect Metadata')
const doc = this.metadata
var oidc = {}
oidc.algorithms = doc.id_token_signing_alg_values_supported
oidc.authorization_endpoint = doc.authorization_endpoint
oidc.end_session_endpoint = doc.end_session_endpoint
oidc.issuer = doc.issuer
oidc.token_endpoint = doc.token_endpoint
oidc.userinfo_endpoint = doc.userinfo_endpoint
this.oidc = oidc
const jwksUri = doc.jwks_uri
if (!this.loggingNoPII) {
log.info('Algorithm retrieved was: ', oidc.algorithms)
log.info('Issuer we are using is: ', oidc.issuer)
log.info('Key Endpoint we will use is: ', jwksUri)
log.info('Authentication endpoint we will use is: ', oidc.authorization_endpoint)
log.info('Token endpoint we will use is: ', oidc.token_endpoint)
log.info('User info endpoint we will use is: ', oidc.userinfo_endpoint)
log.info('The logout endpoint we will use is: ', oidc.end_session_endpoint)
}
// fetch the signing keys
try {
const { data: { keys } } = await axios.get(jwksUri)
oidc.keys = keys
} catch (error) {
log.info(error)
throw new Error(`Cannot get AAD Signing Keys from: ${jwksUri}`)
}
}
generateOidcPEM(log, kid) {
const keys = this && this.oidc && Array.isArray(this.oidc.keys) ? this.oidc.keys : null
let pubKey = null
let foundKey = false
const loggingNoPII = this.loggingNoPII
if (!kid) {
throw new Error('kid is missing')
}
if (!keys) {
throw new Error('keys is missing')
}
keys.some((key) => {
if (loggingNoPII) { log.info('working on key') } else { log.info('working on key:', key) }
// are we working on the right key?
if (key.kid !== kid) {
return false
}
// check for `modulus` to be present
if (!key.n) {
if (loggingNoPII) { log.warn('modulus is empty; corrupt key') } else { log.warn('modulus is empty; corrupt key', key) }
return false
}
// check for `exponent` to be present
if (!key.e) {
if (loggingNoPII) { log.warn('exponent is empty; corrupt key') } else { log.warn('exponent is empty; corrupt key', key) }
return false
}
// generate PEM from `modulus` and `exponent`
pubKey = aadutils.rsaPublicKeyPem(key.n, key.e)
foundKey = true
return pubKey
})
if (!foundKey) {
if (loggingNoPII) { throw new Error('a key with the specific kid cannot be found') } else { throw new Error('a key with kid %s cannot be found', kid) }
}
if (!pubKey) {
if (loggingNoPII) { throw new Error('generating public key pem failed') } else { throw new Error('generating public key pem failed for kid: %s', kid) }
}
return pubKey
}
}
module.exports = Metadata