@@ -241,7 +241,8 @@ describe('OAuthProvider', () => {
241
241
tokenEndpoint : '/oauth/token' ,
242
242
clientRegistrationEndpoint : '/oauth/register' ,
243
243
scopesSupported : [ 'read' , 'write' , 'profile' ] ,
244
- accessTokenTTL : 3600
244
+ accessTokenTTL : 3600 ,
245
+ allowImplicitFlow : true // Enable implicit flow for tests
245
246
} ) ;
246
247
} ) ;
247
248
@@ -264,9 +265,32 @@ describe('OAuthProvider', () => {
264
265
expect ( metadata . registration_endpoint ) . toBe ( 'https://example.com/oauth/register' ) ;
265
266
expect ( metadata . scopes_supported ) . toEqual ( [ 'read' , 'write' , 'profile' ] ) ;
266
267
expect ( metadata . response_types_supported ) . toContain ( 'code' ) ;
268
+ expect ( metadata . response_types_supported ) . toContain ( 'token' ) ; // Implicit flow enabled
267
269
expect ( metadata . grant_types_supported ) . toContain ( 'authorization_code' ) ;
268
270
expect ( metadata . code_challenge_methods_supported ) . toContain ( 'S256' ) ;
269
271
} ) ;
272
+
273
+ it ( 'should not include token response type when implicit flow is disabled' , async ( ) => {
274
+ // Create a provider with implicit flow disabled
275
+ const providerWithoutImplicit = new OAuthProvider ( {
276
+ apiRoute : [ '/api/' ] ,
277
+ apiHandler : TestApiHandler ,
278
+ defaultHandler : testDefaultHandler ,
279
+ authorizeEndpoint : '/authorize' ,
280
+ tokenEndpoint : '/oauth/token' ,
281
+ scopesSupported : [ 'read' , 'write' ] ,
282
+ allowImplicitFlow : false // Explicitly disable
283
+ } ) ;
284
+
285
+ const request = createMockRequest ( 'https://example.com/.well-known/oauth-authorization-server' ) ;
286
+ const response = await providerWithoutImplicit . fetch ( request , mockEnv , mockCtx ) ;
287
+
288
+ expect ( response . status ) . toBe ( 200 ) ;
289
+
290
+ const metadata = await response . json ( ) ;
291
+ expect ( metadata . response_types_supported ) . toContain ( 'code' ) ;
292
+ expect ( metadata . response_types_supported ) . not . toContain ( 'token' ) ;
293
+ } ) ;
270
294
} ) ;
271
295
272
296
describe ( 'Client Registration' , ( ) => {
@@ -394,6 +418,180 @@ describe('OAuthProvider', () => {
394
418
expect ( grants . keys . length ) . toBe ( 1 ) ;
395
419
} ) ;
396
420
421
+ // Add more tests for auth code flow...
422
+ } ) ;
423
+
424
+ describe ( 'Implicit Flow' , ( ) => {
425
+ let clientId : string ;
426
+ let redirectUri : string ;
427
+
428
+ // Helper to create a test client before authorization tests
429
+ async function createPublicClient ( ) {
430
+ const clientData = {
431
+ redirect_uris : [ 'https://spa-client.example.com/callback' ] ,
432
+ client_name : 'SPA Test Client' ,
433
+ token_endpoint_auth_method : 'none' // Public client
434
+ } ;
435
+
436
+ const request = createMockRequest (
437
+ 'https://example.com/oauth/register' ,
438
+ 'POST' ,
439
+ { 'Content-Type' : 'application/json' } ,
440
+ JSON . stringify ( clientData )
441
+ ) ;
442
+
443
+ const response = await oauthProvider . fetch ( request , mockEnv , mockCtx ) ;
444
+ const client = await response . json ( ) ;
445
+
446
+ clientId = client . client_id ;
447
+ redirectUri = 'https://spa-client.example.com/callback' ;
448
+ }
449
+
450
+ beforeEach ( async ( ) => {
451
+ await createPublicClient ( ) ;
452
+ } ) ;
453
+
454
+ it ( 'should handle implicit flow request and redirect with token in fragment' , async ( ) => {
455
+ // Create an implicit flow authorization request
456
+ const authRequest = createMockRequest (
457
+ `https://example.com/authorize?response_type=token&client_id=${ clientId } ` +
458
+ `&redirect_uri=${ encodeURIComponent ( redirectUri ) } ` +
459
+ `&scope=read%20write&state=xyz123`
460
+ ) ;
461
+
462
+ // The default handler will process this request and generate a redirect
463
+ const response = await oauthProvider . fetch ( authRequest , mockEnv , mockCtx ) ;
464
+
465
+ expect ( response . status ) . toBe ( 302 ) ;
466
+
467
+ // Check that we're redirected to the client's redirect_uri with token in fragment
468
+ const location = response . headers . get ( 'Location' ) ;
469
+ expect ( location ) . toBeDefined ( ) ;
470
+ expect ( location ) . toContain ( redirectUri ) ;
471
+
472
+ const url = new URL ( location ! ) ;
473
+
474
+ // Check that there's no code parameter in the query string
475
+ expect ( url . searchParams . has ( 'code' ) ) . toBe ( false ) ;
476
+
477
+ // Check that we have a hash/fragment with token parameters
478
+ expect ( url . hash ) . toBeTruthy ( ) ;
479
+
480
+ // Parse the fragment
481
+ const fragment = new URLSearchParams ( url . hash . substring ( 1 ) ) ; // Remove the # character
482
+
483
+ // Verify token parameters
484
+ expect ( fragment . get ( 'access_token' ) ) . toBeTruthy ( ) ;
485
+ expect ( fragment . get ( 'token_type' ) ) . toBe ( 'bearer' ) ;
486
+ expect ( fragment . get ( 'expires_in' ) ) . toBe ( '3600' ) ;
487
+ expect ( fragment . get ( 'scope' ) ) . toBe ( 'read write' ) ;
488
+ expect ( fragment . get ( 'state' ) ) . toBe ( 'xyz123' ) ;
489
+
490
+ // Verify a grant was created in KV
491
+ const grants = await mockEnv . OAUTH_KV . list ( { prefix : 'grant:' } ) ;
492
+ expect ( grants . keys . length ) . toBe ( 1 ) ;
493
+
494
+ // Verify access token was stored in KV
495
+ const tokenEntries = await mockEnv . OAUTH_KV . list ( { prefix : 'token:' } ) ;
496
+ expect ( tokenEntries . keys . length ) . toBe ( 1 ) ;
497
+ } ) ;
498
+
499
+ it ( 'should reject implicit flow when allowImplicitFlow is disabled' , async ( ) => {
500
+ // Create a provider with implicit flow disabled
501
+ const providerWithoutImplicit = new OAuthProvider ( {
502
+ apiRoute : [ '/api/' ] ,
503
+ apiHandler : TestApiHandler ,
504
+ defaultHandler : testDefaultHandler ,
505
+ authorizeEndpoint : '/authorize' ,
506
+ tokenEndpoint : '/oauth/token' ,
507
+ scopesSupported : [ 'read' , 'write' ] ,
508
+ allowImplicitFlow : false // Explicitly disable
509
+ } ) ;
510
+
511
+ // Create an implicit flow authorization request
512
+ const authRequest = createMockRequest (
513
+ `https://example.com/authorize?response_type=token&client_id=${ clientId } ` +
514
+ `&redirect_uri=${ encodeURIComponent ( redirectUri ) } ` +
515
+ `&scope=read%20write&state=xyz123`
516
+ ) ;
517
+
518
+ // Mock parseAuthRequest to test error handling
519
+ vi . spyOn ( authRequest , 'formData' ) . mockImplementation ( ( ) => {
520
+ throw new Error ( 'The implicit grant flow is not enabled for this provider' ) ;
521
+ } ) ;
522
+
523
+ // Expect an error response
524
+ await expect ( providerWithoutImplicit . fetch ( authRequest , mockEnv , mockCtx ) ) . rejects . toThrow (
525
+ 'The implicit grant flow is not enabled for this provider'
526
+ ) ;
527
+ } ) ;
528
+
529
+ it ( 'should use the access token to access API directly' , async ( ) => {
530
+ // Create an implicit flow authorization request
531
+ const authRequest = createMockRequest (
532
+ `https://example.com/authorize?response_type=token&client_id=${ clientId } ` +
533
+ `&redirect_uri=${ encodeURIComponent ( redirectUri ) } ` +
534
+ `&scope=read%20write&state=xyz123`
535
+ ) ;
536
+
537
+ // The default handler will process this request and generate a redirect
538
+ const response = await oauthProvider . fetch ( authRequest , mockEnv , mockCtx ) ;
539
+ const location = response . headers . get ( 'Location' ) ! ;
540
+
541
+ // Parse the fragment to get the access token
542
+ const url = new URL ( location ) ;
543
+ const fragment = new URLSearchParams ( url . hash . substring ( 1 ) ) ;
544
+ const accessToken = fragment . get ( 'access_token' ) ! ;
545
+
546
+ // Now use the access token for an API request
547
+ const apiRequest = createMockRequest (
548
+ 'https://example.com/api/test' ,
549
+ 'GET' ,
550
+ { 'Authorization' : `Bearer ${ accessToken } ` }
551
+ ) ;
552
+
553
+ const apiResponse = await oauthProvider . fetch ( apiRequest , mockEnv , mockCtx ) ;
554
+
555
+ expect ( apiResponse . status ) . toBe ( 200 ) ;
556
+
557
+ const apiData = await apiResponse . json ( ) ;
558
+ expect ( apiData . success ) . toBe ( true ) ;
559
+ expect ( apiData . user ) . toEqual ( { userId : "test-user-123" , username : "TestUser" } ) ;
560
+ } ) ;
561
+ } ) ;
562
+
563
+ describe ( 'Authorization Code Flow Exchange' , ( ) => {
564
+ let clientId : string ;
565
+ let clientSecret : string ;
566
+ let redirectUri : string ;
567
+
568
+ // Helper to create a test client before authorization tests
569
+ async function createTestClient ( ) {
570
+ const clientData = {
571
+ redirect_uris : [ 'https://client.example.com/callback' ] ,
572
+ client_name : 'Test Client' ,
573
+ token_endpoint_auth_method : 'client_secret_basic'
574
+ } ;
575
+
576
+ const request = createMockRequest (
577
+ 'https://example.com/oauth/register' ,
578
+ 'POST' ,
579
+ { 'Content-Type' : 'application/json' } ,
580
+ JSON . stringify ( clientData )
581
+ ) ;
582
+
583
+ const response = await oauthProvider . fetch ( request , mockEnv , mockCtx ) ;
584
+ const client = await response . json ( ) ;
585
+
586
+ clientId = client . client_id ;
587
+ clientSecret = client . client_secret ;
588
+ redirectUri = 'https://client.example.com/callback' ;
589
+ }
590
+
591
+ beforeEach ( async ( ) => {
592
+ await createTestClient ( ) ;
593
+ } ) ;
594
+
397
595
it ( 'should exchange auth code for tokens' , async ( ) => {
398
596
// First get an auth code
399
597
const authRequest = createMockRequest (
0 commit comments