Created
May 4, 2022 21:56
-
-
Save ajmas/ec9f1a1020982f028e486efd142d47ae to your computer and use it in GitHub Desktop.
Code for dealing with OpenId tokens, in node.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import axios from 'axios'; | |
import { Request, Response, NextFunction } from 'express'; | |
import jwt, { GetPublicKeyOrSecret, Secret } from 'jsonwebtoken'; | |
import jwksClient, { JwksClient } from 'jwks-rsa'; | |
import UnauthenticatedError from '../errors/UnauthenticatedError'; | |
import { getLogger } from './logging'; | |
const logger = getLogger('openid'); | |
/** | |
* Home crafted OpenId solution, since we weren't finding anything | |
* useful or straightforward that already existed. | |
*/ | |
class OpenIdProvider { | |
baseUrl: string; | |
openIdConfig: Record<string, any>; | |
jwksClient: JwksClient; | |
constructor (baseUrl: string) { | |
this.baseUrl = baseUrl; | |
} | |
async init () { | |
const url = `${this.baseUrl}/.well-known/openid-configuration`; | |
logger.debug(`OpenIdProvider URL: ${url}`); | |
const response = await axios.get(url); | |
if (response.data) { | |
this.openIdConfig = response.data; | |
} | |
logger.debug(`OpenIdProvider openid-configuration: ${JSON.stringify(this.openIdConfig, undefined, 2)}`); | |
logger.debug(`OpenIdProvider jwks_uri: ${this.openIdConfig.jwks_uri}`); | |
this.jwksClient = jwksClient({ | |
jwksUri: this.openIdConfig.jwks_uri | |
}); | |
} | |
private async jwtVerify (token: string, secretOrPublicKey: Secret | GetPublicKeyOrSecret): Promise<object | undefined> { | |
return new Promise<object | undefined>((resolve, reject) => { | |
jwt.verify(token, secretOrPublicKey, (err: any, decoded: object | undefined) => { | |
if (err) { | |
reject(err); | |
} else { | |
resolve(decoded); | |
} | |
}); | |
}); | |
} | |
private async getKey (header, callback) { | |
this.jwksClient.getSigningKey(header.kid, (error: any, key: Record<string, any>) => { | |
if (error) { | |
callback(error); | |
} | |
if (key) { | |
const signingKey = key.publicKey || key.rsaPublicKey; | |
callback(null, signingKey); | |
} else { | |
callback(new Error('getSigningKey resulted in null key')); | |
} | |
}); | |
} | |
async getUser (token: string): Promise<Record<string, any>> { | |
const response = await axios.get(this.openIdConfig.userinfo_endpoint, { | |
headers: { | |
'authorization': `Bearer ${token}` | |
} | |
}); | |
if (response.data) { | |
return response.data; | |
} | |
return undefined; | |
} | |
private async verifyToken (token: string): Promise<Record<string, any>> { | |
try { | |
return this.jwtVerify(token, this.getKey.bind(this)); | |
} catch (error) { | |
throw new UnauthenticatedError('expired or invalid jwt'); | |
} | |
} | |
middleware (postAuthHandler?: Function) { | |
return async (req: Request, res: Response, next: NextFunction) => { | |
try { | |
logger.debug('authorization', req.headers.authorization); | |
// if there is no token then pass on through. we do this, since | |
// some endpoints may not need an authentication session for access | |
if (req.headers.authorization && req.headers.authorization.toLowerCase().indexOf('bearer ') === 0) { | |
const token = req.headers.authorization.split(' ')[1]; | |
const decodedToken = await this.verifyToken(token); | |
res.locals.decodedOpenIdToken = decodedToken; | |
res.locals.user = await this.getUser(token); | |
if (res.locals.user.sub) { | |
res.locals.user.id = res.locals.user.sub; | |
} | |
// This is somewhat of a hack and needs a better approach | |
if (postAuthHandler) { | |
res.locals.user = postAuthHandler(res.locals.user); | |
} | |
req.user = res.locals.user; | |
} | |
next(); | |
} catch (error) { | |
next(error); | |
} | |
}; | |
} | |
} | |
async function createProvider (baseUrl: string) { | |
const provider = new OpenIdProvider(baseUrl); | |
await provider.init(); | |
return provider; | |
} | |
export default OpenIdProvider; | |
export { createProvider }; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment