Skip to content

Commit

Permalink
feat: update features.jwtIntrospection to draft 09
Browse files Browse the repository at this point in the history
  • Loading branch information
panva committed Apr 27, 2020
1 parent e59cc66 commit 219e8c3
Show file tree
Hide file tree
Showing 8 changed files with 56 additions and 22 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ enabled by default, check the configuration section on how to enable them.

The following draft specifications are implemented by oidc-provider.
- [JSON Web Token (JWT) Profile for OAuth 2.0 Access Tokens - draft 05][jwt-at]
- [JWT Response for OAuth Token Introspection - draft 08][jwt-introspection]
- [JWT Response for OAuth Token Introspection - draft 09][jwt-introspection]
- [JWT Secured Authorization Response Mode for OAuth 2.0 (JARM) - draft 02][jarm]
- [OAuth 2.0 Demonstration of Proof-of-Possession at the Application Layer (DPoP) - draft 00][dpop]
- [OAuth 2.0 JWT Secured Authorization Request (JAR)][jar]
Expand Down Expand Up @@ -166,7 +166,7 @@ See the list of available emitted [event names](/docs/events.md) and their descr
[wmrm]: https://tools.ietf.org/html/draft-sakimura-oauth-wmrm-00
[jar]: https://tools.ietf.org/html/draft-ietf-oauth-jwsreq-19
[device-flow]: https://tools.ietf.org/html/rfc8628
[jwt-introspection]: https://tools.ietf.org/html/draft-ietf-oauth-jwt-introspection-response-08
[jwt-introspection]: https://tools.ietf.org/html/draft-ietf-oauth-jwt-introspection-response-09
[sponsor-auth0]: https://auth0.com/overview?utm_source=GHsponsor&utm_medium=GHsponsor&utm_campaign=oidc-provider&utm_content=auth
[mtls]: https://tools.ietf.org/html/rfc8705
[dpop]: https://tools.ietf.org/html/draft-ietf-oauth-dpop-00
Expand Down
2 changes: 1 addition & 1 deletion docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -1093,7 +1093,7 @@ async function allowedPolicy(ctx, client, token) {

### features.jwtIntrospection

[draft-ietf-oauth-jwt-introspection-response-08](https://tools.ietf.org/html/draft-ietf-oauth-jwt-introspection-response-08) - JWT Response for OAuth Token Introspection
[draft-ietf-oauth-jwt-introspection-response-09](https://tools.ietf.org/html/draft-ietf-oauth-jwt-introspection-response-09) - JWT Response for OAuth Token Introspection

Enables JWT responses for Token Introspection features

Expand Down
9 changes: 4 additions & 5 deletions lib/actions/introspection.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ const Debug = require('debug');
const debug = new Debug('oidc-provider:introspection');
const uidToGrantId = new Debug('oidc-provider:uid');

const epochTime = require('../helpers/epoch_time');
const presence = require('../helpers/validate_presence');
const getTokenAuth = require('../shared/token_auth');
const noCache = require('../shared/no_cache');
Expand All @@ -14,7 +13,7 @@ const paramsMiddleware = require('../shared/assemble_params');
const { InvalidRequest } = require('../helpers/errors');

const introspectable = new Set(['AccessToken', 'ClientCredentials', 'RefreshToken']);
const JWT = 'application/jwt';
const JWT = 'application/token-introspection+jwt';

module.exports = function introspectionAction(provider) {
const { params: authParams, middleware: tokenAuth } = getTokenAuth(provider, 'introspection');
Expand Down Expand Up @@ -96,12 +95,12 @@ module.exports = function introspectionAction(provider) {
if ((encrypt || sign) && accepts === JWT) {
const token = new IdToken({}, { ctx });
token.extra = {
...ctx.body,
iat: ctx.body.iat ? epochTime() : undefined,
token_introspection: ctx.body,
aud: ctx.body.aud,
};

ctx.body = await token.issue({ use: 'introspection' });
ctx.type = 'application/jwt; charset=utf-8';
ctx.type = 'application/token-introspection+jwt; charset=utf-8';
}
} else {
await next();
Expand Down
2 changes: 1 addition & 1 deletion lib/helpers/defaults.js
Original file line number Diff line number Diff line change
Expand Up @@ -842,7 +842,7 @@ const DEFAULTS = {
/*
* features.jwtIntrospection
*
* title: [draft-ietf-oauth-jwt-introspection-response-08](https://tools.ietf.org/html/draft-ietf-oauth-jwt-introspection-response-08) - JWT Response for OAuth Token Introspection
* title: [draft-ietf-oauth-jwt-introspection-response-09](https://tools.ietf.org/html/draft-ietf-oauth-jwt-introspection-response-09) - JWT Response for OAuth Token Introspection
*
* description: Enables JWT responses for Token Introspection features
*
Expand Down
3 changes: 2 additions & 1 deletion lib/models/id_token.js
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,8 @@ module.exports = function getIdToken(provider) {
case 'introspection':
alg = client.introspectionSignedResponseAlg;
signOptions = {
noIat: true,
audience: client.clientId,
issuer: provider.issuer,
typ: 'token-introspection+jwt',
};
encryption = {
Expand Down
54 changes: 44 additions & 10 deletions test/jwt_introspection/jwt_introspection.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,8 @@ describe('jwtIntrospection features', () => {
.expect(200)
.expect('content-type', 'application/json; charset=utf-8')
.expect(({ body }) => {
({ iat, ...json } = body);
json = body;
iat = json.iat;
});

timekeeper.travel(now + (10 * 1000));
Expand All @@ -66,17 +67,50 @@ describe('jwtIntrospection features', () => {
token,
})
.type('form')
.accept('application/jwt')
.accept('application/token-introspection+jwt')
.expect(200)
.expect('content-type', 'application/jwt; charset=utf-8')
.expect('content-type', 'application/token-introspection+jwt; charset=utf-8')
.expect(({ text }) => {
const { payload: { iat: jwtIat, ...payload }, header } = JWT.decode(text);
expect(payload).to.eql(json);
const {
payload: {
iat: jwtIat, iss, aud, token_introspection,
}, header,
} = JWT.decode(text);
expect(iss).to.eql(this.provider.issuer);
expect(aud).to.eql('client-signed');
expect(token_introspection).to.eql(json);
expect(jwtIat).to.eql(iat + 10);
expect(header).to.have.property('typ', 'token-introspection+jwt');
});
});

it('returns the response as jwt (active: false)', async function () {
const now = Date.now();
timekeeper.freeze(now);

return this.agent.post(route)
.auth('client-signed', 'secret')
.send({
token: 'foobar',
})
.type('form')
.accept('application/token-introspection+jwt')
.expect(200)
.expect('content-type', 'application/token-introspection+jwt; charset=utf-8')
.expect(({ text }) => {
const {
payload: {
iat: jwtIat, iss, aud, token_introspection,
}, header,
} = JWT.decode(text);
expect(iss).to.eql(this.provider.issuer);
expect(aud).to.eql('client-signed');
expect(token_introspection).to.eql({ active: false });
expect(jwtIat).to.eql(Math.floor(now / 1000));
expect(header).to.have.property('typ', 'token-introspection+jwt');
});
});

it('errors when secret is expired for HMAC alg', async function () {
const at = new this.provider.AccessToken({
accountId: 'accountId',
Expand All @@ -93,7 +127,7 @@ describe('jwtIntrospection features', () => {
token,
})
.type('form')
.accept('application/jwt')
.accept('application/token-introspection+jwt')
.expect(400)
.expect('content-type', 'application/json; charset=utf-8')
.expect({
Expand All @@ -102,7 +136,7 @@ describe('jwtIntrospection features', () => {
});
});

it('non-authenticated without accept: application/jwt fails', async function () {
it('non-authenticated without accept: application/token-introspection+jwt fails', async function () {
const at = new this.provider.AccessToken({
accountId: 'accountId',
grantId: 'foo',
Expand All @@ -121,7 +155,7 @@ describe('jwtIntrospection features', () => {
.expect('content-type', 'application/json; charset=utf-8')
.expect({
error: 'invalid_request',
error_description: 'introspection must be requested with Accept: application/jwt for this client',
error_description: 'introspection must be requested with Accept: application/token-introspection+jwt for this client',
});

return this.agent.post(route)
Expand All @@ -130,9 +164,9 @@ describe('jwtIntrospection features', () => {
token,
})
.type('form')
.accept('application/jwt')
.accept('application/token-introspection+jwt')
.expect(200)
.expect('content-type', 'application/jwt; charset=utf-8')
.expect('content-type', 'application/token-introspection+jwt; charset=utf-8')
.expect(({ text }) => {
const header = JWT.header(text);
expect(header).to.have.property('alg', 'PBES2-HS256+A128KW');
Expand Down
2 changes: 1 addition & 1 deletion types/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -972,7 +972,7 @@ export interface Configuration {

jwtIntrospection?: {
enabled?: boolean,
ack?: 8 | 'draft-08'
ack?: 'draft-09'
},

jwtResponseModes?: {
Expand Down
2 changes: 1 addition & 1 deletion types/oidc-provider-tests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -331,7 +331,7 @@ const provider = new Provider('https://op.example.com', {
webMessageResponseMode: { enabled: false, ack: 'id-00', scriptNonce() { return "foo"; } },
revocation: { enabled: false },
sessionManagement: { enabled: false, ack: 28, keepHeaders: false, scriptNonce() { return "foo"; } },
jwtIntrospection: { enabled: false, ack: 8 },
jwtIntrospection: { enabled: false, ack: 'draft-09' },
jwtResponseModes: { enabled: false, ack: 2 },
pushedAuthorizationRequests: { enabled: false, ack: 0 },
secp256k1: { enabled: false, ack: 'draft-03' },
Expand Down

0 comments on commit 219e8c3

Please sign in to comment.