Skip to content

Commit

Permalink
feat: add server-wide policy configuration on accepting tokens in query
Browse files Browse the repository at this point in the history
  • Loading branch information
panva committed Apr 27, 2020
1 parent 219e8c3 commit 90b400a
Show file tree
Hide file tree
Showing 5 changed files with 38 additions and 6 deletions.
11 changes: 11 additions & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -1807,6 +1807,17 @@ function scriptNonce(ctx) {

</details>

### acceptQueryParamAccessTokens

Several OAuth 2.0 / OIDC profiles prohibit the use of query strings to carry access tokens. This setting either allows (true) or prohibits (false) that mechanism to be used.



_**default value**_:
```js
true
```

### acrValues

Array of strings, the Authentication Context Class References that OP supports.
Expand Down
1 change: 0 additions & 1 deletion lib/actions/userinfo.js
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,6 @@ module.exports = [
async function validateAccessToken(ctx, next) {
const accessToken = await ctx.oidc.provider.AccessToken.find(ctx.oidc.getAccessToken({
acceptDPoP: true,
acceptQueryParam: !instance(ctx.oidc.provider).configuration('features.fapiRW.enabled'),
}));

ctx.assert(accessToken, new InvalidToken('access token not found'));
Expand Down
11 changes: 11 additions & 0 deletions lib/helpers/defaults.js
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,17 @@ const DEFAULTS = {
conformIdTokenClaims: true,


/*
* acceptQueryParamAccessTokens
*
* description: Several OAuth 2.0 / OIDC profiles prohibit the use of query strings to carry
* access tokens. This setting either allows (true) or prohibits (false) that mechanism to be
* used.
*
*/
acceptQueryParamAccessTokens: true,


/*
* cookies
*
Expand Down
17 changes: 14 additions & 3 deletions lib/helpers/oidc_context.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,11 @@ const COOKIES = Symbol('context#cookies');
const UID = Symbol('context#uid');

module.exports = function getContext(provider) {
const { clockTolerance, features: { dPoP: dPoPConfig } } = instance(provider).configuration();
const {
acceptQueryParamAccessTokens,
clockTolerance,
features: { dPoP: dPoPConfig, fapiRW: { enabled: fapiEnabled } },
} = instance(provider).configuration();
const { app } = provider;

class OIDCContext extends events.EventEmitter {
Expand Down Expand Up @@ -293,15 +297,18 @@ module.exports = function getContext(provider) {
return this.entities.Client;
}

getAccessToken({ acceptDPoP = false, acceptQueryParam = true } = {}) {
getAccessToken({
acceptDPoP = false, acceptQueryParam = !fapiEnabled && acceptQueryParamAccessTokens,
} = {}) {
if ('accessToken' in instance(this)) {
return instance(this).accessToken;
}

const { ctx } = this;
const mechanisms = omitBy({
body: get(ctx.oidc, 'body.access_token'),
header: ctx.headers.authorization,
query: acceptQueryParam ? ctx.query.access_token : undefined,
query: ctx.query.access_token,
}, (value) => typeof value !== 'string' || !value);

debug('uid=%s received access token via %o', this.uid, mechanisms);
Expand All @@ -322,6 +329,10 @@ module.exports = function getContext(provider) {
throw new InvalidRequest('access token must only be provided using one mechanism');
}

if (!acceptQueryParam && mechanism === 'query') {
throw new InvalidRequest('access tokens must not be provided via query parameter');
}

let dPoP;
if (acceptDPoP && dPoPConfig.enabled) {
({ dPoP } = this);
Expand Down
4 changes: 2 additions & 2 deletions test/fapirw/fapirw.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,11 @@ describe('Financial-grade API - Part 2: Read and Write API Security Profile beha
.expect('x-fapi-interaction-id', 'b2bef873-2fd8-4fcd-943b-caafcd0b1c3b');
});

it('does not recognize query string bearer token', async function () {
it('does not allow query string bearer token', async function () {
const at = await new this.provider.AccessToken({ clientId: 'client', accountId: 'account', scope: 'openid' }).save();
await this.agent.get('/me')
.query({ access_token: at })
.expect(this.failWith(400, 'invalid_request', 'no access token provided'));
.expect(this.failWith(400, 'invalid_request', 'access tokens must not be provided via query parameter'));

await this.agent.get('/me')
.auth(at, { type: 'bearer' })
Expand Down

0 comments on commit 90b400a

Please sign in to comment.