Source file
src/github.com/ory/fosite/client_authentication_test.go
1
21
22 package fosite_test
23
24 import (
25 "context"
26 "crypto/ecdsa"
27 "crypto/rsa"
28 "encoding/base64"
29 "encoding/json"
30 "fmt"
31 "net/http"
32 "net/http/httptest"
33 "net/url"
34 "testing"
35 "time"
36
37 "github.com/ory/fosite/token/jwt"
38 "github.com/pkg/errors"
39 "github.com/stretchr/testify/assert"
40 "github.com/stretchr/testify/require"
41 jose "gopkg.in/square/go-jose.v2"
42
43 . "github.com/ory/fosite"
44 "github.com/ory/fosite/internal"
45 "github.com/ory/fosite/storage"
46 )
47
48 func mustGenerateRSAAssertion(t *testing.T, claims jwt.MapClaims, key *rsa.PrivateKey, kid string) string {
49 token := jwt.NewWithClaims(jose.RS256, claims)
50 token.Header["kid"] = kid
51 tokenString, err := token.SignedString(key)
52 require.NoError(t, err)
53 return tokenString
54 }
55
56 func mustGenerateECDSAAssertion(t *testing.T, claims jwt.MapClaims, key *ecdsa.PrivateKey, kid string) string {
57 token := jwt.NewWithClaims(jose.ES256, claims)
58 token.Header["kid"] = kid
59 tokenString, err := token.SignedString(key)
60 require.NoError(t, err)
61 return tokenString
62 }
63
64 func mustGenerateHSAssertion(t *testing.T, claims jwt.MapClaims, key *rsa.PrivateKey, kid string) string {
65 token := jwt.NewWithClaims(jose.HS256, claims)
66 tokenString, err := token.SignedString([]byte("aaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbcccccccccccccccccccccddddddddddddddddddddddd"))
67 require.NoError(t, err)
68 return tokenString
69 }
70
71 func mustGenerateNoneAssertion(t *testing.T, claims jwt.MapClaims, key *rsa.PrivateKey, kid string) string {
72 token := jwt.NewWithClaims(jwt.SigningMethodNone, claims)
73 tokenString, err := token.SignedString(jwt.UnsafeAllowNoneSignatureType)
74 require.NoError(t, err)
75 return tokenString
76 }
77
78
79 func clientBasicAuthHeader(clientID, clientSecret string) http.Header {
80 creds := url.QueryEscape(clientID) + ":" + url.QueryEscape(clientSecret)
81 return http.Header{
82 "Authorization": {
83 "Basic " + base64.StdEncoding.EncodeToString([]byte(creds)),
84 },
85 }
86 }
87
88 func TestAuthenticateClient(t *testing.T) {
89 const at = "urn:ietf:params:oauth:client-assertion-type:jwt-bearer"
90
91 hasher := &BCrypt{WorkFactor: 6}
92 f := &Fosite{
93 JWKSFetcherStrategy: NewDefaultJWKSFetcherStrategy(),
94 Store: storage.NewMemoryStore(),
95 Hasher: hasher,
96 TokenURL: "token-url",
97 }
98
99 barSecret, err := hasher.Hash(context.TODO(), []byte("bar"))
100 require.NoError(t, err)
101
102
103 complexSecretRaw := "foo %66%6F%6F@$<§!✓"
104 complexSecret, err := hasher.Hash(context.TODO(), []byte(complexSecretRaw))
105 require.NoError(t, err)
106
107 rsaKey := internal.MustRSAKey()
108 rsaJwks := &jose.JSONWebKeySet{
109 Keys: []jose.JSONWebKey{
110 {
111 KeyID: "kid-foo",
112 Use: "sig",
113 Key: &rsaKey.PublicKey,
114 },
115 },
116 }
117
118 ecdsaKey := internal.MustECDSAKey()
119 ecdsaJwks := &jose.JSONWebKeySet{
120 Keys: []jose.JSONWebKey{
121 {
122 KeyID: "kid-foo",
123 Use: "sig",
124 Key: &ecdsaKey.PublicKey,
125 },
126 },
127 }
128
129 var h http.HandlerFunc
130 h = func(w http.ResponseWriter, r *http.Request) {
131 require.NoError(t, json.NewEncoder(w).Encode(rsaJwks))
132 }
133 ts := httptest.NewServer(h)
134 defer ts.Close()
135
136 for k, tc := range []struct {
137 d string
138 client *DefaultOpenIDConnectClient
139 assertionType string
140 assertion string
141 r *http.Request
142 form url.Values
143 expectErr error
144 }{
145 {
146 d: "should fail because authentication can not be determined",
147 client: &DefaultOpenIDConnectClient{DefaultClient: &DefaultClient{ID: "foo"}, TokenEndpointAuthMethod: "client_secret_basic"},
148 form: url.Values{},
149 r: new(http.Request),
150 expectErr: ErrInvalidRequest,
151 },
152 {
153 d: "should fail because client does not exist",
154 client: &DefaultOpenIDConnectClient{DefaultClient: &DefaultClient{ID: "foo", Public: true}, TokenEndpointAuthMethod: "none"},
155 form: url.Values{"client_id": []string{"bar"}},
156 r: new(http.Request),
157 expectErr: ErrInvalidClient,
158 },
159 {
160 d: "should pass because client is public and authentication requirements are met",
161 client: &DefaultOpenIDConnectClient{DefaultClient: &DefaultClient{ID: "foo", Public: true}, TokenEndpointAuthMethod: "none"},
162 form: url.Values{"client_id": []string{"foo"}},
163 r: new(http.Request),
164 },
165 {
166 d: "should pass because client is public and client secret is empty in query param",
167 client: &DefaultOpenIDConnectClient{DefaultClient: &DefaultClient{ID: "foo", Public: true}, TokenEndpointAuthMethod: "none"},
168 form: url.Values{"client_id": []string{"foo"}, "client_secret": []string{""}},
169 r: new(http.Request),
170 },
171 {
172 d: "should pass because client is public and client secret is empty in basic auth header",
173 client: &DefaultOpenIDConnectClient{DefaultClient: &DefaultClient{ID: "foo", Public: true}, TokenEndpointAuthMethod: "none"},
174 form: url.Values{},
175 r: &http.Request{Header: clientBasicAuthHeader("foo", "")},
176 },
177 {
178 d: "should fail because client requires basic auth and client secret is empty in basic auth header",
179 client: &DefaultOpenIDConnectClient{DefaultClient: &DefaultClient{ID: "foo", Public: true}, TokenEndpointAuthMethod: "client_secret_basic"},
180 form: url.Values{},
181 r: &http.Request{Header: clientBasicAuthHeader("foo", "")},
182 expectErr: ErrInvalidClient,
183 },
184 {
185 d: "should pass with client credentials containing special characters",
186 client: &DefaultOpenIDConnectClient{DefaultClient: &DefaultClient{ID: "!foo%20bar", Secret: complexSecret}, TokenEndpointAuthMethod: "client_secret_post"},
187 form: url.Values{"client_id": []string{"!foo%20bar"}, "client_secret": []string{complexSecretRaw}},
188 r: new(http.Request),
189 },
190 {
191 d: "should pass with client credentials containing special characters via basic auth",
192 client: &DefaultOpenIDConnectClient{DefaultClient: &DefaultClient{ID: "foo — bar! +<&>*", Secret: complexSecret}, TokenEndpointAuthMethod: "client_secret_basic"},
193 form: url.Values{},
194 r: &http.Request{Header: clientBasicAuthHeader("foo — bar! +<&>*", complexSecretRaw)},
195 },
196 {
197 d: "should fail because auth method is not none",
198 client: &DefaultOpenIDConnectClient{DefaultClient: &DefaultClient{ID: "foo", Public: true}, TokenEndpointAuthMethod: "client_secret_basic"},
199 form: url.Values{"client_id": []string{"foo"}},
200 r: new(http.Request),
201 expectErr: ErrInvalidClient,
202 },
203 {
204 d: "should pass because client is confidential and id and secret match in post body",
205 client: &DefaultOpenIDConnectClient{DefaultClient: &DefaultClient{ID: "foo", Secret: []byte("invalid_hash"), RotatedSecrets: [][]byte{barSecret}}, TokenEndpointAuthMethod: "client_secret_post"},
206 form: url.Values{"client_id": []string{"foo"}, "client_secret": []string{"bar"}},
207 r: new(http.Request),
208 },
209 {
210 d: "should pass because client is confidential and id and rotated secret match in post body",
211 client: &DefaultOpenIDConnectClient{DefaultClient: &DefaultClient{ID: "foo", Secret: barSecret}, TokenEndpointAuthMethod: "client_secret_post"},
212 form: url.Values{"client_id": []string{"foo"}, "client_secret": []string{"bar"}},
213 r: new(http.Request),
214 },
215 {
216 d: "should fail because client is confidential and secret does not match in post body",
217 client: &DefaultOpenIDConnectClient{DefaultClient: &DefaultClient{ID: "foo", Secret: barSecret}, TokenEndpointAuthMethod: "client_secret_post"},
218 form: url.Values{"client_id": []string{"foo"}, "client_secret": []string{"baz"}},
219 r: new(http.Request),
220 expectErr: ErrInvalidClient,
221 },
222 {
223 d: "should fail because client is confidential and id does not exist in post body",
224 client: &DefaultOpenIDConnectClient{DefaultClient: &DefaultClient{ID: "bar", Secret: barSecret}, TokenEndpointAuthMethod: "client_secret_post"},
225 form: url.Values{"client_id": []string{"foo"}, "client_secret": []string{"bar"}},
226 r: new(http.Request),
227 expectErr: ErrInvalidClient,
228 },
229 {
230 d: "should pass because client is confidential and id and secret match in header",
231 client: &DefaultOpenIDConnectClient{DefaultClient: &DefaultClient{ID: "foo", Secret: barSecret}, TokenEndpointAuthMethod: "client_secret_basic"},
232 form: url.Values{},
233 r: &http.Request{Header: clientBasicAuthHeader("foo", "bar")},
234 },
235 {
236 d: "should pass because client is confidential and id and rotated secret match in header",
237 client: &DefaultOpenIDConnectClient{DefaultClient: &DefaultClient{ID: "foo", Secret: []byte("invalid_hash"), RotatedSecrets: [][]byte{barSecret}}, TokenEndpointAuthMethod: "client_secret_basic"},
238 form: url.Values{},
239 r: &http.Request{Header: clientBasicAuthHeader("foo", "bar")},
240 },
241 {
242 d: "should pass because client is confidential and id and rotated secret match in header",
243 client: &DefaultOpenIDConnectClient{DefaultClient: &DefaultClient{ID: "foo", Secret: []byte("invalid_hash"), RotatedSecrets: [][]byte{[]byte("invalid"), barSecret}}, TokenEndpointAuthMethod: "client_secret_basic"},
244 form: url.Values{},
245 r: &http.Request{Header: clientBasicAuthHeader("foo", "bar")},
246 },
247 {
248 d: "should fail because auth method is not client_secret_basic",
249 client: &DefaultOpenIDConnectClient{DefaultClient: &DefaultClient{ID: "foo", Secret: barSecret}, TokenEndpointAuthMethod: "client_secret_post"},
250 form: url.Values{},
251 r: &http.Request{Header: clientBasicAuthHeader("foo", "bar")},
252 expectErr: ErrInvalidClient,
253 },
254 {
255 d: "should fail because client is confidential and secret does not match in header",
256 client: &DefaultOpenIDConnectClient{DefaultClient: &DefaultClient{ID: "foo", Secret: barSecret}, TokenEndpointAuthMethod: "client_secret_basic"},
257 form: url.Values{},
258 r: &http.Request{Header: clientBasicAuthHeader("foo", "baz")},
259 expectErr: ErrInvalidClient,
260 },
261 {
262 d: "should fail because client is confidential and neither secret nor rotated does match in header",
263 client: &DefaultOpenIDConnectClient{DefaultClient: &DefaultClient{ID: "foo", Secret: barSecret, RotatedSecrets: [][]byte{barSecret}}, TokenEndpointAuthMethod: "client_secret_basic"},
264 form: url.Values{},
265 r: &http.Request{Header: clientBasicAuthHeader("foo", "baz")},
266 expectErr: ErrInvalidClient,
267 },
268 {
269 d: "should fail because client id is not encoded using application/x-www-form-urlencoded",
270 client: &DefaultOpenIDConnectClient{DefaultClient: &DefaultClient{ID: "foo", Secret: barSecret}, TokenEndpointAuthMethod: "client_secret_basic"},
271 form: url.Values{},
272 r: &http.Request{Header: http.Header{"Authorization": {"Basic " + base64.StdEncoding.EncodeToString([]byte("%%%%%%:foo"))}}},
273 expectErr: ErrInvalidRequest,
274 },
275 {
276 d: "should fail because client secret is not encoded using application/x-www-form-urlencoded",
277 client: &DefaultOpenIDConnectClient{DefaultClient: &DefaultClient{ID: "foo", Secret: barSecret}, TokenEndpointAuthMethod: "client_secret_basic"},
278 form: url.Values{},
279 r: &http.Request{Header: http.Header{"Authorization": {"Basic " + base64.StdEncoding.EncodeToString([]byte("foo:%%%%%%%"))}}},
280 expectErr: ErrInvalidRequest,
281 },
282 {
283 d: "should fail because client is confidential and id does not exist in header",
284 client: &DefaultOpenIDConnectClient{DefaultClient: &DefaultClient{ID: "bar", Secret: barSecret}, TokenEndpointAuthMethod: "client_secret_basic"},
285 form: url.Values{},
286 r: &http.Request{Header: http.Header{"Authorization": {"Basic " + base64.StdEncoding.EncodeToString([]byte("foo:bar"))}}},
287 expectErr: ErrInvalidClient,
288 },
289 {
290 d: "should fail because client_assertion but client_assertion is missing",
291 client: &DefaultOpenIDConnectClient{DefaultClient: &DefaultClient{ID: "foo", Secret: barSecret}, TokenEndpointAuthMethod: "private_key_jwt"},
292 form: url.Values{"client_id": []string{"foo"}, "client_assertion_type": []string{at}},
293 r: new(http.Request),
294 expectErr: ErrInvalidRequest,
295 },
296 {
297 d: "should fail because client_assertion_type is unknown",
298 client: &DefaultOpenIDConnectClient{DefaultClient: &DefaultClient{ID: "foo", Secret: barSecret}, TokenEndpointAuthMethod: "private_key_jwt"},
299 form: url.Values{"client_id": []string{"foo"}, "client_assertion_type": []string{"foobar"}},
300 r: new(http.Request),
301 expectErr: ErrInvalidRequest,
302 },
303 {
304 d: "should pass with proper RSA assertion when JWKs are set within the client and client_id is not set in the request",
305 client: &DefaultOpenIDConnectClient{DefaultClient: &DefaultClient{ID: "bar", Secret: barSecret}, JSONWebKeys: rsaJwks, TokenEndpointAuthMethod: "private_key_jwt"},
306 form: url.Values{"client_assertion": {mustGenerateRSAAssertion(t, jwt.MapClaims{
307 "sub": "bar",
308 "exp": time.Now().Add(time.Hour).Unix(),
309 "iss": "bar",
310 "jti": "12345",
311 "aud": "token-url",
312 }, rsaKey, "kid-foo")}, "client_assertion_type": []string{at}},
313 r: new(http.Request),
314 },
315 {
316 d: "should pass with proper ECDSA assertion when JWKs are set within the client and client_id is not set in the request",
317 client: &DefaultOpenIDConnectClient{DefaultClient: &DefaultClient{ID: "bar", Secret: barSecret}, JSONWebKeys: ecdsaJwks, TokenEndpointAuthMethod: "private_key_jwt", TokenEndpointAuthSigningAlgorithm: "ES256"},
318 form: url.Values{"client_assertion": {mustGenerateECDSAAssertion(t, jwt.MapClaims{
319 "sub": "bar",
320 "exp": time.Now().Add(time.Hour).Unix(),
321 "iss": "bar",
322 "jti": "12345",
323 "aud": "token-url",
324 }, ecdsaKey, "kid-foo")}, "client_assertion_type": []string{at}},
325 r: new(http.Request),
326 },
327 {
328 d: "should fail because RSA assertion is used, but ECDSA assertion is required",
329 client: &DefaultOpenIDConnectClient{DefaultClient: &DefaultClient{ID: "bar", Secret: barSecret}, JSONWebKeys: ecdsaJwks, TokenEndpointAuthMethod: "private_key_jwt", TokenEndpointAuthSigningAlgorithm: "ES256"},
330 form: url.Values{"client_assertion": {mustGenerateRSAAssertion(t, jwt.MapClaims{
331 "sub": "bar",
332 "exp": time.Now().Add(time.Hour).Unix(),
333 "iss": "bar",
334 "jti": "12345",
335 "aud": "token-url",
336 }, rsaKey, "kid-foo")}, "client_assertion_type": []string{at}},
337 r: new(http.Request),
338 expectErr: ErrInvalidClient,
339 },
340 {
341 d: "should fail because token auth method is not private_key_jwt, but client_secret_jwt",
342 client: &DefaultOpenIDConnectClient{DefaultClient: &DefaultClient{ID: "bar", Secret: barSecret}, JSONWebKeys: rsaJwks, TokenEndpointAuthMethod: "client_secret_jwt"},
343 form: url.Values{"client_assertion": {mustGenerateRSAAssertion(t, jwt.MapClaims{
344 "sub": "bar",
345 "exp": time.Now().Add(time.Hour).Unix(),
346 "iss": "bar",
347 "jti": "12345",
348 "aud": "token-url",
349 }, rsaKey, "kid-foo")}, "client_assertion_type": []string{at}},
350 r: new(http.Request),
351 expectErr: ErrInvalidClient,
352 },
353 {
354 d: "should fail because token auth method is not private_key_jwt, but none",
355 client: &DefaultOpenIDConnectClient{DefaultClient: &DefaultClient{ID: "bar", Secret: barSecret}, JSONWebKeys: rsaJwks, TokenEndpointAuthMethod: "none"},
356 form: url.Values{"client_assertion": {mustGenerateRSAAssertion(t, jwt.MapClaims{
357 "sub": "bar",
358 "exp": time.Now().Add(time.Hour).Unix(),
359 "iss": "bar",
360 "jti": "12345",
361 "aud": "token-url",
362 }, rsaKey, "kid-foo")}, "client_assertion_type": []string{at}},
363 r: new(http.Request),
364 expectErr: ErrInvalidClient,
365 },
366 {
367 d: "should fail because token auth method is not private_key_jwt, but client_secret_post",
368 client: &DefaultOpenIDConnectClient{DefaultClient: &DefaultClient{ID: "bar", Secret: barSecret}, JSONWebKeys: rsaJwks, TokenEndpointAuthMethod: "client_secret_post"},
369 form: url.Values{"client_assertion": {mustGenerateRSAAssertion(t, jwt.MapClaims{
370 "sub": "bar",
371 "exp": time.Now().Add(time.Hour).Unix(),
372 "iss": "bar",
373 "jti": "12345",
374 "aud": "token-url",
375 }, rsaKey, "kid-foo")}, "client_assertion_type": []string{at}},
376 r: new(http.Request),
377 expectErr: ErrInvalidClient,
378 },
379 {
380 d: "should fail because token auth method is not private_key_jwt, but client_secret_basic",
381 client: &DefaultOpenIDConnectClient{DefaultClient: &DefaultClient{ID: "bar", Secret: barSecret}, JSONWebKeys: rsaJwks, TokenEndpointAuthMethod: "client_secret_basic"},
382 form: url.Values{"client_assertion": {mustGenerateRSAAssertion(t, jwt.MapClaims{
383 "sub": "bar",
384 "exp": time.Now().Add(time.Hour).Unix(),
385 "iss": "bar",
386 "jti": "12345",
387 "aud": "token-url",
388 }, rsaKey, "kid-foo")}, "client_assertion_type": []string{at}},
389 r: new(http.Request),
390 expectErr: ErrInvalidClient,
391 },
392 {
393 d: "should fail because token auth method is not private_key_jwt, but foobar",
394 client: &DefaultOpenIDConnectClient{DefaultClient: &DefaultClient{ID: "bar", Secret: barSecret}, JSONWebKeys: rsaJwks, TokenEndpointAuthMethod: "foobar"},
395 form: url.Values{"client_assertion": {mustGenerateRSAAssertion(t, jwt.MapClaims{
396 "sub": "bar",
397 "exp": time.Now().Add(time.Hour).Unix(),
398 "iss": "bar",
399 "jti": "12345",
400 "aud": "token-url",
401 }, rsaKey, "kid-foo")}, "client_assertion_type": []string{at}},
402 r: new(http.Request),
403 expectErr: ErrInvalidClient,
404 },
405 {
406 d: "should pass with proper assertion when JWKs are set within the client and client_id is not set in the request (aud is array)",
407 client: &DefaultOpenIDConnectClient{DefaultClient: &DefaultClient{ID: "bar", Secret: barSecret}, JSONWebKeys: rsaJwks, TokenEndpointAuthMethod: "private_key_jwt"},
408 form: url.Values{"client_assertion": {mustGenerateRSAAssertion(t, jwt.MapClaims{
409 "sub": "bar",
410 "exp": time.Now().Add(time.Hour).Unix(),
411 "iss": "bar",
412 "jti": "12345",
413 "aud": []string{"token-url-2", "token-url"},
414 }, rsaKey, "kid-foo")}, "client_assertion_type": []string{at}},
415 r: new(http.Request),
416 },
417 {
418 d: "should fail because audience (array) does not match token url",
419 client: &DefaultOpenIDConnectClient{DefaultClient: &DefaultClient{ID: "bar", Secret: barSecret}, JSONWebKeys: rsaJwks, TokenEndpointAuthMethod: "private_key_jwt"},
420 form: url.Values{"client_assertion": {mustGenerateRSAAssertion(t, jwt.MapClaims{
421 "sub": "bar",
422 "exp": time.Now().Add(time.Hour).Unix(),
423 "iss": "bar",
424 "jti": "12345",
425 "aud": []string{"token-url-1", "token-url-2"},
426 }, rsaKey, "kid-foo")}, "client_assertion_type": []string{at}},
427 r: new(http.Request),
428 expectErr: ErrInvalidClient,
429 },
430 {
431 d: "should pass with proper assertion when JWKs are set within the client",
432 client: &DefaultOpenIDConnectClient{DefaultClient: &DefaultClient{ID: "bar", Secret: barSecret}, JSONWebKeys: rsaJwks, TokenEndpointAuthMethod: "private_key_jwt"},
433 form: url.Values{"client_id": []string{"bar"}, "client_assertion": {mustGenerateRSAAssertion(t, jwt.MapClaims{
434 "sub": "bar",
435 "exp": time.Now().Add(time.Hour).Unix(),
436 "iss": "bar",
437 "jti": "12345",
438 "aud": "token-url",
439 }, rsaKey, "kid-foo")}, "client_assertion_type": []string{at}},
440 r: new(http.Request),
441 },
442 {
443 d: "should fail because JWT algorithm is HS256",
444 client: &DefaultOpenIDConnectClient{DefaultClient: &DefaultClient{ID: "bar", Secret: barSecret}, JSONWebKeys: rsaJwks, TokenEndpointAuthMethod: "private_key_jwt"},
445 form: url.Values{"client_id": []string{"bar"}, "client_assertion": {mustGenerateHSAssertion(t, jwt.MapClaims{
446 "sub": "bar",
447 "exp": time.Now().Add(time.Hour).Unix(),
448 "iss": "bar",
449 "jti": "12345",
450 "aud": "token-url",
451 }, rsaKey, "kid-foo")}, "client_assertion_type": []string{at}},
452 r: new(http.Request),
453 expectErr: ErrInvalidClient,
454 },
455 {
456 d: "should fail because JWT algorithm is none",
457 client: &DefaultOpenIDConnectClient{DefaultClient: &DefaultClient{ID: "bar", Secret: barSecret}, JSONWebKeys: rsaJwks, TokenEndpointAuthMethod: "private_key_jwt"},
458 form: url.Values{"client_id": []string{"bar"}, "client_assertion": {mustGenerateNoneAssertion(t, jwt.MapClaims{
459 "sub": "bar",
460 "exp": time.Now().Add(time.Hour).Unix(),
461 "iss": "bar",
462 "jti": "12345",
463 "aud": "token-url",
464 }, rsaKey, "kid-foo")}, "client_assertion_type": []string{at}},
465 r: new(http.Request),
466 expectErr: ErrInvalidClient,
467 },
468 {
469 d: "should pass with proper assertion when JWKs URI is set",
470 client: &DefaultOpenIDConnectClient{DefaultClient: &DefaultClient{ID: "bar", Secret: barSecret}, JSONWebKeysURI: ts.URL, TokenEndpointAuthMethod: "private_key_jwt"},
471 form: url.Values{"client_id": []string{"bar"}, "client_assertion": {mustGenerateRSAAssertion(t, jwt.MapClaims{
472 "sub": "bar",
473 "exp": time.Now().Add(time.Hour).Unix(),
474 "iss": "bar",
475 "jti": "12345",
476 "aud": "token-url",
477 }, rsaKey, "kid-foo")}, "client_assertion_type": []string{at}},
478 r: new(http.Request),
479 },
480 {
481 d: "should fail because client_assertion sub does not match client",
482 client: &DefaultOpenIDConnectClient{DefaultClient: &DefaultClient{ID: "bar", Secret: barSecret}, JSONWebKeys: rsaJwks, TokenEndpointAuthMethod: "private_key_jwt"},
483 form: url.Values{"client_id": []string{"bar"}, "client_assertion": {mustGenerateRSAAssertion(t, jwt.MapClaims{
484 "sub": "not-bar",
485 "exp": time.Now().Add(time.Hour).Unix(),
486 "iss": "bar",
487 "jti": "12345",
488 "aud": "token-url",
489 }, rsaKey, "kid-foo")}, "client_assertion_type": []string{at}},
490 r: new(http.Request),
491 expectErr: ErrInvalidClient,
492 },
493 {
494 d: "should fail because client_assertion iss does not match client",
495 client: &DefaultOpenIDConnectClient{DefaultClient: &DefaultClient{ID: "bar", Secret: barSecret}, JSONWebKeys: rsaJwks, TokenEndpointAuthMethod: "private_key_jwt"},
496 form: url.Values{"client_id": []string{"bar"}, "client_assertion": {mustGenerateRSAAssertion(t, jwt.MapClaims{
497 "sub": "bar",
498 "exp": time.Now().Add(time.Hour).Unix(),
499 "iss": "not-bar",
500 "jti": "12345",
501 "aud": "token-url",
502 }, rsaKey, "kid-foo")}, "client_assertion_type": []string{at}},
503 r: new(http.Request),
504 expectErr: ErrInvalidClient,
505 },
506 {
507 d: "should fail because client_assertion jti is not set",
508 client: &DefaultOpenIDConnectClient{DefaultClient: &DefaultClient{ID: "bar", Secret: barSecret}, JSONWebKeys: rsaJwks, TokenEndpointAuthMethod: "private_key_jwt"},
509 form: url.Values{"client_id": []string{"bar"}, "client_assertion": {mustGenerateRSAAssertion(t, jwt.MapClaims{
510 "sub": "bar",
511 "exp": time.Now().Add(time.Hour).Unix(),
512 "iss": "bar",
513 "aud": "token-url",
514 }, rsaKey, "kid-foo")}, "client_assertion_type": []string{at}},
515 r: new(http.Request),
516 expectErr: ErrInvalidClient,
517 },
518 {
519 d: "should fail because client_assertion aud is not set",
520 client: &DefaultOpenIDConnectClient{DefaultClient: &DefaultClient{ID: "bar", Secret: barSecret}, JSONWebKeys: rsaJwks, TokenEndpointAuthMethod: "private_key_jwt"},
521 form: url.Values{"client_id": []string{"bar"}, "client_assertion": {mustGenerateRSAAssertion(t, jwt.MapClaims{
522 "sub": "bar",
523 "exp": time.Now().Add(time.Hour).Unix(),
524 "iss": "bar",
525 "jti": "12345",
526 "aud": "not-token-url",
527 }, rsaKey, "kid-foo")}, "client_assertion_type": []string{at}},
528 r: new(http.Request),
529 expectErr: ErrInvalidClient,
530 },
531 } {
532 t.Run(fmt.Sprintf("case=%d/description=%s", k, tc.d), func(t *testing.T) {
533 store := storage.NewMemoryStore()
534 store.Clients[tc.client.ID] = tc.client
535 f.Store = store
536
537 c, err := f.AuthenticateClient(nil, tc.r, tc.form)
538 if tc.expectErr != nil {
539 require.EqualError(t, err, tc.expectErr.Error())
540 return
541 }
542
543 if err != nil {
544 var validationError *jwt.ValidationError
545 var rfcError *RFC6749Error
546 if errors.As(err, &validationError) {
547 t.Logf("Error is: %s", validationError.Inner)
548 } else if errors.As(err, &rfcError) {
549 t.Logf("DebugField is: %s", rfcError.DebugField)
550 t.Logf("HintField is: %s", rfcError.HintField)
551 }
552 }
553 require.NoError(t, err)
554 assert.EqualValues(t, tc.client, c)
555 })
556 }
557 }
558
559 func TestAuthenticateClientTwice(t *testing.T) {
560 const at = "urn:ietf:params:oauth:client-assertion-type:jwt-bearer"
561
562 key := internal.MustRSAKey()
563 client := &DefaultOpenIDConnectClient{
564 DefaultClient: &DefaultClient{
565 ID: "bar",
566 Secret: []byte("secret"),
567 },
568 JSONWebKeys: &jose.JSONWebKeySet{
569 Keys: []jose.JSONWebKey{
570 {
571 KeyID: "kid-foo",
572 Use: "sig",
573 Key: &key.PublicKey,
574 },
575 },
576 },
577 TokenEndpointAuthMethod: "private_key_jwt",
578 }
579 store := storage.NewMemoryStore()
580 store.Clients[client.ID] = client
581
582 hasher := &BCrypt{WorkFactor: 6}
583 f := &Fosite{
584 JWKSFetcherStrategy: NewDefaultJWKSFetcherStrategy(),
585 Store: store,
586 Hasher: hasher,
587 TokenURL: "token-url",
588 }
589
590 formValues := url.Values{"client_id": []string{"bar"}, "client_assertion": {mustGenerateRSAAssertion(t, jwt.MapClaims{
591 "sub": "bar",
592 "exp": time.Now().Add(time.Hour).Unix(),
593 "iss": "bar",
594 "jti": "12345",
595 "aud": "token-url",
596 }, key, "kid-foo")}, "client_assertion_type": []string{at}}
597
598 c, err := f.AuthenticateClient(nil, new(http.Request), formValues)
599 require.NoError(t, err, "%#v", err)
600 assert.Equal(t, client, c)
601
602
603 c, err = f.AuthenticateClient(nil, new(http.Request), formValues)
604 require.Error(t, err)
605 assert.EqualError(t, err, ErrJTIKnown.Error())
606 assert.Nil(t, c)
607 }
608
View as plain text