Forked from bcnzer/cloudflareworker-verifyjwt.js
Last active
September 9, 2019 14:04
-
-
Save CyrusRoshan/3a93d56e7cf1b4cf849c7eecb16116e2 to your computer and use it in GitHub Desktop.
Sample Cloudflare worker that gets a Cloudflare Access JWT, validates it, and returns a result
This file contains hidden or 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 * as cookie from 'cookie'; | |
addEventListener('fetch', event => { | |
event.respondWith(handleRequest(event.request)); | |
}); | |
/** | |
* Fetch and log a request | |
* @param {Request} request | |
*/ | |
export async function handleRequest(request: Request) { | |
var resp: Response; | |
try { | |
resp = await respond(request); | |
} catch (e) { | |
resp = new Response(`Error: ${e}`, {status: 500}); | |
} | |
return resp; | |
} | |
async function respond(request: Request) { | |
let isValid = await isValidJwt(request); | |
if (!isValid) { | |
console.log('is NOT valid'); | |
return new Response('Invalid JWT', {status: 403}); | |
} else { | |
console.log('is valid'); | |
} | |
return new Response('Valid JWT, good job!', {status: 200}); | |
} | |
/** | |
* Parse the JWT and validate it. | |
* | |
* We are just checking that the signature is valid, but you can do more that. | |
* For example, check that the payload has the expected entries or if the signature is expired.. | |
*/ | |
async function isValidJwt(request: Request) { | |
const encodedToken = getJwt(request); | |
if (encodedToken === null) { | |
return false; | |
} | |
const token = decodeJwt(encodedToken); | |
// Is the token expired? | |
let expiryDate = new Date(token.payload.exp * 1000); | |
let currentDate = new Date(Date.now()); | |
if (expiryDate <= currentDate) { | |
console.log('expired token'); | |
return false; | |
} | |
return isValidJwtSignature(token); | |
} | |
/** | |
* For this example, the JWT is passed in as part of the Authorization header, | |
* after the Bearer scheme. | |
* Parse the JWT out of the header and return it. | |
*/ | |
function getJwt(request: Request) { | |
const rawCookieHeader = request.headers.get('Cookie'); | |
if (!rawCookieHeader) { | |
return ''; | |
} | |
const cookies = cookie.parse(rawCookieHeader); | |
if (!cookies) { | |
return ''; | |
} | |
const authJWT = cookies['CF_Authorization']; | |
if (!authJWT) { | |
return ''; | |
} | |
return authJWT; | |
} | |
/** | |
* 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: string) { | |
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, '+')); | |
console.log(header); | |
return { | |
header: header, | |
payload: payload, | |
signature: signature, | |
raw: {header: parts[0], payload: parts[1], signature: parts[2]}, | |
}; | |
} | |
/** | |
* Validate the JWT. | |
* | |
* Steps: | |
* Reconstruct the signed message from the Base64 encoded strings. | |
* Load the RSA public key into the crypto library. | |
* Verify the signature with the message and the key. | |
*/ | |
async function isValidJwtSignature(token: any) { | |
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: any) => c.charCodeAt(0)), | |
); | |
/* | |
const jwk = { | |
alg: 'RS256', | |
e: 'AQAB', | |
ext: true, | |
key_ops: ['verify'], | |
kty: 'RSA', | |
n: RSA_PUBLIC_KEY | |
}; | |
*/ | |
const upToDateJWKReq = await fetch( | |
'https://[CHANGE THIS TO YOUR WEBSITE].cloudflareaccess.com/cdn-cgi/access/certs', | |
); | |
const upToDateJWKJson = await upToDateJWKReq.json(); | |
for (var i = 0; i < upToDateJWKJson.keys.length; i++) { | |
const jwk = Object.assign({key_ops: ['verify']}, upToDateJWKJson.keys[i]); | |
const key = await crypto.subtle.importKey( | |
'jwk', | |
jwk, | |
{name: 'RSASSA-PKCS1-v1_5', hash: 'SHA-256'}, | |
false, | |
['verify'], | |
); | |
if (crypto.subtle.verify('RSASSA-PKCS1-v1_5', key, signature, data)) { | |
return true; | |
} | |
} | |
return false; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment