Skip to content
This repository was archived by the owner on Jan 20, 2024. It is now read-only.

Commit a237644

Browse files
authored
Fixes #98: Do not send empty scopes to an auth server (#154)
If `scopes` is set to `""` or `[]` then we should send an empty string. If `scopes` is undefined (not set), then we don't send it at all.
1 parent 75af020 commit a237644

File tree

7 files changed

+302
-29
lines changed

7 files changed

+302
-29
lines changed

src/client-oauth2.js

Lines changed: 38 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -161,15 +161,19 @@ function createUri (options, tokenType) {
161161
// Check the required parameters are set.
162162
expects(options, 'clientId', 'authorizationUri')
163163

164-
const sep = options.authorizationUri.includes('?') ? '&' : '?'
165-
166-
return options.authorizationUri + sep + Querystring.stringify(Object.assign({
164+
const qs = {
167165
client_id: options.clientId,
168166
redirect_uri: options.redirectUri,
169-
scope: sanitizeScope(options.scopes),
170167
response_type: tokenType,
171168
state: options.state
172-
}, options.query))
169+
}
170+
if (options.scopes !== undefined) {
171+
qs.scope = sanitizeScope(options.scopes)
172+
}
173+
174+
const sep = options.authorizationUri.includes('?') ? '&' : '?'
175+
return options.authorizationUri + sep + Querystring.stringify(
176+
Object.assign(qs, options.query))
173177
}
174178

175179
/**
@@ -417,18 +421,22 @@ OwnerFlow.prototype.getToken = function (username, password, opts) {
417421
var self = this
418422
var options = Object.assign({}, this.client.options, opts)
419423

424+
const body = {
425+
username: username,
426+
password: password,
427+
grant_type: 'password'
428+
}
429+
if (options.scopes !== undefined) {
430+
body.scope = sanitizeScope(options.scopes)
431+
}
432+
420433
return this.client._request(requestOptions({
421434
url: options.accessTokenUri,
422435
method: 'POST',
423436
headers: Object.assign({}, DEFAULT_HEADERS, {
424437
Authorization: auth(options.clientId, options.clientSecret)
425438
}),
426-
body: {
427-
scope: sanitizeScope(options.scopes),
428-
username: username,
429-
password: password,
430-
grant_type: 'password'
431-
}
439+
body: body
432440
}, options))
433441
.then(function (data) {
434442
return self.client.createToken(data)
@@ -530,16 +538,21 @@ CredentialsFlow.prototype.getToken = function (opts) {
530538

531539
expects(options, 'clientId', 'clientSecret', 'accessTokenUri')
532540

541+
const body = {
542+
grant_type: 'client_credentials'
543+
}
544+
545+
if (options.scopes !== undefined) {
546+
body.scope = sanitizeScope(options.scopes)
547+
}
548+
533549
return this.client._request(requestOptions({
534550
url: options.accessTokenUri,
535551
method: 'POST',
536552
headers: Object.assign({}, DEFAULT_HEADERS, {
537553
Authorization: auth(options.clientId, options.clientSecret)
538554
}),
539-
body: {
540-
scope: sanitizeScope(options.scopes),
541-
grant_type: 'client_credentials'
542-
}
555+
body: body
543556
}, options))
544557
.then(function (data) {
545558
return self.client.createToken(data)
@@ -671,15 +684,20 @@ JwtBearerFlow.prototype.getToken = function (token, opts) {
671684
headers.Authorization = auth(options.clientId, options.clientSecret)
672685
}
673686

687+
const body = {
688+
grant_type: 'urn:ietf:params:oauth:grant-type:jwt-bearer',
689+
assertion: token
690+
}
691+
692+
if (options.scopes !== undefined) {
693+
body.scope = sanitizeScope(options.scopes)
694+
}
695+
674696
return this.client._request(requestOptions({
675697
url: options.accessTokenUri,
676698
method: 'POST',
677699
headers: headers,
678-
body: {
679-
scope: sanitizeScope(options.scopes),
680-
grant_type: 'urn:ietf:params:oauth:grant-type:jwt-bearer',
681-
assertion: token
682-
}
700+
body: body
683701
}, options))
684702
.then(function (data) {
685703
return self.client.createToken(data)

test/code.js

Lines changed: 51 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,56 @@ describe('code', function () {
2121
expect(githubAuth.code.getUri()).to.equal(
2222
config.authorizationUri + '?client_id=abc&' +
2323
'redirect_uri=http%3A%2F%2Fexample.com%2Fauth%2Fcallback&' +
24-
'scope=notifications&response_type=code&state='
24+
'response_type=code&state=&scope=notifications'
25+
)
26+
})
27+
context('when scopes are undefined', function () {
28+
it('should not include scope in the uri', function () {
29+
var authWithoutScopes = new ClientOAuth2({
30+
clientId: config.clientId,
31+
clientSecret: config.clientSecret,
32+
accessTokenUri: config.accessTokenUri,
33+
authorizationUri: config.authorizationUri,
34+
authorizationGrants: ['code'],
35+
redirectUri: config.redirectUri
36+
})
37+
expect(authWithoutScopes.code.getUri()).to.equal(
38+
config.authorizationUri + '?client_id=abc&' +
39+
'redirect_uri=http%3A%2F%2Fexample.com%2Fauth%2Fcallback&' +
40+
'response_type=code&state='
41+
)
42+
})
43+
})
44+
it('should include empty scopes array as an empty string', function () {
45+
var authWithEmptyScopes = new ClientOAuth2({
46+
clientId: config.clientId,
47+
clientSecret: config.clientSecret,
48+
accessTokenUri: config.accessTokenUri,
49+
authorizationUri: config.authorizationUri,
50+
authorizationGrants: ['code'],
51+
redirectUri: config.redirectUri,
52+
scopes: []
53+
})
54+
expect(authWithEmptyScopes.code.getUri()).to.equal(
55+
config.authorizationUri + '?client_id=abc&' +
56+
'redirect_uri=http%3A%2F%2Fexample.com%2Fauth%2Fcallback&' +
57+
'response_type=code&state=&scope='
58+
)
59+
})
60+
it('should include empty scopes string as an empty string', function () {
61+
var authWithEmptyScopes = new ClientOAuth2({
62+
clientId: config.clientId,
63+
clientSecret: config.clientSecret,
64+
accessTokenUri: config.accessTokenUri,
65+
authorizationUri: config.authorizationUri,
66+
authorizationGrants: ['code'],
67+
redirectUri: config.redirectUri,
68+
scopes: ''
69+
})
70+
expect(authWithEmptyScopes.code.getUri()).to.equal(
71+
config.authorizationUri + '?client_id=abc&' +
72+
'redirect_uri=http%3A%2F%2Fexample.com%2Fauth%2Fcallback&' +
73+
'response_type=code&state=&scope='
2574
)
2675
})
2776
context('when authorizationUri contains query parameters', function () {
@@ -38,7 +87,7 @@ describe('code', function () {
3887
expect(authWithParams.code.getUri()).to.equal(
3988
config.authorizationUri + '?bar=qux&client_id=abc&' +
4089
'redirect_uri=http%3A%2F%2Fexample.com%2Fauth%2Fcallback&' +
41-
'scope=notifications&response_type=code&state='
90+
'response_type=code&state=&scope=notifications'
4291
)
4392
})
4493
})

test/credentials.js

Lines changed: 55 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
/* global describe, it */
1+
/* global describe, it, context */
22
var expect = require('chai').expect
33
var config = require('./support/config')
44
var ClientOAuth2 = require('../')
@@ -19,8 +19,62 @@ describe('credentials', function () {
1919
expect(user).to.an.instanceOf(ClientOAuth2.Token)
2020
expect(user.accessToken).to.equal(config.accessToken)
2121
expect(user.tokenType).to.equal('bearer')
22+
expect(user.data.scope).to.equal('notifications')
2223
})
2324
})
25+
context('when scopes are undefined', function () {
26+
it('should not send scopes to an auth server', function () {
27+
var authWithoutScopes = new ClientOAuth2({
28+
clientId: config.clientId,
29+
clientSecret: config.clientSecret,
30+
accessTokenUri: config.accessTokenUri,
31+
authorizationGrants: ['credentials']
32+
})
33+
return authWithoutScopes.credentials.getToken()
34+
.then(function (user) {
35+
expect(user).to.an.instanceOf(ClientOAuth2.Token)
36+
expect(user.accessToken).to.equal(config.accessToken)
37+
expect(user.tokenType).to.equal('bearer')
38+
expect(user.data.scope).to.equal(undefined)
39+
})
40+
})
41+
})
42+
context('when scopes is an empty array', function () {
43+
it('should send empty scope string to an auth server', function () {
44+
var authWithoutScopes = new ClientOAuth2({
45+
clientId: config.clientId,
46+
clientSecret: config.clientSecret,
47+
accessTokenUri: config.accessTokenUri,
48+
authorizationGrants: ['credentials'],
49+
scopes: []
50+
})
51+
return authWithoutScopes.credentials.getToken()
52+
.then(function (user) {
53+
expect(user).to.an.instanceOf(ClientOAuth2.Token)
54+
expect(user.accessToken).to.equal(config.accessToken)
55+
expect(user.tokenType).to.equal('bearer')
56+
expect(user.data.scope).to.equal('')
57+
})
58+
})
59+
})
60+
context('when scopes is an empty string', function () {
61+
it('should send empty scope string to an auth server', function () {
62+
var authWithoutScopes = new ClientOAuth2({
63+
clientId: config.clientId,
64+
clientSecret: config.clientSecret,
65+
accessTokenUri: config.accessTokenUri,
66+
authorizationGrants: ['credentials'],
67+
scopes: ''
68+
})
69+
return authWithoutScopes.credentials.getToken()
70+
.then(function (user) {
71+
expect(user).to.an.instanceOf(ClientOAuth2.Token)
72+
expect(user.accessToken).to.equal(config.accessToken)
73+
expect(user.tokenType).to.equal('bearer')
74+
expect(user.data.scope).to.equal('')
75+
})
76+
})
77+
})
2478

2579
describe('#sign', function () {
2680
it('should be able to sign a standard request object', function () {

test/jwtbearer.js

Lines changed: 55 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
/* global describe, it */
1+
/* global describe, it, context */
22
var expect = require('chai').expect
33
var config = require('./support/config')
44
var ClientOAuth2 = require('../')
@@ -19,8 +19,62 @@ describe('jwt', function () {
1919
expect(user).to.an.instanceOf(ClientOAuth2.Token)
2020
expect(user.accessToken).to.equal(config.accessToken)
2121
expect(user.tokenType).to.equal('bearer')
22+
expect(user.data.scope).to.equal('notifications')
2223
})
2324
})
25+
context('when scopes are undefined', function () {
26+
it('should not send scopes to an auth server', function () {
27+
var scopelessAuth = new ClientOAuth2({
28+
clientId: config.clientId,
29+
clientSecret: config.clientSecret,
30+
accessTokenUri: config.accessTokenUri,
31+
authorizationGrants: ['jwt']
32+
})
33+
return scopelessAuth.jwt.getToken(config.jwt)
34+
.then(function (user) {
35+
expect(user).to.an.instanceOf(ClientOAuth2.Token)
36+
expect(user.accessToken).to.equal(config.accessToken)
37+
expect(user.tokenType).to.equal('bearer')
38+
expect(user.data.scope).to.equal(undefined)
39+
})
40+
})
41+
})
42+
context('when scopes are an empty array', function () {
43+
it('should send empty scope string to an auth server', function () {
44+
var scopelessAuth = new ClientOAuth2({
45+
clientId: config.clientId,
46+
clientSecret: config.clientSecret,
47+
accessTokenUri: config.accessTokenUri,
48+
authorizationGrants: ['jwt'],
49+
scopes: []
50+
})
51+
return scopelessAuth.jwt.getToken(config.jwt)
52+
.then(function (user) {
53+
expect(user).to.an.instanceOf(ClientOAuth2.Token)
54+
expect(user.accessToken).to.equal(config.accessToken)
55+
expect(user.tokenType).to.equal('bearer')
56+
expect(user.data.scope).to.equal('')
57+
})
58+
})
59+
})
60+
context('when scopes are an empty array', function () {
61+
it('should send empty scope string to an auth server', function () {
62+
var scopelessAuth = new ClientOAuth2({
63+
clientId: config.clientId,
64+
clientSecret: config.clientSecret,
65+
accessTokenUri: config.accessTokenUri,
66+
authorizationGrants: ['jwt'],
67+
scopes: ''
68+
})
69+
return scopelessAuth.jwt.getToken(config.jwt)
70+
.then(function (user) {
71+
expect(user).to.an.instanceOf(ClientOAuth2.Token)
72+
expect(user.accessToken).to.equal(config.accessToken)
73+
expect(user.tokenType).to.equal('bearer')
74+
expect(user.data.scope).to.equal('')
75+
})
76+
})
77+
})
2478

2579
describe('#sign', function () {
2680
it('should be able to sign a standard request object', function () {

test/owner.js

Lines changed: 56 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
/* global describe, it */
1+
/* global describe, it, context */
22
var expect = require('chai').expect
33
var config = require('./support/config')
44
var ClientOAuth2 = require('../')
@@ -9,7 +9,7 @@ describe('owner', function () {
99
clientSecret: config.clientSecret,
1010
accessTokenUri: config.accessTokenUri,
1111
authorizationGrants: ['owner'],
12-
scope: 'notifications'
12+
scopes: 'notifications'
1313
})
1414

1515
describe('#getToken', function () {
@@ -19,8 +19,62 @@ describe('owner', function () {
1919
expect(user).to.an.instanceOf(ClientOAuth2.Token)
2020
expect(user.accessToken).to.equal(config.accessToken)
2121
expect(user.tokenType).to.equal('bearer')
22+
expect(user.data.scope).to.equal('notifications')
2223
})
2324
})
25+
context('when scopes are undefined', function () {
26+
it('should not send scope to an auth server', function () {
27+
var scopelessAuth = new ClientOAuth2({
28+
clientId: config.clientId,
29+
clientSecret: config.clientSecret,
30+
accessTokenUri: config.accessTokenUri,
31+
authorizationGrants: ['owner']
32+
})
33+
return scopelessAuth.owner.getToken(config.username, config.password)
34+
.then(function (user) {
35+
expect(user).to.an.instanceOf(ClientOAuth2.Token)
36+
expect(user.accessToken).to.equal(config.accessToken)
37+
expect(user.tokenType).to.equal('bearer')
38+
expect(user.data.scope).to.equal(undefined)
39+
})
40+
})
41+
})
42+
context('when scopes are an empty array', function () {
43+
it('should send empty scope string to an auth server', function () {
44+
var scopelessAuth = new ClientOAuth2({
45+
clientId: config.clientId,
46+
clientSecret: config.clientSecret,
47+
accessTokenUri: config.accessTokenUri,
48+
authorizationGrants: ['owner'],
49+
scopes: []
50+
})
51+
return scopelessAuth.owner.getToken(config.username, config.password)
52+
.then(function (user) {
53+
expect(user).to.an.instanceOf(ClientOAuth2.Token)
54+
expect(user.accessToken).to.equal(config.accessToken)
55+
expect(user.tokenType).to.equal('bearer')
56+
expect(user.data.scope).to.equal('')
57+
})
58+
})
59+
})
60+
context('when scopes are an empty string', function () {
61+
it('should send empty scope string to an auth server', function () {
62+
var scopelessAuth = new ClientOAuth2({
63+
clientId: config.clientId,
64+
clientSecret: config.clientSecret,
65+
accessTokenUri: config.accessTokenUri,
66+
authorizationGrants: ['owner'],
67+
scopes: ''
68+
})
69+
return scopelessAuth.owner.getToken(config.username, config.password)
70+
.then(function (user) {
71+
expect(user).to.an.instanceOf(ClientOAuth2.Token)
72+
expect(user.accessToken).to.equal(config.accessToken)
73+
expect(user.tokenType).to.equal('bearer')
74+
expect(user.data.scope).to.equal('')
75+
})
76+
})
77+
})
2478

2579
describe('#sign', function () {
2680
it('should be able to sign a standard request object', function () {

test/support/server.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ app.post(
5151
access_token: config.accessToken,
5252
refresh_token: config.refreshToken,
5353
token_type: 'bearer',
54-
scope: 'notifications'
54+
scope: req.body.scope
5555
})
5656
}
5757
)

0 commit comments

Comments
 (0)