adal-express
Version:
Active Directory Authentication Library (ADAL) for Express
141 lines (121 loc) • 4.92 kB
JavaScript
//==============================================================================
// Provides the get and renew access token functions. The options parameter
// must have the following four properties: azureTenantId azureClientId,
// azureClientSecret, and azureResourceUri.
//==============================================================================
// Copyright (c) 2016 Frank Hellwig
//==============================================================================
;
//------------------------------------------------------------------------------
// Dependencies
//------------------------------------------------------------------------------
const HttpsService = require('https-service');
const HttpsError = require('https-error');
//------------------------------------------------------------------------------
// Initialization
//------------------------------------------------------------------------------
const FIVE_MINUTES = 5 * 60;
const _aadService = new HttpsService('login.microsoftonline.com');
let _options = null;
//------------------------------------------------------------------------------
// Exports
//------------------------------------------------------------------------------
module.exports = function(app, options) {
_options = options;
app.use(options.localApiPath, _appendBearerToken);
};
module.exports.getToken = getToken;
module.exports.renewToken = renewToken;
//------------------------------------------------------------------------------
// Public
//------------------------------------------------------------------------------
function getToken(options, authorizationCode, replyUri, callback) {
let body = {
client_id: options.azureClientId,
client_secret: options.azureClientSecret,
code: authorizationCode,
redirect_uri: replyUri,
grant_type: 'authorization_code',
resource: options.azureResourceUri
};
_aadPost(options.azureTenantId, body, (err, response) => {
if (err) return callback(err);
callback(null, _processResponse(response));
});
}
function renewToken(options, refreshToken, callback) {
let body = {
client_id: options.azureClientId,
client_secret: options.azureClientSecret,
grant_type: 'refresh_token',
refresh_token: refreshToken,
resource: options.azureResourceUri
};
_aadPost(options.azureTenantId, body, (err, response) => {
if (err) return callback(err);
callback(null, _processResponse(response));
});
}
//------------------------------------------------------------------------------
// Private
//------------------------------------------------------------------------------
function _appendBearerToken(req, res, next) {
const session = req.session;
_ensureValidToken(session, err => {
if (err) return next(err);
if (typeof session.accessToken !== 'string') {
return next(HttpsError.unauthorized('No accessToken property in session.'));
}
req.headers.authorization = 'Bearer ' + session.accessToken;
if (!req.ext) {
req.ext = {};
}
req.ext.isAuthenticated = true;
next();
});
}
function _ensureValidToken(session, callback) {
if (typeof session.expiresAt !== 'number') {
return callback(HttpsError.unauthorized('No expiresAt property in session.'));
}
if (Date.now() < session.expiresAt) {
return callback(null); // the access token has not expired
}
renewToken(_options, session.refreshToken, (err, response) => {
if (err) return callback(err);
Object.assign(session, response);
session.touch();
callback(null);
});
}
function _processResponse(response) {
const userClaims = _decodeClaims(response.access_token);
const expiresIn = parseInt(response.expires_in);
const expiresAt = Date.now() + (expiresIn - FIVE_MINUTES) * 1000;
return {
accessToken: response.access_token,
refreshToken: response.refresh_token,
userClaims: _decodeClaims(response.access_token),
expiresAt: expiresAt
};
}
function _decodeClaims(accessToken) {
if (!accessToken) return null;
let payload = accessToken.split('.')[1];
let claims = JSON.parse(new Buffer(payload, 'base64').toString('ascii'));
let retval = {
userId: claims.oid,
principalName: claims.upn || claims.unique_name,
firstName: claims.given_name || '(no first name)',
lastName: claims.family_name || '(no last name)',
displayName: claims.name || '(no display name)'
};
return retval;
}
function _aadPost(tenantId, body, callback) {
let path = `/${tenantId}/oauth2/token`;
let headers = {
'Content-Type': 'application/x-www-form-urlencoded',
};
_aadService.request('POST', path, headers, body, callback);
}