Skip to content

Instantly share code, notes, and snippets.

@bcnzer
Last active July 2, 2024 10:18
Show Gist options
  • Save bcnzer/e6a7265fd368fa22ef960b17b9a76488 to your computer and use it in GitHub Desktop.
Save bcnzer/e6a7265fd368fa22ef960b17b9a76488 to your computer and use it in GitHub Desktop.
Sample Cloudflare worker that gets the JWT, ensures it hasn't expired, decrypts it and returns a result
addEventListener('fetch', event => {
event.respondWith(handleRequest(event.request))
})
// Following code is a modified version of that found at https://blog.cloudflare.com/dronedeploy-and-cloudflare-workers/
/**
* Fetch and log a request
* @param {Request} request
*/
async function handleRequest(request) {
let isValid = await isValidJwt(request)
if (!isValid) {
// It is immediately failing here, which is great. The worker doesn't bother hitting your API
console.log('is NOT valid')
return new Response('Invalid JWT', { status: 403 })
} else {
console.log('is valid')
}
console.log('Got request', request)
const response = await fetch(request)
console.log('Got response', response)
return response
}
/**
* 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) {
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) {
const authHeader = request.headers.get('Authorization');
if (!authHeader || authHeader.substring(0, 6) !== 'Bearer') {
return null
}
return authHeader.substring(6).trim()
}
/**
* 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, '+'));
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) {
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 jwk = {
alg: 'RS256',
e: 'AQAB',
ext: true,
key_ops: ['verify'],
kty: 'RSA',
n: RSA_PUBLIC_KEY
};
*/
// You need to JWK data with whatever is your public RSA key. If you're using Auth0 you
// can download it from https://[your_domain].auth0.com/.well-known/jwks.json
const jwk = {
alg: "RS256",
kty: "RSA",
key_ops: ['verify'],
use: "sig",
x5c: ["REPLACE-ME-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"],
n: "REPLACE-ME-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
e: "AQAB",
kid: "REPLACE-ME-ccccccccccccccccccccccccccccccccccccccccccccccccc",
x5t: "REPLACE-ME-ddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd"
}
const key = await crypto.subtle.importKey('jwk', jwk, { name: 'RSASSA-PKCS1-v1_5', hash: 'SHA-256' }, false, ['verify']);
return crypto.subtle.verify('RSASSA-PKCS1-v1_5', key, signature, data)
}
@CanRau
Copy link

CanRau commented Oct 26, 2022

Thanks for sharing 🙏
Alternatively there's tsndr/cloudflare-worker-jwt with signing support

@mckennip
Copy link

Thanks a lot for this, it was a life saver.

I was using this for an Auth0 integration and came across an issue where auth0 getIdTokenClaims would return invalid base64 strings, which led to the atob functions throwing an error. I believe it happens with other providers too.

If anyone else has the same issue you can solve it by using a package like js-base64's "decode" to replacing the atob functions. Or implementing something similar yourself.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment