Skip to content

Instantly share code, notes, and snippets.

@PaperNick
Forked from westmark/auth0-verify.js
Last active August 17, 2022 19:21
Show Gist options
  • Save PaperNick/1fa0fa9f280b1d29a0a8f8e1b31df1e2 to your computer and use it in GitHub Desktop.
Save PaperNick/1fa0fa9f280b1d29a0a8f8e1b31df1e2 to your computer and use it in GitHub Desktop.

Revisions

  1. PaperNick revised this gist Aug 17, 2022. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion auth0-verify.js
    Original file line number Diff line number Diff line change
    @@ -23,7 +23,7 @@ const jwksClient = jwksRsa({
    /**
    * Attempt to parse the given JWT token. Throws an Error if token is invalid.
    *
    * @param {string} token
    * @param {string} token JWT token without "Bearer " in the beginning
    * @throws {Error}
    * @returns {JwtPayload}
    */
  2. PaperNick revised this gist Aug 17, 2022. 1 changed file with 58 additions and 115 deletions.
    173 changes: 58 additions & 115 deletions auth0-verify.js
    Original file line number Diff line number Diff line change
    @@ -1,129 +1,72 @@
    /**
    The MIT License (MIT)
    Copyright (c) 2017 Fredrik Westmark
    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.
    **/
    * The MIT License (MIT)
    * Copyright (c) 2022 PaperNick
    *
    * Resouces used:
    * https://github.com/auth0/node-jwks-rsa
    * https://github.com/auth0/node-jsonwebtoken
    * https://auth0.com/blog/navigating-rs256-and-jwks/
    * https://gist.github.com/westmark/faee223e05bcbab433bfd4ed8e36fb5f
    * https://auth0.com/blog/critical-vulnerabilities-in-json-web-token-libraries/
    */

    const AUTH0_AUTHORITY = 'https://auth0.tenant.com';
    const AUTH0_AUDIENCE = 'https://api.yourapp.com/';

    const jwksRsa = require('jwks-rsa');
    const jwt = require('jsonwebtoken');

    const jwksClient = jwksRsa({
    jwksUri: `${AUTH0_AUTHORITY.replace(/\/+$/, '')}/.well-known/jwks.json`,
    });

    /**
    Based on https://auth0.com/blog/navigating-rs256-and-jwks/
    **/

    const request = require( 'request' );
    const jwt = require( 'jsonwebtoken' );

    function certToPEM( cert ) {
    let pem = cert.match( /.{1,64}/g ).join( '\n' );
    pem = `-----BEGIN CERTIFICATE-----\n${ cert }\n-----END CERTIFICATE-----\n`;
    return pem;
    }

    let jwks = null;

    function fetchJWKS( tenant ) {
    if ( jwks ) {
    return Promise.resolve();
    * Attempt to parse the given JWT token. Throws an Error if token is invalid.
    *
    * @param {string} token
    * @throws {Error}
    * @returns {JwtPayload}
    */
    async function parseJwtToken(token) {
    if (!token) {
    throw new Error('Missing authorization token');
    }
    return new Promise( ( resolve, reject ) => {
    request(
    {
    uri: `https://${ tenant }/.well-known/jwks.json`,
    strictSsl: true,
    json: true,
    },
    ( err, res ) => {
    if ( err ) {
    reject( err );
    } else if ( res.statusCode < 200 || res.statusCode >= 300 ) {
    reject( new Error( res.body && ( res.body.message || res.body ) ) );
    } else {
    jwks = res.body.keys;
    resolve();
    }
    }
    );
    } );
    }

    function getJWKS() {
    return jwks;
    }

    function getJWKSSigningKeys() {
    return jwks
    .filter(
    ( key ) =>
    key.use === 'sig' && // JWK property `use` determines the JWK is for signing
    key.kty === 'RSA' && // We are only supporting RSA (RS256)
    key.kid && // The `kid` must be present to be useful for later
    ( ( key.x5c && key.x5c.length ) || ( key.n && key.e ) ) // Has useful public keys
    )
    .map( ( key ) => ( { kid: key.kid, nbf: key.nbf, publicKey: certToPEM( key.x5c[ 0 ] ) } ) );
    }

    function getJWKSSigningKey( kid ) {
    return getJWKSSigningKeys().find( ( key ) => key.kid === kid );
    }

    function extractAuthenicationToken( req ) {
    const authHeader = req.headers.authorization;
    const parts = authHeader.split( ' ' );

    if ( parts.length !== 2 ) {
    throw new Error( 'credentials_required', { message: 'No authorization token was found' } );
    // Decode without verifying if the signature is valid.
    // Warning: do not access decoded token payload before verifying with jwt.verify()
    const decodedToken = jwt.decode(token, { complete: true });
    if (!decodedToken || decodedToken.header.alg !== 'RS256') {
    // Only RS256 tokens are supported at the moment
    throw new Error('Invalid token or algorithm');
    }

    const scheme = parts[ 0 ];
    if ( !/^Bearer$/i.test( scheme ) ) {
    throw new Error( 'credentials_bad_scheme', {
    message: 'Format is Authorization: Bearer [token]',
    } );
    let signingKey;
    try {
    signingKey = await jwksClient.getSigningKey(decodedToken.header.kid);
    } catch (error) {
    throw new Error('Could not retrieve key to verify token');
    }

    return parts[ 1 ];
    try {
    const jwtPayload = jwt.verify(token, signingKey.getPublicKey(), { audience: AUTH0_AUDIENCE });
    return jwtPayload;
    } catch (error) {
    throw new Error('The token is invalid or has expired');
    }
    }

    async function verifyJWTToken( tenant, req ) {
    await fetchJWKS( tenant );
    const token = extractAuthenicationToken( req );
    const decodedToken = jwt.decode( token, { complete: true } );
    const { header } = decodedToken;

    if ( !header || header.alg !== 'RS256' ) {
    throw new Error( 'Token is not RS256 encoded' );
    /**
    * Attempt to parse the given JWT token. Returns undefined on error.
    *
    * @param {string} token
    * @returns {JwtPayload|undefined}
    */
    async function parseJwtTokenQuiet(token) {
    try {
    return await parseJwtToken(token);
    } catch (error) {
    return;
    }

    const key = getJWKSSigningKey( header.kid );
    const actualKey = key.publicKey || key.rsaPublicKey;

    return new Promise( ( resolve, reject ) => {
    jwt.verify( token, actualKey, { algorithms: [ 'RS256' ] }, ( err, decoded ) => {
    if ( err ) {
    reject( new Error( 'invalid_token', err ) );
    } else {
    resolve( decoded );
    }
    } );
    } );
    }

    module.exports = {
    verifyJWTToken,
    };
    module.exports = { parseJwtToken, parseJwtTokenQuiet };
  3. @westmark westmark revised this gist Nov 23, 2017. 1 changed file with 28 additions and 0 deletions.
    28 changes: 28 additions & 0 deletions auth0-verify.js
    Original file line number Diff line number Diff line change
    @@ -1,3 +1,31 @@
    /**
    The MIT License (MIT)
    Copyright (c) 2017 Fredrik Westmark
    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.
    **/

    /**
    Based on https://auth0.com/blog/navigating-rs256-and-jwks/
    **/

    const request = require( 'request' );
    const jwt = require( 'jsonwebtoken' );

  4. @westmark westmark created this gist Nov 23, 2017.
    101 changes: 101 additions & 0 deletions auth0-verify.js
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,101 @@
    const request = require( 'request' );
    const jwt = require( 'jsonwebtoken' );

    function certToPEM( cert ) {
    let pem = cert.match( /.{1,64}/g ).join( '\n' );
    pem = `-----BEGIN CERTIFICATE-----\n${ cert }\n-----END CERTIFICATE-----\n`;
    return pem;
    }

    let jwks = null;

    function fetchJWKS( tenant ) {
    if ( jwks ) {
    return Promise.resolve();
    }
    return new Promise( ( resolve, reject ) => {
    request(
    {
    uri: `https://${ tenant }/.well-known/jwks.json`,
    strictSsl: true,
    json: true,
    },
    ( err, res ) => {
    if ( err ) {
    reject( err );
    } else if ( res.statusCode < 200 || res.statusCode >= 300 ) {
    reject( new Error( res.body && ( res.body.message || res.body ) ) );
    } else {
    jwks = res.body.keys;
    resolve();
    }
    }
    );
    } );
    }

    function getJWKS() {
    return jwks;
    }

    function getJWKSSigningKeys() {
    return jwks
    .filter(
    ( key ) =>
    key.use === 'sig' && // JWK property `use` determines the JWK is for signing
    key.kty === 'RSA' && // We are only supporting RSA (RS256)
    key.kid && // The `kid` must be present to be useful for later
    ( ( key.x5c && key.x5c.length ) || ( key.n && key.e ) ) // Has useful public keys
    )
    .map( ( key ) => ( { kid: key.kid, nbf: key.nbf, publicKey: certToPEM( key.x5c[ 0 ] ) } ) );
    }

    function getJWKSSigningKey( kid ) {
    return getJWKSSigningKeys().find( ( key ) => key.kid === kid );
    }

    function extractAuthenicationToken( req ) {
    const authHeader = req.headers.authorization;
    const parts = authHeader.split( ' ' );

    if ( parts.length !== 2 ) {
    throw new Error( 'credentials_required', { message: 'No authorization token was found' } );
    }

    const scheme = parts[ 0 ];
    if ( !/^Bearer$/i.test( scheme ) ) {
    throw new Error( 'credentials_bad_scheme', {
    message: 'Format is Authorization: Bearer [token]',
    } );
    }

    return parts[ 1 ];
    }

    async function verifyJWTToken( tenant, req ) {
    await fetchJWKS( tenant );
    const token = extractAuthenicationToken( req );
    const decodedToken = jwt.decode( token, { complete: true } );
    const { header } = decodedToken;

    if ( !header || header.alg !== 'RS256' ) {
    throw new Error( 'Token is not RS256 encoded' );
    }

    const key = getJWKSSigningKey( header.kid );
    const actualKey = key.publicKey || key.rsaPublicKey;

    return new Promise( ( resolve, reject ) => {
    jwt.verify( token, actualKey, { algorithms: [ 'RS256' ] }, ( err, decoded ) => {
    if ( err ) {
    reject( new Error( 'invalid_token', err ) );
    } else {
    resolve( decoded );
    }
    } );
    } );
    }

    module.exports = {
    verifyJWTToken,
    };