Last active
June 20, 2018 16:10
-
-
Save tomasaschan/fdaf47dc31e03de43a1a07fbbea2ab91 to your computer and use it in GitHub Desktop.
This file contains hidden or 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
require('top-level-await') | |
require('./refresh-auth-token') |
This file contains hidden or 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
{ | |
"name": "refresh-token-repro", | |
"version": "1.0.0", | |
"main": "index.js", | |
"author": "tlycken <[email protected]>", | |
"license": "MIT", | |
"dependencies": { | |
"fetch-with-proxy": "^1.1.0", | |
"form-data": "^2.3.2", | |
"isomorphic-fetch": "^2.2.1", | |
"jsonwebtoken": "^8.3.0", | |
"jwks-rsa": "^1.2.1", | |
"koa": "^2.5.1", | |
"koa-passport": "^4.1.0", | |
"koa-router": "^7.4.0", | |
"koa-session": "^5.8.1", | |
"nodemon": "^1.17.5", | |
"passport-azure-ad-oauth2": "^0.0.4", | |
"top-level-await": "^1.1.0" | |
} | |
} |
This file contains hidden or 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 jwksClient = require('jwks-rsa') | |
const jwt = require('jsonwebtoken') | |
const fetch = require('fetch-with-proxy').default | |
const FormData = require('form-data') | |
const Koa = require('koa') | |
const Router = require('koa-router') | |
const passport = require('koa-passport') | |
const session = require('koa-session') | |
const AzureAdOauth2Strategy = require('passport-azure-ad-oauth2').Strategy | |
const AZURE_CLIENT_ID = '899ceec4-0192-441c-a215-c9d9cf51b059' | |
const AZURE_CLIENT_SECRET = 'mLPMQoXh55iHW8ctcM/R54JjJzTu6AYRamoATKeRJ7M=' | |
const AZURE_RESOURCE = '202ecd60-bb38-40e1-9dfb-c3ab76b0cd88' | |
const AZURE_TENANT = '7ccf77fc-472c-49f4-af56-f46cd8618318' | |
const callbackURL = 'http://localhost:8181/auth/callback' | |
const configResponse = await fetch(`https://login.microsoftonline.com/${AZURE_TENANT}/v2.0/.well-known/openid-configuration`) | |
const { authorization_endpoint, token_endpoint, jwks_uri } = await configResponse.json() | |
const client = new jwksClient({ jwksUri: jwks_uri }) | |
const getKey = (header, callback) => { | |
client.getSigningKey(header.kid, (err, key) => { | |
if (err) { | |
callback(err) | |
} else { | |
var signingKey = key.publicKey || key.rsaPublicKey | |
callback(null, signingKey) | |
} | |
}) | |
} | |
passport.use( | |
new AzureAdOauth2Strategy({ | |
clientID: AZURE_CLIENT_ID, | |
clientSecret: AZURE_CLIENT_SECRET, | |
callbackURL, | |
resource: AZURE_RESOURCE, | |
tenant: AZURE_TENANT, | |
prompt: 'login' | |
}, | |
async (accessToken, refreshToken, params, profile, done) => accessToken ? | |
done(null, { accessToken, refreshToken, params, profile }) : | |
done(null, false) | |
) | |
) | |
const app = new Koa() | |
const router = new Router() | |
app.keys = ['foo'] | |
app.use(session(app)) | |
router.get('/auth', '/auth', passport.authenticate('azure_ad_oauth2')) | |
router.get('/auth/callback', (ctx, next) => | |
passport.authenticate( | |
'azure_ad_oauth2', { failureRedirect: '/auth' }, | |
(err, { accessToken, refreshToken, params, profile }) => { | |
if (err) { | |
console.error('Error in auth callback', err) | |
return ctx.throw(500, err) | |
} | |
ctx.session.accessToken = accessToken | |
ctx.session.refreshToken = refreshToken | |
ctx.session.tokenScope = params.scope | |
return ctx.redirect('/') | |
} | |
)(ctx, next) | |
) | |
app | |
.use(router.routes()) | |
.use(router.allowedMethods()) | |
const verifyToken = token => new Promise((resolve, reject) => { | |
jwt.verify(token, getKey, { maxAge: "1h" }, (err, decoded) => { | |
if (err) { | |
console.log('token verification failed:', err.message) | |
reject(err) | |
} else { | |
resolve(decoded) | |
} | |
}) | |
}) | |
const renewToken = async (refreshToken, tokenScope) => { | |
var data = new FormData() | |
console.log('refreshing') | |
data.append('grant_type', 'refresh_token') | |
data.append('refresh_token', refreshToken) | |
data.append('client_id', AZURE_CLIENT_ID) | |
data.append('client_secret', AZURE_CLIENT_SECRET) | |
data.append('scope', tokenScope) | |
data.append('redirect_uri', callbackURL) | |
var refresh = await fetch( | |
token_endpoint, { | |
method: 'POST', | |
body: data | |
}) | |
const body = await refresh.json() | |
const { refresh_token, access_token, scope } = body | |
return { accessToken: access_token, refreshToken: refresh_token, tokenScope: scope } | |
} | |
app.use(async (ctx, next) => { | |
if (!ctx.session.accessToken || !ctx.session.refreshToken || !ctx.session.tokenScope) { | |
console.log('redirecting') | |
ctx.redirect('/auth') | |
} else { | |
console.log('verifying old token') | |
const decoded = await verifyToken(ctx.session.accessToken) | |
console.log('decoded user id', decoded.oid) | |
return next() | |
} | |
}) | |
app.use(async (ctx, next) => { | |
const { accessToken, refreshToken, tokenScope } = await renewToken(ctx.session.refreshToken, ctx.session.tokenScope) | |
console.log('fetching /me with renewed token') | |
const response = await fetch('https://graph.microsoft.com/v1.0/me', { | |
headers: { Authorization: `Bearer ${accessToken}` } | |
}) | |
const me = await response.json() | |
console.log('got user id', me.id) | |
console.log('verifying new token') | |
await verifyToken(accessToken) | |
return next() | |
}) | |
app.use((ctx, next) => { | |
ctx.body = 'ok' | |
}) | |
app.listen(8181) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
To start, run