Skip to content

Commit 1f712d3

Browse files
committed
Merge branch 'features/pkce-v5-dev' into v5-dev
2 parents a7a6a0a + 26b13f8 commit 1f712d3

File tree

12 files changed

+586
-71
lines changed

12 files changed

+586
-71
lines changed

docs/model/spec.rst

Lines changed: 78 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -336,25 +336,29 @@ This model function is **required** if the ``authorization_code`` grant is used.
336336

337337
An ``Object`` representing the authorization code and associated data.
338338

339-
+--------------------+--------+--------------------------------------------------------------+
340-
| Name | Type | Description |
341-
+====================+========+==============================================================+
342-
| code | Object | The return value. |
343-
+--------------------+--------+--------------------------------------------------------------+
344-
| code.code | String | The authorization code passed to ``getAuthorizationCode()``. |
345-
+--------------------+--------+--------------------------------------------------------------+
346-
| code.expiresAt | Date | The expiry time of the authorization code. |
347-
+--------------------+--------+--------------------------------------------------------------+
348-
| [code.redirectUri] | String | The redirect URI of the authorization code. |
349-
+--------------------+--------+--------------------------------------------------------------+
350-
| [code.scope] | String | The authorized scope of the authorization code. |
351-
+--------------------+--------+--------------------------------------------------------------+
352-
| code.client | Object | The client associated with the authorization code. |
353-
+--------------------+--------+--------------------------------------------------------------+
354-
| code.client.id | String | A unique string identifying the client. |
355-
+--------------------+--------+--------------------------------------------------------------+
356-
| code.user | Object | The user associated with the authorization code. |
357-
+--------------------+--------+--------------------------------------------------------------+
339+
+----------------------------+--------+---------------------------------------------------------------+
340+
| Name | Type | Description |
341+
+============================+========+===============================================================+
342+
| code | Object | The return value. |
343+
+----------------------------+--------+---------------------------------------------------------------+
344+
| code.code | String | The authorization code passed to ``getAuthorizationCode()``. |
345+
+----------------------------+--------+---------------------------------------------------------------+
346+
| code.expiresAt | Date | The expiry time of the authorization code. |
347+
+----------------------------+--------+---------------------------------------------------------------+
348+
| [code.redirectUri] | String | The redirect URI of the authorization code. |
349+
+----------------------------+--------+---------------------------------------------------------------+
350+
| [code.scope] | String | The authorized scope of the authorization code. |
351+
+----------------------------+--------+---------------------------------------------------------------+
352+
| code.client | Object | The client associated with the authorization code. |
353+
+----------------------------+--------+---------------------------------------------------------------+
354+
| code.client.id | String | A unique string identifying the client. |
355+
+----------------------------+--------+---------------------------------------------------------------+
356+
| code.user | Object | The user associated with the authorization code. |
357+
+----------------------------+--------+---------------------------------------------------------------+
358+
| [code.codeChallenge] | String | The code challenge string used with PKCE (RFC7636). |
359+
+----------------------------+--------+---------------------------------------------------------------+
360+
| [code.codeChallengeMethod] | String | The string for the code challenge method (`S256` or `plain`). |
361+
+----------------------------+--------+---------------------------------------------------------------+
358362

359363
``code.client`` and ``code.user`` can carry additional properties that will be ignored by *oauth2-server*.
360364

@@ -379,7 +383,9 @@ An ``Object`` representing the authorization code and associated data.
379383
redirectUri: code.redirect_uri,
380384
scope: code.scope,
381385
client: client, // with 'id' property
382-
user: user
386+
user: user,
387+
codeChallenge: code.code_challenge,
388+
codeChallengeMethod: code.code_challenge_method
383389
};
384390
});
385391
}
@@ -665,51 +671,59 @@ This model function is **required** if the ``authorization_code`` grant is used.
665671

666672
**Arguments:**
667673

668-
+------------------------+----------+---------------------------------------------------------------------+
669-
| Name | Type | Description |
670-
+========================+==========+=====================================================================+
671-
| code | Object | The code to be saved. |
672-
+------------------------+----------+---------------------------------------------------------------------+
673-
| code.authorizationCode | String | The authorization code to be saved. |
674-
+------------------------+----------+---------------------------------------------------------------------+
675-
| code.expiresAt | Date | The expiry time of the authorization code. |
676-
+------------------------+----------+---------------------------------------------------------------------+
677-
| code.redirectUri | String | The redirect URI associated with the authorization code. |
678-
+------------------------+----------+---------------------------------------------------------------------+
679-
| [code.scope] | String | The authorized scope of the authorization code. |
680-
+------------------------+----------+---------------------------------------------------------------------+
681-
| client | Object | The client associated with the authorization code. |
682-
+------------------------+----------+---------------------------------------------------------------------+
683-
| user | Object | The user associated with the authorization code. |
684-
+------------------------+----------+---------------------------------------------------------------------+
685-
| [callback] | Function | Node-style callback to be used instead of the returned ``Promise``. |
686-
+------------------------+----------+---------------------------------------------------------------------+
674+
+----------------------------+----------+---------------------------------------------------------------------+
675+
| Name | Type | Description |
676+
+============================+==========+=====================================================================+
677+
| code | Object | The code to be saved. |
678+
+----------------------------+----------+---------------------------------------------------------------------+
679+
| code.authorizationCode | String | The authorization code to be saved. |
680+
+----------------------------+----------+---------------------------------------------------------------------+
681+
| code.expiresAt | Date | The expiry time of the authorization code. |
682+
+----------------------------+----------+---------------------------------------------------------------------+
683+
| code.redirectUri | String | The redirect URI associated with the authorization code. |
684+
+----------------------------+----------+---------------------------------------------------------------------+
685+
| [code.scope] | String | The authorized scope of the authorization code. |
686+
+----------------------------+----------+---------------------------------------------------------------------+
687+
| [code.codeChallenge] | String | The code challenge string used with PKCE (RFC7636). |
688+
+----------------------------+----------+---------------------------------------------------------------------+
689+
| [code.codeChallengeMethod] | String | The string for the code challenge method (`S256` or `plain`). |
690+
+----------------------------+----------+---------------------------------------------------------------------+
691+
| client | Object | The client associated with the authorization code. |
692+
+----------------------------+----------+---------------------------------------------------------------------+
693+
| user | Object | The user associated with the authorization code. |
694+
+----------------------------+----------+---------------------------------------------------------------------+
695+
| [callback] | Function | Node-style callback to be used instead of the returned ``Promise``. |
696+
+----------------------------+----------+---------------------------------------------------------------------+
687697

688698
.. todo:: Is ``code.scope`` really optional?
689699

690700
**Return value:**
691701

692702
An ``Object`` representing the authorization code and associated data.
693703

694-
+------------------------+--------+---------------------------------------------------------------+
695-
| Name | Type | Description |
696-
+========================+========+===============================================================+
697-
| code | Object | The return value. |
698-
+------------------------+--------+---------------------------------------------------------------+
699-
| code.authorizationCode | String | The authorization code passed to ``saveAuthorizationCode()``. |
700-
+------------------------+--------+---------------------------------------------------------------+
701-
| code.expiresAt | Date | The expiry time of the authorization code. |
702-
+------------------------+--------+---------------------------------------------------------------+
703-
| code.redirectUri | String | The redirect URI associated with the authorization code. |
704-
+------------------------+--------+---------------------------------------------------------------+
705-
| [code.scope] | String | The authorized scope of the authorization code. |
706-
+------------------------+--------+---------------------------------------------------------------+
707-
| code.client | Object | The client associated with the authorization code. |
708-
+------------------------+--------+---------------------------------------------------------------+
709-
| code.client.id | String | A unique string identifying the client. |
710-
+------------------------+--------+---------------------------------------------------------------+
711-
| code.user | Object | The user associated with the authorization code. |
712-
+------------------------+--------+---------------------------------------------------------------+
704+
+----------------------------+--------+----------------------------------------------------------------+
705+
| Name | Type | Description |
706+
+============================+========+================================================================+
707+
| code | Object | The return value. |
708+
+----------------------------+--------+----------------------------------------------------------------+
709+
| code.authorizationCode | String | The authorization code passed to ``saveAuthorizationCode()``. |
710+
+----------------------------+--------+----------------------------------------------------------------+
711+
| code.expiresAt | Date | The expiry time of the authorization code. |
712+
+----------------------------+--------+----------------------------------------------------------------+
713+
| code.redirectUri | String | The redirect URI associated with the authorization code. |
714+
+----------------------------+--------+----------------------------------------------------------------+
715+
| [code.scope] | String | The authorized scope of the authorization code. |
716+
+----------------------------+--------+----------------------------------------------------------------+
717+
| code.client | Object | The client associated with the authorization code. |
718+
+----------------------------+--------+----------------------------------------------------------------+
719+
| code.client.id | String | A unique string identifying the client. |
720+
+----------------------------+--------+----------------------------------------------------------------+
721+
| code.user | Object | The user associated with the authorization code. |
722+
+----------------------------+--------+----------------------------------------------------------------+
723+
| [code.codeChallenge] | String | The code challenge string used with PKCE (RFC7636). |
724+
+----------------------------+--------+----------------------------------------------------------------+
725+
| [code.codeChallengeMethod] | String | The string for the code challenge method (`S256` or `plain` |
726+
+----------------------------+--------+----------------------------------------------------------------+
713727

714728
``code.client`` and ``code.user`` can carry additional properties that will be ignored by *oauth2-server*.
715729

@@ -725,7 +739,9 @@ An ``Object`` representing the authorization code and associated data.
725739
redirect_uri: code.redirectUri,
726740
scope: code.scope,
727741
client_id: client.id,
728-
user_id: user.id
742+
user_id: user.id,
743+
code_challenge: code.codeChallenge,
744+
code_challenge_method: code.codeChallengeMethod
729745
};
730746
return db.saveAuthorizationCode(authCode)
731747
.then(function(authorizationCode) {
@@ -735,7 +751,9 @@ An ``Object`` representing the authorization code and associated data.
735751
redirectUri: authorizationCode.redirect_uri,
736752
scope: authorizationCode.scope,
737753
client: {id: authorizationCode.client_id},
738-
user: {id: authorizationCode.user_id}
754+
user: {id: authorizationCode.user_id},
755+
codeChallenge: authorizationCode.code_challenge,
756+
codeChallengeMethod: authorizationCode.code_challenge_method
739757
};
740758
});
741759
}

lib/grant-types/authorization-code-grant-type.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ import {
88
import { AuthorizationCode, Client, Token, User } from '../interfaces';
99
import { Request } from '../request';
1010
import * as is from '../validator/is';
11+
import * as crypto from 'crypto';
12+
import * as stringUtil from '../utils/string-util';
1113

1214
export class AuthorizationCodeGrantType extends AbstractGrantType {
1315
constructor(options: any = {}) {
@@ -117,6 +119,33 @@ export class AuthorizationCodeGrantType extends AbstractGrantType {
117119
);
118120
}
119121

122+
if (code.codeChallenge) {
123+
if (!request.body.code_verifier) {
124+
throw new InvalidGrantError('Missing parameter: `code_verifier`');
125+
}
126+
127+
let hash;
128+
switch (code.codeChallengeMethod) {
129+
case 'plain':
130+
hash = request.body.code_verifier;
131+
break;
132+
case 'S256':
133+
hash = stringUtil.base64URLEncode(crypto.createHash('sha256').update(request.body.code_verifier).digest());
134+
break;
135+
default:
136+
throw new ServerError('Server error: `getAuthorizationCode()` did not return a valid `codeChallengeMethod` property');
137+
}
138+
139+
if (code.codeChallenge !== hash) {
140+
throw new InvalidGrantError('Invalid grant: code verifier is invalid');
141+
}
142+
} else {
143+
if (request.body.code_verifier) {
144+
// No code challenge but code_verifier was passed in.
145+
throw new InvalidGrantError('Invalid grant: code verifier is invalid');
146+
}
147+
}
148+
120149
return code;
121150
}
122151

lib/handlers/token-handler.ts

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,11 @@ import { BearerTokenType } from '../token-types';
2222
import { hasOwnProperty } from '../utils/fn';
2323
import * as is from '../validator/is';
2424

25+
interface ClientCredentials {
26+
clientId: string;
27+
clientSecret?: string;
28+
}
29+
2530
/**
2631
* Grant types.
2732
*/
@@ -127,17 +132,19 @@ export class TokenHandler {
127132
* Get the client from the model.
128133
*/
129134

130-
async getClient(request, response) {
131-
const credentials = this.getClientCredentials(request);
135+
async getClient(request: Request, response: Response) {
136+
const credentials: ClientCredentials = this.getClientCredentials(request);
132137
const grantType = request.body.grant_type;
138+
const isPkce = this.isPKCERequest(request, grantType);
133139

134140
if (!credentials.clientId) {
135141
throw new InvalidRequestError('Missing parameter: `client_id`');
136142
}
137143

138144
if (
139145
this.isClientAuthenticationRequired(grantType) &&
140-
!credentials.clientSecret
146+
!credentials.clientSecret &&
147+
!isPkce
141148
) {
142149
throw new InvalidRequestError('Missing parameter: `client_secret`');
143150
}
@@ -209,6 +216,12 @@ export class TokenHandler {
209216
};
210217
}
211218

219+
if (this.isPKCERequest(request, grantType)) {
220+
if (request.body.client_id) {
221+
return { clientId: request.body.client_id };
222+
}
223+
}
224+
212225
if (
213226
!this.isClientAuthenticationRequired(grantType) &&
214227
request.body.client_id
@@ -328,4 +341,17 @@ export class TokenHandler {
328341

329342
return true;
330343
}
344+
345+
/**
346+
* Check if the request is a PCKE request. We assume PKCE if grant type is 'authorization_code'
347+
* and code verifier is present.
348+
*/
349+
isPKCERequest(request: Request, grantType: string): boolean {
350+
if (grantType === 'authorization_code' && request.body.code_verifier) {
351+
return true;
352+
}
353+
354+
return false;
355+
}
356+
331357
}

lib/interfaces/authorization-code.interface.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,5 +10,7 @@ export interface AuthorizationCode {
1010
scope?: string;
1111
client: Client;
1212
user: User;
13+
codeChallenge?: string;
14+
codeChallengeMethod?: 'S256' | 'plain' | null;
1315
[key: string]: any;
1416
}

0 commit comments

Comments
 (0)