UNPKG

cloudworker-proxy

Version:
99 lines (82 loc) 2.73 kB
/** * Parse and decode a JWT. * A JWT is three, base64 encoded, strings concatenated with ‘.’: * a header, a payload, and the signature. * The signature is “URL safe”, in that ‘/+’ characters have been replaced by ‘_-’ * * Steps: * 1. Split the token at the ‘.’ character * 2. Base64 decode the individual parts * 3. Retain the raw Bas64 encoded strings to verify the signature */ function decodeJwt(token) { const parts = token.split('.'); const header = JSON.parse(atob(parts[0])); const payload = JSON.parse(atob(parts[1])); const signature = atob(parts[2].replace(/-/g, '+').replace(/_/g, '/')); return { header, payload, signature, raw: { header: parts[0], payload: parts[1], signature: parts[2] }, }; } module.exports = function jwtHandler({ jwksUri, allowPublicAccess = false }) { async function getJwk() { // TODO: override jwksTtl.. const response = await fetch(jwksUri); const body = await response.json(); return body.keys; } async function isValidJwtSignature(token) { const encoder = new TextEncoder(); const data = encoder.encode([token.raw.header, token.raw.payload].join('.')); const signature = new Uint8Array(Array.from(token.signature).map((c) => c.charCodeAt(0))); const jwkKeys = await getJwk(); const validations = await Promise.all( jwkKeys.map(async (jwkKey) => { const key = await crypto.subtle.importKey( 'jwk', jwkKey, { name: 'RSASSA-PKCS1-v1_5', hash: 'SHA-256' }, false, ['verify'], ); return crypto.subtle.verify('RSASSA-PKCS1-v1_5', key, signature, data); }), ); return validations.some((result) => result); } /** * Validates the request based on bearer token and cookie * @param {*} ctx * @param {*} next */ async function handleValidate(ctx, next) { // Options requests should not be authenticated if (ctx.request.method === 'OPTIONS') { return next(ctx); } const authHeader = ctx.request.headers.authorization || ''; if (authHeader.toLowerCase().startsWith('bearer')) { const token = decodeJwt(ctx.request.headers.authorization.slice(7)); // Is the token expired? const expiryDate = new Date(token.payload.exp * 1000); const currentDate = new Date(Date.now()); if (expiryDate <= currentDate) { return false; } if (await isValidJwtSignature(token)) { ctx.state.user = token.payload; return next(ctx); } } if (allowPublicAccess) { return next(ctx); } ctx.status = 403; ctx.body = 'Forbidden'; return ctx; } return handleValidate; };