Skip to content

Commit

Permalink
feat: add support for additional authorized parties (azp) (closes pan…
Browse files Browse the repository at this point in the history
  • Loading branch information
svvac committed Feb 28, 2020
1 parent 30a41d1 commit ec36e1d
Show file tree
Hide file tree
Showing 4 changed files with 123 additions and 6 deletions.
6 changes: 6 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
- `additionalAuthorizedParties`: `<string>` &vert; `string[]` additional accepted values for the
Authorized Party (`azp`) claim. **Default:** only the client's client_id value is accepted.
- Returns: `<Client>`

---
Expand Down Expand Up @@ -488,6 +490,8 @@ Performs Dynamic Client Registration with the provided metadata at the issuer's
public parts will be registered as `jwks`.
- `initialAccessToken`: `<string>` Initial Access Token to use as a Bearer token during the
registration call.
- `additionalAuthorizedParties`: `<string>` &vert; `string[]` additional accepted values for the
Authorized Party (`azp`) claim. **Default:** only the client's client_id value is accepted.

---

Expand All @@ -501,6 +505,8 @@ Performs Dynamic Client Read Request to retrieve a Client instance.
- `jwks`: `<Object>` JWK Set formatted object with private keys used for signing client assertions
or decrypting responses.
- `clientOptions`: `<Object>` additional options passed to the `Client` constructor
- `additionalAuthorizedParties`: `<string>` &vert; `string[]` additional accepted values for the
Authorized Party (`azp`) claim. **Default:** only the client's client_id value is accepted.

---

Expand Down
22 changes: 17 additions & 5 deletions lib/client.js
Original file line number Diff line number Diff line change
Expand Up @@ -907,11 +907,23 @@ 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) {
let { additionalAuthorizedParties } = instance(this).get('options') || {};

if (typeof additionalAuthorizedParties === 'string') {
additionalAuthorizedParties = [this.client_id, additionalAuthorizedParties];
} else if (Array.isArray(additionalAuthorizedParties)) {
additionalAuthorizedParties = [this.client_id, ...additionalAuthorizedParties];
} else {
additionalAuthorizedParties = [this.client_id];
}

if (!additionalAuthorizedParties.includes(payload.azp)) {
throw new RPError({
printf: ['azp mismatch, got: %s', payload.azp],
jwt,
});
}
}

let key;
Expand Down
100 changes: 99 additions & 1 deletion test/client/client_instance.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -1845,6 +1845,14 @@ 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, { additionalAuthorizedParties: '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, { additionalAuthorizedParties: ['authorized third party', 'another third party'] });

this.fapiClient = new this.issuer.FAPIClient({
client_id: 'identifier',
Expand Down Expand Up @@ -1980,7 +1988,7 @@ describe('Client', () => {
return this.IdToken(this.keystore.get(), 'RS256', payload)
.then((token) => this.client.validateIdToken(token))
.then(fail, (error) => {
expect(error).to.have.property('message', 'azp must be the client_id, expected identifier, got: not the client');
expect(error).to.have.property('message', 'azp mismatch, got: not the client');
});
});

Expand Down Expand Up @@ -2014,6 +2022,96 @@ describe('Client', () => {
.then((token) => this.client.validateIdToken(token));
});

it('rejects unknown additional party azp values (single additional value)', 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', 'azp mismatch, got: some unknown third party');
});
});

it('allows configured additional party azp value (single additional 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 the default (client_id) additional party azp value (single additional value)', function () {
const payload = {
iss: this.issuer.issuer,
sub: 'userId',
aud: [this.client.client_id, 'someone else'],
azp: this.client.client_id,
exp: now() + 3600,
iat: now(),
};

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

it('rejects unknown additional party azp values (multiple additional 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.clientWith3rdParties.validateIdToken(token))
.then(fail, (error) => {
expect(error).to.have.property('message', 'azp mismatch, got: some unknown third party');
});
});

it('allows configured additional party azp value (multiple additional values)', 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.clientWith3rdParties.validateIdToken(token));
});

it('allows the default (client_id) additional party azp value (multiple additional values)', function () {
const payload = {
iss: this.issuer.issuer,
sub: 'userId',
aud: [this.client.client_id, 'someone else'],
azp: this.client.client_id,
exp: now() + 3600,
iat: now(),
};

return this.IdToken(this.keystore.get(), 'RS256', payload)
.then((token) => this.clientWith3rdParties.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 {
additionalAuthorizedParties?: false | string | string[];
}

/**
Expand Down

0 comments on commit ec36e1d

Please sign in to comment.