Skip to content

Instantly share code, notes, and snippets.

@tomasaschan
Last active June 20, 2018 16:10
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save tomasaschan/fdaf47dc31e03de43a1a07fbbea2ab91 to your computer and use it in GitHub Desktop.
Save tomasaschan/fdaf47dc31e03de43a1a07fbbea2ab91 to your computer and use it in GitHub Desktop.
require('top-level-await')
require('./refresh-auth-token')
{
"name": "refresh-token-repro",
"version": "1.0.0",
"main": "index.js",
"author": "tlycken <tlycken@users.noreply.github.com>",
"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"
}
}
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)
@tomasaschan
Copy link
Author

To start, run

yarn install
nodemon

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment