Skip to content

Commit

Permalink
feat: add support for authorized 3rd-parties (azp) (closes panva#231)
Browse files Browse the repository at this point in the history
  • Loading branch information
svvac committed Feb 27, 2020
1 parent a633ec4 commit 1a09e40
Show file tree
Hide file tree
Showing 5 changed files with 86 additions and 6 deletions.
2 changes: 2 additions & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,8 @@ Creates a new Client with the provided metadata
- `jwks`: `<Object>` JWK Set formatted object with private keys used for signing client assertions
or decrypting responses.
- `options`: `<Object>` additional options for the client
- `authorizedThirdParties`: `<boolean|string|string[]>` additional authorized values for the
Authorized Party claim. Accept any value if set to true. **Default:** 'false'.
- Returns: `<Client>`

---
Expand Down
28 changes: 23 additions & 5 deletions lib/client.js
Original file line number Diff line number Diff line change
Expand Up @@ -908,11 +908,29 @@ module.exports = (issuer, aadIssValidation = false) => class Client extends Base
}
}

if (payload.azp !== undefined && payload.azp !== this.client_id) {
throw new RPError({
printf: ['azp must be the client_id, expected %s, got: %s', this.client_id, payload.azp],
jwt,
});
if (payload.azp !== undefined) {
if (payload.azp === this.client_id) {
// Accept if issued to this client
} else if (!this.options.authorizedThirdParties) {
// Not issued to self and no configured authorized 3rd parties
throw new RPError({
printf: ['azp must be the client_id, expected %s, got: %s', this.client_id, payload.azp],
jwt,
});
} else if (this.options.authorizedThirdParties === true) {
// Accept any authorized party
} else if (typeof this.options.authorizedThirdParties === 'string'
&& payload.azp === this.options.authorizedThirdParties) {
// values match
} else if (Array.isArray(this.options.authorizedThirdParties)
&& this.options.authorizedThirdParties.includes(payload.azp)) {
// value included
} else {
throw new RPError({
message: 'unrecognized azp',
jwt,
});
}
}

let key;
Expand Down
4 changes: 3 additions & 1 deletion lib/helpers/consts.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@ const CLIENT_DEFAULTS = {
token_endpoint_auth_method: 'client_secret_basic',
};

const CLIENT_DEFAULT_OPTIONS = {};
const CLIENT_DEFAULT_OPTIONS = {
authorizedThirdParties: false,
};

const ISSUER_DEFAULTS = {
claim_types_supported: ['normal'],
Expand Down
57 changes: 57 additions & 0 deletions test/client/client_instance.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -1845,6 +1845,18 @@ describe('Client', () => {
client_id: 'identifier',
client_secret: 'its gotta be a long secret and i mean at least 32 characters',
});
this.clientWith3rdParty = new this.issuer.Client({
client_id: 'identifier',
client_secret: 'its gotta be a long secret and i mean at least 32 characters',
}, undefined, { authorizedThirdParties: 'authorized third party' });
this.clientWith3rdParties = new this.issuer.Client({
client_id: 'identifier',
client_secret: 'its gotta be a long secret and i mean at least 32 characters',
}, undefined, { authorizedThirdParties: ['authorized third party', 'another third party'] });
this.clientWithUnknown3rdParties = new this.issuer.Client({
client_id: 'identifier',
client_secret: 'its gotta be a long secret and i mean at least 32 characters',
}, undefined, { authorizedThirdParties: true });

this.fapiClient = new this.issuer.FAPIClient({
client_id: 'identifier',
Expand Down Expand Up @@ -2014,6 +2026,51 @@ describe('Client', () => {
.then((token) => this.client.validateIdToken(token));
});

it('rejects unknown 3rd party azp values', function () {
const payload = {
iss: this.issuer.issuer,
sub: 'userId',
aud: [this.client.client_id, 'someone else'],
azp: 'some unknown third party',
exp: now() + 3600,
iat: now(),
};

return this.IdToken(this.keystore.get(), 'RS256', payload)
.then((token) => this.clientWith3rdParty.validateIdToken(token))
.then(fail, (error) => {
expect(error).to.have.property('message', 'unrecognized azp');
});
});

it('allows configured allowed 3rd party azp value', function () {
const payload = {
iss: this.issuer.issuer,
sub: 'userId',
aud: [this.client.client_id, 'someone else'],
azp: 'authorized third party',
exp: now() + 3600,
iat: now(),
};

return this.IdToken(this.keystore.get(), 'RS256', payload)
.then((token) => this.clientWith3rdParty.validateIdToken(token));
});

it('allows all azp values when allowedThirdParties is true', function () {
const payload = {
iss: this.issuer.issuer,
sub: 'userId',
aud: [this.client.client_id, 'someone else'],
azp: 'unknown third party',
exp: now() + 3600,
iat: now(),
};

return this.IdToken(this.keystore.get(), 'RS256', payload)
.then((token) => this.clientWithUnknown3rdParties.validateIdToken(token));
});

it('verifies the audience when string', function () {
const payload = {
iss: this.issuer.issuer,
Expand Down
1 change: 1 addition & 0 deletions types/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -335,6 +335,7 @@ export interface IntrospectionResponse {
}

export interface ClientOptions {
authorizedThirdParties?: boolean | string | string[];
}

/**
Expand Down

0 comments on commit 1a09e40

Please sign in to comment.