diff --git a/lib/routes/token.js b/lib/routes/token.js index 0313398..2b8015e 100644 --- a/lib/routes/token.js +++ b/lib/routes/token.js @@ -448,14 +448,15 @@ function _validateJwtSub(sub) { return sub; } -function generateIdToken(options) { +function generateIdToken(options, access) { var now = Math.floor(Date.now() / 1000); var claims = { sub: hex(options.userId), aud: hex(options.clientId), iss: ID_TOKEN_ISSUER, iat: now, - exp: now + ID_TOKEN_EXPIRATION + exp: now + ID_TOKEN_EXPIRATION, + at_hash: util.generateTokenHash(access.token) }; if (options.amr) { claims.amr = options.amr; @@ -467,42 +468,43 @@ function generateIdToken(options) { return ID_TOKEN_KEY.sign(claims); } -function generateTokens(options) { +function generateTokens (options) { // we always are generating an access token here // but depending on options, we may also be generating a refresh_token - var promises = { - access: db.generateAccessToken(options) - }; - if (options.offline) { - promises.refresh = db.generateRefreshToken(options); - } - if (options.idToken) { - promises.idToken = generateIdToken(options); - } - return P.props(promises).then(function(result) { - var access = result.access; - var refresh = result.refresh; - var idToken = result.idToken; + return db.generateAccessToken(options) + .then((access) => { + const promises = {}; + if (options.offline) { + promises.refresh = db.generateRefreshToken(options); + } + if (options.idToken) { + promises.idToken = generateIdToken(options, access); + } - var json = { - access_token: access.token.toString('hex'), - token_type: access.type, - scope: access.scope.toString() - }; - if (options.authAt) { - json.auth_at = options.authAt; - } - json.expires_in = options.ttl; - if (refresh) { - json.refresh_token = refresh.token.toString('hex'); - } - if (idToken) { - json.id_token = idToken; - } - if (options.keysJwe) { - json.keys_jwe = options.keysJwe; - } - return json; - }); + return P.props(promises).then(function (result) { + const refresh = result.refresh; + const idToken = result.idToken; + + const json = { + access_token: access.token.toString('hex'), + token_type: access.type, + scope: access.scope.toString() + }; + if (options.authAt) { + json.auth_at = options.authAt; + } + json.expires_in = options.ttl; + if (refresh) { + json.refresh_token = refresh.token.toString('hex'); + } + if (idToken) { + json.id_token = idToken; + } + if (options.keysJwe) { + json.keys_jwe = options.keysJwe; + } + return json; + }); + }); } diff --git a/lib/util.js b/lib/util.js index e148fac..6fbfe6e 100644 --- a/lib/util.js +++ b/lib/util.js @@ -2,6 +2,8 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +const encrypt = require('./encrypt'); + /** * .base64URLEncode * @@ -22,6 +24,23 @@ const base64URLEncode = function base64URLEncode(buf) { .replace(/=/g, ''); }; -module.exports = { - base64URLEncode: base64URLEncode +/** + * Generates a hash of the access token based on + * http://openid.net/specs/openid-connect-core-1_0.html#CodeIDToken + * + * This value is the hash of the ascii value of the access token, then the base64url + * value of the left half. + * + * @param {Buffer} accessTokenBuf + * @returns {String} + * @api public + */ +const generateTokenHash = function generateTokenHash (accessTokenBuf) { + const hash = encrypt.hash(accessTokenBuf.toString('ascii')); + return base64URLEncode(hash.slice(0, hash.length / 2)); +}; + +module.exports = { + base64URLEncode: base64URLEncode, + generateTokenHash: generateTokenHash }; diff --git a/test/api.js b/test/api.js index 56a1d1b..58147bc 100644 --- a/test/api.js +++ b/test/api.js @@ -17,6 +17,7 @@ const encrypt = require('../lib/encrypt'); const P = require('../lib/promise'); const Server = require('./lib/server'); const unique = require('../lib/unique'); +const util = require('../lib/util'); const assertSecurityHeaders = require('./lib/util').assertSecurityHeaders; @@ -1952,6 +1953,9 @@ describe('/v1', function() { assert.deepEqual(claims.amr, AMR); assert.equal(claims.acr, ACR); assert.equal(claims['fxa-aal'], AAL); + + const at_hash = util.generateTokenHash(Buffer.from(res.result.access_token, 'hex')); + assert.equal(claims.at_hash, at_hash); }); });