Skip to content

Commit 704d917

Browse files
committed
tests(compliance): added client credential workflow compliance tests
1 parent c6682a6 commit 704d917

File tree

1 file changed

+142
-0
lines changed

1 file changed

+142
-0
lines changed
Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
/**
2+
* 4.4. Client Credentials Grant
3+
*
4+
* The client can request an access token using only its client
5+
* credentials (or other supported means of authentication) when the
6+
* client is requesting access to the protected resources under its
7+
* control, or those of another resource owner that have been previously
8+
* arranged with the authorization server (the method of which is beyond
9+
* the scope of this specification).
10+
*
11+
* The client credentials grant type MUST only be used by confidential
12+
* clients.
13+
*
14+
* @see https://www.rfc-editor.org/rfc/rfc6749#section-4.4
15+
*/
16+
17+
const OAuth2Server = require('../..');
18+
const DB = require('../helpers/db');
19+
const createModel = require('../helpers/model');
20+
const createRequest = require('../helpers/request');
21+
const Response = require('../../lib/response');
22+
23+
require('chai').should();
24+
25+
const db = new DB();
26+
// this user represents requests in the name of an external server
27+
// TODO: we should discuss, if we can make user optional for client credential workflows
28+
// as it's not desired to have an extra fake-user representing a server just to pass validation
29+
const userDoc = { id: 'machine2-123456789', name: 'machine2' };
30+
db.saveUser(userDoc);
31+
32+
const oAuth2Server = new OAuth2Server({
33+
model: {
34+
...createModel(db),
35+
getUserFromClient: async function (_client) {
36+
// in a machine2machine setup we might not have a dedicated "user"
37+
// but we need to return a truthy response to
38+
const client = db.findClient(_client.id, _client.secret);
39+
return client && { ...userDoc };
40+
}
41+
}
42+
});
43+
44+
const clientDoc = db.saveClient({
45+
id: 'client-credential-test-client',
46+
secret: 'client-credential-test-secret',
47+
grants: ['client_credentials']
48+
});
49+
50+
const enabledScope = 'read write';
51+
52+
describe('ClientCredentials Workflow Compliance (4.4)', function () {
53+
describe('Access Token Request (4.4.1)', function () {
54+
/**
55+
* 4.4.2. Access Token Request
56+
*
57+
* The client makes a request to the token endpoint by adding the
58+
* following parameters using the "application/x-www-form-urlencoded"
59+
* format per Appendix B with a character encoding of UTF-8 in the HTTP
60+
* request entity-body:
61+
*
62+
* grant_type
63+
* REQUIRED. Value MUST be set to "client_credentials".
64+
*
65+
* scope
66+
* OPTIONAL. The scope of the access request as described by
67+
* Section 3.3.
68+
*
69+
* The client MUST authenticate with the authorization server as
70+
* described in Section 3.2.1.
71+
*/
72+
it('authenticates the client with valid credentials', async function () {
73+
const response = new Response();
74+
const request = createRequest({
75+
body: {
76+
grant_type: 'client_credentials',
77+
scope: enabledScope
78+
},
79+
headers: {
80+
'authorization': 'Basic ' + Buffer.from(clientDoc.id + ':' + clientDoc.secret).toString('base64'),
81+
'content-type': 'application/x-www-form-urlencoded'
82+
},
83+
method: 'POST',
84+
});
85+
86+
const token = await oAuth2Server.token(request, response);
87+
88+
response.status.should.equal(200);
89+
response.headers.should.deep.equal( { 'cache-control': 'no-store', pragma: 'no-cache' });
90+
response.body.token_type.should.equal('Bearer');
91+
response.body.access_token.should.equal(token.accessToken);
92+
response.body.expires_in.should.be.a('number');
93+
response.body.scope.should.equal(enabledScope);
94+
('refresh_token' in response.body).should.equal(false);
95+
96+
token.accessToken.should.be.a('string');
97+
token.accessTokenExpiresAt.should.be.a('date');
98+
('refreshToken' in token).should.equal(false);
99+
('refreshTokenExpiresAt' in token).should.equal(false);
100+
token.scope.should.equal(enabledScope);
101+
102+
db.accessTokens.has(token.accessToken).should.equal(true);
103+
db.refreshTokens.has(token.refreshToken).should.equal(false);
104+
});
105+
106+
/**
107+
* 7. Accessing Protected Resources
108+
*
109+
* The client accesses protected resources by presenting the access
110+
* token to the resource server. The resource server MUST validate the
111+
* access token and ensure that it has not expired and that its scope
112+
* covers the requested resource. The methods used by the resource
113+
* server to validate the access token (as well as any error responses)
114+
* are beyond the scope of this specification but generally involve an
115+
* interaction or coordination between the resource server and the
116+
* authorization server.
117+
*/
118+
it('enables an authenticated request using the access token', async function () {
119+
const [accessToken] = [...db.accessTokens.entries()][0];
120+
const response = new Response();
121+
const request = createRequest({
122+
query: {},
123+
headers: {
124+
'authorization': `Bearer ${accessToken}`
125+
},
126+
method: 'GET',
127+
});
128+
129+
const token = await oAuth2Server.authenticate(request, response);
130+
token.accessToken.should.equal(accessToken);
131+
token.user.should.deep.equal(userDoc);
132+
token.client.should.deep.equal(clientDoc);
133+
token.scope.should.equal(enabledScope);
134+
135+
response.status.should.equal(200);
136+
// there should be no information in the response as it
137+
// should only add information, if permission is denied
138+
response.body.should.deep.equal({});
139+
response.headers.should.deep.equal({});
140+
});
141+
});
142+
});

0 commit comments

Comments
 (0)