diff --git a/lib/state/session.js b/lib/state/session.js index 0ac3d22..35b80e5 100644 --- a/lib/state/session.js +++ b/lib/state/session.js @@ -45,6 +45,7 @@ SessionStore.prototype.store = function(req, ctx, appState, meta, cb) { if (ctx.maxAge) { state.maxAge = ctx.maxAge; } if (ctx.nonce) { state.nonce = ctx.nonce; } if (ctx.issued) { state.issued = ctx.issued; } + if (ctx.pkceCodeVerifier) { state.pkceCodeVerifier = ctx.pkceCodeVerifier; } if (appState) { state.state = appState; } if (!req.session[key]) { req.session[key] = {}; } @@ -89,7 +90,8 @@ SessionStore.prototype.verify = function(req, handle, cb) { var ctx = { maxAge: state.maxAge, nonce: state.nonce, - issued: state.issued + issued: state.issued, + pkceCodeVerifier: state.pkceCodeVerifier }; if (typeof ctx.issued === 'string') { // convert issued to a Date object diff --git a/lib/strategy.js b/lib/strategy.js index ad8bf99..54934db 100644 --- a/lib/strategy.js +++ b/lib/strategy.js @@ -5,6 +5,8 @@ var passport = require('passport-strategy') , url = require('url') , util = require('util') , utils = require('./utils') + , base64url = require('base64url') + , crypto = require('crypto') , OAuth2 = require('oauth').OAuth2 , Profile = require('./profile') , Context = require('./context') @@ -62,6 +64,8 @@ function Strategy(options, verify) { this._idTokenHint = options.idTokenHint; this._nonce = options.nonce; this._claims = options.claims; + this._pkce = options.pkce; + this._pkceMethod = (options.pkce === true) ? 'S256' : options.pkce; var key = options.sessionKey || (this.name + ':' + url.parse(options.authorizationURL).hostname); this._stateStore = options.store || new SessionStateStore({ key: key }); @@ -129,6 +133,9 @@ Strategy.prototype.authenticate = function(req, options) { var params = { grant_type: 'authorization_code' }; if (callbackURL) { params.redirect_uri = callbackURL; } + if (ctx.pkceCodeVerifier) { + params.code_verifier = ctx.pkceCodeVerifier; + } self._oauth2.getOAuthAccessToken(code, params, function(err, accessToken, refreshToken, params) { if (err) { @@ -333,8 +340,29 @@ Strategy.prototype.authenticate = function(req, options) { if (claims) { params.claims = JSON.stringify(claims); } - + var ctx = {}; + + var verifier, challenge; + + if (this._pkceMethod) { + verifier = base64url(crypto.pseudoRandomBytes(32)) + switch (this._pkceMethod) { + case 'plain': + challenge = verifier; + break; + case 'S256': + challenge = base64url(crypto.createHash('sha256').update(verifier).digest()); + break; + default: + return this.error(new Error('Unsupported code verifier transformation method: ' + this._pkceMethod)); + } + + params.code_challenge = challenge; + params.code_challenge_method = this._pkceMethod; + ctx.pkceCodeVerifier = verifier; + } + if (params.max_age) { ctx.maxAge = params.max_age; ctx.issued = new Date(); diff --git a/package.json b/package.json index a3cf841..394b8c1 100644 --- a/package.json +++ b/package.json @@ -37,7 +37,8 @@ "main": "./lib", "dependencies": { "oauth": "0.10.x", - "passport-strategy": "1.x.x" + "passport-strategy": "1.x.x", + "base64url": "3.x.x" }, "devDependencies": { "chai": "2.x.x",