Created
October 10, 2019 06:11
-
-
Save joawan/f1cefbb8ee5b8fc90503ce32f21181a8 to your computer and use it in GitHub Desktop.
Token introspection middleware with cache
This file contains 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
const tokenIntrospection = require('token-introspection'); | |
const crypto = require('crypto'); | |
const createError = require('http-errors'); | |
const throwIfTrue = require('throw-if-true'); | |
const Cache = require('../lib/cache'); | |
const wrap = require('../lib/routeWrapper'); | |
const logger = require('../lib/logger'); | |
const cache = new Cache(); | |
function cacheKey(token) { | |
const hash = crypto.createHash('sha256'); | |
hash.update(token); | |
const digest = hash.digest('base64'); | |
return `token:${digest}`; | |
} | |
function expressTokenIntrospection(opts = {}) { | |
if (!opts.enabled) { | |
return (req, res, next) => next(); | |
} | |
const maxTTL = opts.maxTTL || 2 * 60 * 60; | |
const introspect = tokenIntrospection(opts); | |
return wrap(async (req, res, next) => { | |
throwIfTrue(!req.token, createError.BadRequest, 'Token missing from request'); | |
const key = cacheKey(req.token); | |
let updateCache = false; | |
let token = await cache.get(key); | |
if (!token) { | |
try { | |
token = await introspect(req.token, 'access_token'); | |
updateCache = true; | |
logger.debug(`Got valid token introspection response: ${JSON.stringify(token)}`); | |
} catch (err) { | |
if (err instanceof tokenIntrospection.errors.TokenNotActiveError) { | |
throw new createError.Unauthorized(err.message); | |
} | |
logger.error(`Token introspection failed: ${err.message}`); | |
throw new createError.InternalServerError('An unknown error occurred when introspecting token'); | |
} | |
} else { | |
logger.debug(`Found token in cache: ${JSON.stringify(token)}`); | |
} | |
req.token = token; | |
next(); // Intentionally continue execution before caching | |
if (updateCache && token.exp) { | |
const expiresIn = Math.ceil(token.exp - (Date.now() / 1000)); | |
const ttl = Math.min(maxTTL, expiresIn); | |
if (ttl <= 0) { | |
return; | |
} | |
logger.debug(`Updating token '${token.jti}' in cache for client ${token.client_id}, expires in ${ttl}s`); | |
try { | |
cache.set(key, token, ttl); | |
} catch (err) { | |
logger.error(`Failed to cache token '${token.jti}' for client ${token.client_id}, and ttl ${ttl}s`); | |
} | |
} | |
}); | |
} | |
module.exports = expressTokenIntrospection; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment