From 3b2363de16cbb76d8d6fb734728992937dd76e10 Mon Sep 17 00:00:00 2001 From: Dan Hensby Date: Thu, 15 Oct 2026 15:21:54 +0100 Subject: [PATCH] fix: default `addAcceptedScopesHeader` and `addAuthorizedScopesHeader` to `true` Both options are documented as defaulting to `true`, but only `OAuth2Server#authenticate()` applied that default. The AuthenticateHandler itself threw `InvalidArgumentError: Missing parameter` when `scope` was set without them. As a result, `OAuth2Server#authorize()` threw synchronously for any server configured with a `scope` option (which the constructor docs say is used as the default for the other methods) and no custom `authenticateHandler`, because the default AuthenticateHandler it creates received the server's `scope` but not the header options. The AuthenticateHandler now applies the documented default, so `authorize()` works and sets both scopes headers, and explicit `false` values are still respected. Co-Authored-By: Claude Opus 5.5 --- lib/handlers/authenticate-handler.js | 14 ++----- .../handlers/authenticate-handler_test.js | 39 +++++++---------- test/integration/server_test.js | 42 +++++++++++++++++++ 3 files changed, 62 insertions(+), 33 deletions(-) diff --git a/lib/handlers/authenticate-handler.js b/lib/handlers/authenticate-handler.js index 4072f385..f292e47d 100644 --- a/lib/handlers/authenticate-handler.js +++ b/lib/handlers/authenticate-handler.js @@ -41,20 +41,14 @@ class AuthenticateHandler { throw new InvalidArgumentError('Invalid argument: model does not implement `getAccessToken()`'); } - if (options.scope && undefined === options.addAcceptedScopesHeader) { - throw new InvalidArgumentError('Missing parameter: `addAcceptedScopesHeader`'); - } - - if (options.scope && undefined === options.addAuthorizedScopesHeader) { - throw new InvalidArgumentError('Missing parameter: `addAuthorizedScopesHeader`'); - } - if (options.scope && !options.model.verifyScope) { throw new InvalidArgumentError('Invalid argument: model does not implement `verifyScope()`'); } - this.addAcceptedScopesHeader = options.addAcceptedScopesHeader; - this.addAuthorizedScopesHeader = options.addAuthorizedScopesHeader; + this.addAcceptedScopesHeader = + undefined === options.addAcceptedScopesHeader ? true : options.addAcceptedScopesHeader; + this.addAuthorizedScopesHeader = + undefined === options.addAuthorizedScopesHeader ? true : options.addAuthorizedScopesHeader; this.allowBearerTokensInQueryString = options.allowBearerTokensInQueryString; this.model = options.model; this.scope = Array.isArray(options.scope) ? options.scope : parseScope(options.scope); diff --git a/test/integration/handlers/authenticate-handler_test.js b/test/integration/handlers/authenticate-handler_test.js index f3e5e92d..a9169a4f 100644 --- a/test/integration/handlers/authenticate-handler_test.js +++ b/test/integration/handlers/authenticate-handler_test.js @@ -45,33 +45,26 @@ describe('AuthenticateHandler integration', function () { } }); - it('should throw an error if `scope` was given and `addAcceptedScopesHeader()` is missing', function () { - try { - new AuthenticateHandler({ - model: { getAccessToken: function () {} }, - scope: ['foobar'], - }); + it('should default `addAcceptedScopesHeader` to `true` if `scope` was given', function () { + const handler = new AuthenticateHandler({ + addAuthorizedScopesHeader: false, + model: { getAccessToken: function () {}, verifyScope: function () {} }, + scope: ['foobar'], + }); - should.fail(); - } catch (e) { - e.should.be.an.instanceOf(InvalidArgumentError); - e.message.should.equal('Missing parameter: `addAcceptedScopesHeader`'); - } + handler.addAcceptedScopesHeader.should.be.true; + handler.addAuthorizedScopesHeader.should.be.false; }); - it('should throw an error if `scope` was given and `addAuthorizedScopesHeader()` is missing', function () { - try { - new AuthenticateHandler({ - addAcceptedScopesHeader: true, - model: { getAccessToken: function () {} }, - scope: ['foobar'], - }); + it('should default `addAuthorizedScopesHeader` to `true` if `scope` was given', function () { + const handler = new AuthenticateHandler({ + addAcceptedScopesHeader: false, + model: { getAccessToken: function () {}, verifyScope: function () {} }, + scope: ['foobar'], + }); - should.fail(); - } catch (e) { - e.should.be.an.instanceOf(InvalidArgumentError); - e.message.should.equal('Missing parameter: `addAuthorizedScopesHeader`'); - } + handler.addAcceptedScopesHeader.should.be.false; + handler.addAuthorizedScopesHeader.should.be.true; }); it('should throw an error if `scope` was given and the model does not implement `verifyScope()`', function () { diff --git a/test/integration/server_test.js b/test/integration/server_test.js index 53617806..914bb1f0 100644 --- a/test/integration/server_test.js +++ b/test/integration/server_test.js @@ -165,6 +165,48 @@ describe('Server integration', function () { handler.should.be.an.instanceOf(Promise); }); + + it('should set the scopes headers by default if `scope` is set on the server', async function () { + const model = Model.from({ + getAccessToken: function () { + return { + user: {}, + accessTokenExpiresAt: new Date(new Date().getTime() + 10000), + scope: ['foo', 'bar'], + }; + }, + verifyScope: function () { + return true; + }, + getClient: function () { + return { + id: 1234, + grants: ['authorization_code'], + redirectUris: ['http://example.com/cb'], + }; + }, + saveAuthorizationCode: function () { + return { authorizationCode: 123 }; + }, + }); + const server = new Server({ model: model, scope: ['foo'] }); + const request = new Request({ + body: { + client_id: 1234, + response_type: 'code', + }, + headers: { Authorization: 'Bearer foo' }, + method: {}, + query: { state: 'foobar' }, + }); + const response = new Response({ body: {}, headers: {} }); + + const code = await server.authorize(request, response); + + code.authorizationCode.should.equal(123); + response.get('X-Accepted-OAuth-Scopes').should.equal('foo'); + response.get('X-OAuth-Scopes').should.equal('foo bar'); + }); }); describe('token()', function () {