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