Skip to content

Instantly share code, notes, and snippets.

@bcnzer
Created October 15, 2018 09:31
Show Gist options
  • Star 38 You must be signed in to star a gist
  • Fork 4 You must be signed in to fork a gist
  • Save bcnzer/04620abc992da72f83f6f1c61d71c93c to your computer and use it in GitHub Desktop.
Save bcnzer/04620abc992da72f83f6f1c61d71c93c to your computer and use it in GitHub Desktop.
Cloudflare Worker that uses Workers KV to get Authorization data. All authentication and authorization is done on the edge
addEventListener('fetch', event => {
event.respondWith(handleRequest(event))
})
/**
* Entry point of the worker
*/
async function handleRequest(event) {
try {
// Get the JWT
const jwtInfo = await getJwtInfo(event.request)
if (jwtInfo == null) {
console.error('Invalid JWT')
return new Response('Invalid JWT', { status: 403 })
}
// Check header
const eventId = isHeaderValid(event.request)
if (eventId === null) {
console.error('400 - missing event ID')
return new Response('Badly formatted request.',
{ status: 400, statusText: 'Bad Request' })
}
// Check authorization data
if (isAuthorized(eventId, jwtInfo.userId) === false) {
console.error('401 - Unauthorized')
return new Response(`Unauthorized for event ${eventId}.`,
{ status: 401, statusText: 'Unauthorized' })
}
console.log('JWT is valid, header is good and you are authorized')
const response = await fetch(event.request)
return response
}
catch (err) {
console.error(err)
const currentTime = Date.now()
const logKey = `${currentTime}|${event.request.url}`
const logValue = {
url: event.request.url,
time: currentTime,
error: JSON.stringify(err.stack)
}
console.log(JSON.stringify(logValue))
WORKER_LOG.put(logKey, JSON.stringify(logValue))
return new Response(err.stack || err)
}
}
function isHeaderValid(request) {
const eventId = request.headers.get('EventId');
if (!eventId) {
return null
}
return eventId
}
/**
* Parse the JWT, validate it and return the user's info
*/
async function getJwtInfo(request) {
const encodedToken = getJwt(request)
if (encodedToken === null) {
return null
}
const token = decodeJwt(encodedToken)
if (token === null) {
return null
}
// Is the token expired?
const currentDate = new Date(Date.now())
const expiryDate = new Date(token.payload.exp * 1000)
if (expiryDate <= currentDate) {
console.error('expired token')
return null
}
// Valid signature?
const isValidSignature = await isValidJwtSignature(token)
if (!isValidSignature) {
console.error('invalid JWT signature')
return null
}
return token
}
/**
* 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()
}
/**
* Do a basic authorization check. In the meantime I'll also hard
* code it to demand Meet Director
*/
async function isAuthorized (eventId, userId) {
console.log(userId)
const unparsedAuth = await AUTHENTICATION_CACHE.get(userId)
const auth = JSON.parse(unparsedAuth)
console.log(auth)
console.log(auth.EventPermissions[1])
let role = null
for (let index = 0; index < auth.EventPermissions.length; index++) {
const element = auth.EventPermissions[index];
if (element.EventId == eventId) {
role = element.Role
break
}
}
return (role === 'Meet Director')
}
/**
* 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) {
try {
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: header,
payload: payload,
signature: signature,
raw: { header: parts[0], payload: parts[1], signature: parts[2] }
}
}
catch (err) {
// If I manually enter junk characters in the JWT it can
// result in the atob failing. Without the try catch the
// atob exception could be returned as the response body
return null
}
}
/**
* 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 = {
// TODO - enter your details here. See https://liftcodeplay.com/2018/10/01/validating-auth0-jwts-on-the-edge-with-a-cloudflare-worker/
}
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)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment