import { Server, Plugin, Request, ResponseToolkit } from '@hapi/hapi'; import { internal as boomInternal, unauthorized as boomUnauthorized } from '@hapi/boom'; import { decode as jwtDecode } from 'jsonwebtoken'; import * as _get from 'lodash.get'; import { bind } from './utils'; export const HapiAuthAzureB2CPlugin: Plugin = { name: 'azureaccesstoken', // @ts-ignore (registrationOptions) register: (registrationServer: Server, registrationOptions: any) => { registrationServer.auth.scheme('azureaccesstoken', (server: Server, options: any) => { if (!options) { throw boomInternal('options are required for azureaccesstoken auth scheme'); } if (!options.validate) { throw boomInternal('validate method is required'); } return new HapiAuthAzureB2C(server, options); }); } }; export class HapiAuthAzureB2C { // @ts-ignore (server) private server: Server; private options: any; public constructor(server: Server, options: any) { this.server = server; this.options = options; } @bind public async authenticate(request: Request, h: ResponseToolkit) { const { isValid, credentials } = await this.validateRequest(request, h); if (!isValid) { return boomUnauthorized(null, 'azureaccesstoken-auth'); } return h.authenticated({ credentials }); } // @ts-ignore (h) public async validateRequest(request: Request, h: ResponseToolkit) { const accessToken = _get(request, 'payload.context.System.user.accessToken') || ''; try { const claims = jwtDecode(accessToken, { complete: true }); return { isValid: (_get(claims, 'payload.aud') || '') === this.options.azureB2CClientId, credentials: { scope: this.options.azureB2CClientId } }; } catch (ex) { return { isValid: false }; } } }