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