Skip to content

Commit

Permalink
feat: allow consuming JARM responses (jwt response mode)
Browse files Browse the repository at this point in the history
  • Loading branch information
panva committed Dec 27, 2019
1 parent 3d9095c commit dd4aae9
Show file tree
Hide file tree
Showing 4 changed files with 70 additions and 12 deletions.
1 change: 1 addition & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -283,6 +283,7 @@ Performs the callback for Authorization Server's authorization response.
- `state`: `<string>` When provided the authorization response's state parameter will be checked
to be the this expected one. Use of this check is required if you sent a state parameter into an
authorization request.
- `jarm`: `<boolean>` When provided the authorization response must be a JARM one.
- `nonce`: `<string>` When provided the authorization response's ID Token nonce parameter will be
checked to be the this expected one. Use of this check is required if you sent a nonce parameter
into an authorization request.
Expand Down
51 changes: 49 additions & 2 deletions lib/client.js
Original file line number Diff line number Diff line change
Expand Up @@ -373,7 +373,18 @@ module.exports = (issuer, aadIssValidation = false) => class Client extends Base
checks = {},
{ exchangeBody, clientAssertionPayload } = {},
) {
const params = pickCb(parameters);
let params = pickCb(parameters);

if (checks.jarm && !('response' in parameters)) {
throw new RPError({
message: 'expected a JARM response',
checks,
params,
});
} else if ('response' in parameters) {
const decrypted = await this.decryptJARM(params.response);
params = await this.validateJARM(decrypted);
}

if (this.default_max_age && !checks.max_age) {
checks.max_age = this.default_max_age;
Expand Down Expand Up @@ -475,7 +486,18 @@ module.exports = (issuer, aadIssValidation = false) => class Client extends Base
checks = {},
{ exchangeBody, clientAssertionPayload } = {},
) {
const params = pickCb(parameters);
let params = pickCb(parameters);

if (checks.jarm && !('response' in parameters)) {
throw new RPError({
message: 'expected a JARM response',
checks,
params,
});
} else if ('response' in parameters) {
const decrypted = await this.decryptJARM(params.response);
params = await this.validateJARM(decrypted);
}

if (params.state && !checks.state) {
throw new TypeError('checks.state argument is missing');
Expand Down Expand Up @@ -582,6 +604,31 @@ module.exports = (issuer, aadIssValidation = false) => class Client extends Base
return this.validateJWT(body, expectedAlg, []);
}

/**
* @name decryptJARM
* @api private
*/
async decryptJARM(response) {
if (!this.authorization_encrypted_response_alg && !this.authorization_encrypted_response_enc) {
return response;
}

const expectedAlg = this.authorization_encrypted_response_alg;
const expectedEnc = this.authorization_encrypted_response_enc;

return this.decryptJWE(response, expectedAlg, expectedEnc);
}

/**
* @name validateJARM
* @api private
*/
async validateJARM(response) {
const expectedAlg = this.authorization_signed_response_alg;
const { payload } = await this.validateJWT(response, expectedAlg, ['iss', 'exp', 'aud']);
return pickCb(payload);
}

/**
* @name decryptJWTUserinfo
* @api private
Expand Down
22 changes: 12 additions & 10 deletions lib/helpers/consts.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ const AAD_MULTITENANT_DISCOVERY = new Set([
const CLIENT_DEFAULTS = {
grant_types: ['authorization_code'],
id_token_signed_response_alg: 'RS256',
authorization_signed_response_alg: 'RS256',
response_types: ['code'],
token_endpoint_auth_method: 'client_secret_basic',
};
Expand All @@ -27,16 +28,17 @@ const ISSUER_DEFAULTS = {
};

const CALLBACK_PROPERTIES = [
'access_token',
'code',
'error',
'error_description',
'error_uri',
'expires_in',
'id_token',
'state',
'token_type',
'session_state',
'access_token', // 6749
'code', // 6749
'error', // 6749
'error_description', // 6749
'error_uri', // 6749
'expires_in', // 6749
'id_token', // Core 1.0
'state', // 6749
'token_type', // 6749
'session_state', // Session Management
'response', // JARM
];

const JWT_CONTENT = /^application\/jwt/;
Expand Down
8 changes: 8 additions & 0 deletions types/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,9 @@ export interface ClientMetadata {
userinfo_encrypted_response_alg?: string;
userinfo_encrypted_response_enc?: string;
userinfo_signed_response_alg?: string;
authorization_encrypted_response_alg?: string;
authorization_encrypted_response_enc?: string;
authorization_signed_response_alg?: string;

[key: string]: unknown;
}
Expand Down Expand Up @@ -133,6 +136,7 @@ export interface CallbackParamsType {
state?: string;
token_type?: string;
session_state?: string;
response?: string;

[key: string]: unknown;
}
Expand All @@ -153,6 +157,10 @@ export interface OAuthCallbackChecks {
* if you sent a code_challenge parameter into an authorization request.
*/
code_verifier?: string;
/**
* This must be set to true when requesting JARM responses.
*/
jarm?: boolean;
}

export interface OpenIDCallbackChecks extends OAuthCallbackChecks {
Expand Down

0 comments on commit dd4aae9

Please sign in to comment.