Skip to content

Instantly share code, notes, and snippets.

@theBoEffect
Created March 9, 2022 20:33
Show Gist options
  • Save theBoEffect/c4d98f3d8503089afea90c405858965d to your computer and use it in GitHub Desktop.
Save theBoEffect/c4d98f3d8503089afea90c405858965d to your computer and use it in GitHub Desktop.
An example of validating an United Effects Core OIDC token with node and passport
import jwt from 'jsonwebtoken';
import qs from 'querystring';
import jkwsClient from 'jwks-rsa';
import axios from 'axios';
import Boom from '@hapi/boom';
import passport from "passport";
import {Strategy as BearerStrategy} from "passport-http-bearer";
// CORE is the domain + authGroup of the Core EOS OIDC connection (aka issuer)
const CORE = process.env.CORE_EOS;
// Your auth group ID
const AUTH_GROUP = process.env.CORE_AUTH_GROUP;
// OIDC Client ID for authorizations
const CLIENT_ID = process.env.CORE_CLIENT_ID;
// OIDC Client Secret
const CLIENT_SECRET = process.env.CORE_CLIENT_SECRET;
// Your API DNS address
const AUDIENCE = process.env.SWAGGER
// Helper function
const jwtCheck = /^([A-Za-z0-9\-_~+\/]+[=]{0,2})\.([A-Za-z0-9\-_~+\/]+[=]{0,2})(?:\.([A-Za-z0-9\-_~+\/]+[=]{0,2}))?$/;
function isJWT(str) {
return jwtCheck.test(str);
}
async function getUser(token) {
const url = `${CORE}/me`;
const options = {
url,
method: 'get',
headers: {
authorization: `bearer ${token}`
}
}
const result = await axios(options);
return result.data;
}
async function introspect(token) {
const introspection = `${CORE}/token/introspection`;
const options = {
url: introspection,
method: 'post',
auth: {
username: CLIENT_ID,
password: CLIENT_SECRET
},
data: qs.stringify({
token,
'token-hint': 'access_token'
})
}
const result = await axios(options);
return result.data;
}
async function runDecodedChecks(token, issuer, decoded, authGroup) {
if(decoded.nonce) {
console.info('This is an ID Token');
throw Boom.unauthorized('ID Tokens can not be used for API Access');
}
if(decoded.iss !== issuer) {
console.info('Issuer is wrong');
throw Boom.unauthorized('Token issuer not recognized');
}
if(!decoded.group) {
console.info('Auth Group issue');
throw Boom.unauthorized('No Auth Group detected in token');
}
if(decoded.group !== authGroup) {
console.info('AG mismatch');
throw Boom.unauthorized('Auth Group does not match');
}
if(typeof decoded.aud === 'string') {
if(decoded.aud !== AUDIENCE) {
console.info('Audience Issue');
throw Boom.unauthorized('Token audience is not valid');
}
}
if(Array.isArray(decoded.aud)) {
if(!decoded.aud.includes(AUDIENCE)) {
console.info('Audience Issue');
throw Boom.unauthorized('Token audience is not valid');
}
}
if (decoded.client_id) {
if(decoded.client_id !== CLIENT_ID) {
console.info('wrong client');
throw Boom.unauthorized('Unexpected Client ID');
}
}
//check sub if present
if(decoded.sub && decoded.client_id !== decoded.sub) {
let user;
if(decoded.email) {
user = {
sub: decoded.sub,
email: decoded.email
}
} else {
user = await getUser(token);
}
if(!user) throw Boom.unauthorized('Token should include email');
return { ...user, decoded };
}
// client_credential - note, permissions may still stop the request
if((decoded.client_id === decoded.sub) || (!decoded.sub && decoded.client_id)) {
const out = {
client_credential: true,
sub: decoded.client_id,
client_id: decoded.client_id
};
return { ...out, decoded };
}
return decoded;
}
async function oidcValidate(req, token, next) {
try {
const authGroup = AUTH_GROUP;
const issuer = CORE;
if(isJWT(token)){
const client = jkwsClient({
jwksUri: `${CORE}/jwks`
})
function getKey(header, cb) {
client.getSigningKey(header.kid, (err, key) => {
if(err) cb(err);
const signingKey = key.getPublicKey || key.rsaPublicKey;
cb(null, signingKey);
})
}
const decoded = await jwt.verify(token, getKey);
if(decoded) {
const result = await runDecodedChecks(token, issuer, decoded, authGroup);
return next(null, result, { token });
}
}
//opaque token
const inspect = await introspect(token);
if(inspect) {
if (inspect.active === false) return next(null, false);
const result = await runDecodedChecks(token, issuer, inspect, authGroup);
return next(null, result, { token });
}
return next(null, false);
} catch (error) {
console.error(error);
return next(null, false);
}
}
passport.serializeUser((user, done) => {
done(null, user._id);
});
passport.deserializeUser((user, done) => {
done(null, user);
});
passport.use('oidc', new BearerStrategy({
passReqToCallback: true
}, oidcValidate));
export default {
isAuthenticated: passport.authenticate(['oidc'], { session: false }),
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment