UNPKG

@smontero/fastify-az-jwt-verify

Version:

Azure JWT token verification plugin for Fastify

158 lines (136 loc) 4.98 kB
/** * 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. */ 'use strict' 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