Skip to content

Instantly share code, notes, and snippets.

@svvac
Last active August 16, 2021 11:07
Show Gist options
  • Save svvac/a83f86912f361bb6e077dfcb913ac1cb to your computer and use it in GitHub Desktop.
Save svvac/a83f86912f361bb6e077dfcb913ac1cb to your computer and use it in GitHub Desktop.
openid-client test app
const express = require('express');
const openid = require('openid-client');
const helpers = require('./helpers');
const app1 = express();
app1.get('/login', async (req, res) => {
try {
// Store authentication state in a secretbox cookie
const authstate = {
date: new Date(),
ip: req.ip,
ua: req.header('User-Agent'),
nonce: openid.generators.nonce(),
verifier: openid.generators.codeVerifier(),
};
res.cookie('astk', helpers.createSecretToken(authstate), { httpOnly: true });
// Create authorization url
const code_challenge = openid.generators.codeChallenge(authstate.verifier);
const client = await helpers.getClient();
const authurl = client.authorizationUrl({
scope: 'openid email profile',
nonce: authstate.nonce,
code_challenge,
code_challenge_method: 'S256',
});
res.redirect(authurl);
}
catch (err) {
console.error(err);
res.sendStatus(500);
}
});
app1.listen(3000);
const express = require('express');
const helpers = require('./helpers');
const { STATE_VALIDITY } = require('./constants');
const app2 = express();
app2.use(require('cookie-parser')());
app2.get('/callback', async (req, res) => {
try {
let authstate;
try {
if (!req.cookies.astk) return res.redirect('http://localhost:3000/login');
res.clearCookie('astk', { httpOnly: true });
authstate = helpers.readSecretToken(req.cookies.astk);
// Verify state
if (new Date(authstate.date) <= new Date() - STATE_VALIDITY) throw new Error('state expired');
if (authstate.ip !== req.ip) throw new Error('IP mismatch');
if (authstate.ua !== req.header('User-Agent')) throw new Error('User Agent mismatch');
}
catch (err) {
console.error('STATE ERROR', err);
res.sendStatus(400);
return;
}
try {
const client = await helpers.getClient(); // Instanciate a new client
const params = client.callbackParams(req);
const tokset = await client.callback('http://localhost:3001/callback', params, {
nonce: authstate.nonce,
code_verifier: authstate.verifier,
});
res.status(200);
res.send(tokset.claims());
} catch (err) {
console.error('CB ERROR', err);
res.sendStatus(400);
return;
}
}
catch (err) {
console.error(err);
res.sendStatus(500);
}
});
app2.listen(3001);
module.exports = {
// A 32-byte secret key for secretbox
SECRET_KEY: Buffer.from('23566e086a5cbf92e4fcd38b6f3a151b25be69379792c108c6d5feaf3dcd202e', 'hex'),
STATE_VALIDITY: 60 * 10 * 1000, // Auth state is valid 10min
CLIENT_DISCOVERY_URL: 'https://accounts.google.com',
// Options used to instanciate a client
CLIENT_OPTIONS: {
client_id: 'oauth client id',
client_secret: 'oauth client secret',
redirect_uris: [ 'http://localhost:3001/callback' ],
response_types: [ 'code' ],
}
};
const tnacl = require('tweetnacl');
const openid = require('openid-client');
const { SECRET_KEY, CLIENT_DISCOVERY_URL, CLIENT_OPTIONS } = require('./constants');
module.exports = {
createSecretToken,
readSecretToken,
getClient,
}
function createSecretToken (data) {
const nonce = tnacl.randomBytes(tnacl.secretbox.nonceLength);
const payload = Buffer.from(JSON.stringify(data));
const box = tnacl.secretbox(payload, nonce, SECRET_KEY);
const token = Buffer.concat([ nonce, box ]);
return token.toString('base64');
}
function readSecretToken (token) {
const buf = Buffer.from(token, 'base64');
const nonce = buf.slice(0, tnacl.secretbox.nonceLength);
const box = buf.slice(tnacl.secretbox.nonceLength);
const payload = Buffer.from(tnacl.secretbox.open(box, nonce, SECRET_KEY));
const obj = JSON.parse(payload.toString());
return obj;
}
async function getClient () {
const issuer = await openid.Issuer.discover(CLIENT_DISCOVERY_URL);
const client = new issuer.Client(CLIENT_OPTIONS);
return client;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment