Skip to content

Commit

Permalink
feature(pkce): save code challenge with authorization code
Browse files Browse the repository at this point in the history
Merge pull request #161 from martinssonj/save-codechallenge-for-pkce
  • Loading branch information
jankapunkt authored Oct 31, 2022
2 parents 6bafe0e + c597a24 commit 19f7dc4
Show file tree
Hide file tree
Showing 4 changed files with 87 additions and 4 deletions.
4 changes: 3 additions & 1 deletion index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -306,7 +306,7 @@ declare namespace OAuth2Server {
*
*/
saveAuthorizationCode(
code: Pick<AuthorizationCode, 'authorizationCode' | 'expiresAt' | 'redirectUri' | 'scope'>,
code: Pick<AuthorizationCode, 'authorizationCode' | 'expiresAt' | 'redirectUri' | 'scope' | 'codeChallenge' | 'codeChallengeMethod'>,
client: Client,
user: User,
callback?: Callback<AuthorizationCode>): Promise<AuthorizationCode | Falsey>;
Expand Down Expand Up @@ -410,6 +410,8 @@ declare namespace OAuth2Server {
scope?: string | string[] | undefined;
client: Client;
user: User;
codeChallenge?: string;
codeChallengeMethod?: string;
[key: string]: any;
}

Expand Down
27 changes: 24 additions & 3 deletions lib/handlers/authorize-handler.js
Original file line number Diff line number Diff line change
Expand Up @@ -114,8 +114,10 @@ AuthorizeHandler.prototype.handle = function(request, response) {
})
.then(function(authorizationCode) {
ResponseType = this.getResponseType(request);
const codeChallenge = this.getCodeChallenge(request);
const codeChallengeMethod = this.getCodeChallengeMethod(request);

return this.saveAuthorizationCode(authorizationCode, expiresAt, scope, client, uri, user);
return this.saveAuthorizationCode(authorizationCode, expiresAt, scope, client, uri, user, codeChallenge, codeChallengeMethod);
})
.then(function(code) {
const responseType = new ResponseType(code.authorizationCode);
Expand Down Expand Up @@ -293,13 +295,20 @@ AuthorizeHandler.prototype.getRedirectUri = function(request, client) {
* Save authorization code.
*/

AuthorizeHandler.prototype.saveAuthorizationCode = function(authorizationCode, expiresAt, scope, client, redirectUri, user) {
const code = {
AuthorizeHandler.prototype.saveAuthorizationCode = function(authorizationCode, expiresAt, scope, client, redirectUri, user, codeChallenge, codeChallengeMethod) {
let code = {
authorizationCode: authorizationCode,
expiresAt: expiresAt,
redirectUri: redirectUri,
scope: scope
};

if(codeChallenge && codeChallengeMethod){
code = Object.assign({
codeChallenge: codeChallenge,
codeChallengeMethod: codeChallengeMethod
}, code);
}
return promisify(this.model.saveAuthorizationCode, 3).call(this.model, code, client, user);
};

Expand Down Expand Up @@ -369,6 +378,18 @@ AuthorizeHandler.prototype.updateResponse = function(response, redirectUri, stat
response.redirect(url.format(redirectUri));
};

AuthorizeHandler.prototype.getCodeChallenge = function(request) {
return request.body.code_challenge;
};

/**
* Get code challenge method from request or defaults to plain.
* https://www.rfc-editor.org/rfc/rfc7636#section-4.3
*/
AuthorizeHandler.prototype.getCodeChallengeMethod = function(request) {
return request.body.code_challenge_method || 'plain';
};

/**
* Export constructor.
*/
Expand Down
40 changes: 40 additions & 0 deletions test/integration/handlers/authorize-handler_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -1321,4 +1321,44 @@ describe('AuthorizeHandler integration', function() {
response.get('location').should.equal('http://example.com/cb?state=foobar');
});
});

describe('getCodeChallengeMethod()', function() {
it('should get code challenge method', function() {
const model = {
getAccessToken: function() {},
getClient: function() {},
saveAuthorizationCode: function() {}
};
const handler = new AuthorizeHandler({ authorizationCodeLifetime: 120, model: model }); const request = new Request({ body: {code_challenge_method: 'S256'}, headers: {}, method: {}, query: {} });

const codeChallengeMethod = handler.getCodeChallengeMethod(request);
codeChallengeMethod.should.equal('S256');
});

it('should get default code challenge method plain if missing', function() {
const model = {
getAccessToken: function() {},
getClient: function() {},
saveAuthorizationCode: function() {}
};
const handler = new AuthorizeHandler({ authorizationCodeLifetime: 120, model: model }); const request = new Request({ body: {}, headers: {}, method: {}, query: {} });

const codeChallengeMethod = handler.getCodeChallengeMethod(request);
codeChallengeMethod.should.equal('plain');
});
});

describe('getCodeChallenge()', function() {
it('should get code challenge', function() {
const model = {
getAccessToken: function() {},
getClient: function() {},
saveAuthorizationCode: function() {}
};
const handler = new AuthorizeHandler({ authorizationCodeLifetime: 120, model: model }); const request = new Request({ body: {code_challenge: 'challenge'}, headers: {}, method: {}, query: {} });

const codeChallengeMethod = handler.getCodeChallenge(request);
codeChallengeMethod.should.equal('challenge');
});
});
});
20 changes: 20 additions & 0 deletions test/unit/handlers/authorize-handler_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,26 @@ describe('AuthorizeHandler', function() {
})
.catch(should.fail);
});

it('should call `model.saveAuthorizationCode()` with code challenge', function() {
const model = {
getAccessToken: function() {},
getClient: function() {},
saveAuthorizationCode: sinon.stub().returns({})
};
const handler = new AuthorizeHandler({ authorizationCodeLifetime: 120, model: model });

return handler.saveAuthorizationCode('foo', 'bar', 'qux', 'biz', 'baz', 'boz', 'codeChallenge', 'codeChallengeMethod')
.then(function() {
model.saveAuthorizationCode.callCount.should.equal(1);
model.saveAuthorizationCode.firstCall.args.should.have.length(3);
model.saveAuthorizationCode.firstCall.args[0].should.eql({ authorizationCode: 'foo', expiresAt: 'bar', redirectUri: 'baz', scope: 'qux', codeChallenge: 'codeChallenge', codeChallengeMethod: 'codeChallengeMethod' });
model.saveAuthorizationCode.firstCall.args[1].should.equal('biz');
model.saveAuthorizationCode.firstCall.args[2].should.equal('boz');
model.saveAuthorizationCode.firstCall.thisValue.should.equal(model);
})
.catch(should.fail);
});
});

describe('validateRedirectUri()', function() {
Expand Down

0 comments on commit 19f7dc4

Please sign in to comment.