@@ -705,247 +705,122 @@ describe("OAuth Authorization", () => {
705
705
code_challenge_methods_supported : [ "S256" ] ,
706
706
} ;
707
707
708
- it ( "returns OAuth metadata when authorizationServerUrl is provided and OAuth discovery succeeds" , async ( ) => {
709
- mockFetch . mockResolvedValueOnce ( {
710
- ok : true ,
711
- status : 200 ,
712
- json : async ( ) => validOAuthMetadata ,
713
- } ) ;
714
-
715
- const metadata = await discoverAuthorizationServerMetadata (
716
- "https://mcp.example.com" ,
717
- "https://auth.example.com"
718
- ) ;
719
-
720
- expect ( metadata ) . toEqual ( validOAuthMetadata ) ;
721
- const calls = mockFetch . mock . calls ;
722
- expect ( calls . length ) . toBe ( 1 ) ;
723
- const [ url , options ] = calls [ 0 ] ;
724
- expect ( url . toString ( ) ) . toBe ( "https://auth.example.com/.well-known/oauth-authorization-server" ) ;
725
- expect ( options . headers ) . toEqual ( {
726
- "MCP-Protocol-Version" : LATEST_PROTOCOL_VERSION
727
- } ) ;
728
- } ) ;
729
-
730
- it ( "falls back to OpenID Connect discovery when OAuth discovery fails (no path component)" , async ( ) => {
731
- // First call (OAuth) returns 404
732
- mockFetch . mockResolvedValueOnce ( {
733
- ok : false ,
734
- status : 404 ,
735
- } ) ;
736
-
737
- // Second call (OpenID Connect) succeeds
738
- mockFetch . mockResolvedValueOnce ( {
739
- ok : true ,
740
- status : 200 ,
741
- json : async ( ) => validOpenIdMetadata ,
742
- } ) ;
743
-
744
- const metadata = await discoverAuthorizationServerMetadata (
745
- "https://mcp.example.com" ,
746
- "https://auth.example.com"
747
- ) ;
748
-
749
- expect ( metadata ) . toEqual ( validOpenIdMetadata ) ;
750
- const calls = mockFetch . mock . calls ;
751
- expect ( calls . length ) . toBe ( 2 ) ;
752
-
753
- // First call should be OAuth discovery
754
- expect ( calls [ 0 ] [ 0 ] . toString ( ) ) . toBe ( "https://auth.example.com/.well-known/oauth-authorization-server" ) ;
755
-
756
- // Second call should be OpenID Connect discovery
757
- expect ( calls [ 1 ] [ 0 ] . toString ( ) ) . toBe ( "https://auth.example.com/.well-known/openid-configuration" ) ;
758
- } ) ;
759
-
760
- it ( "returns undefined when authorizationServerUrl is provided but both discoveries fail" , async ( ) => {
761
- // Both calls return 404
762
- mockFetch . mockResolvedValue ( {
763
- ok : false ,
764
- status : 404 ,
765
- } ) ;
766
-
767
- const metadata = await discoverAuthorizationServerMetadata (
768
- "https://mcp.example.com" ,
769
- "https://auth.example.com"
770
- ) ;
771
-
772
- expect ( metadata ) . toBeUndefined ( ) ;
773
- const calls = mockFetch . mock . calls ;
774
- expect ( calls . length ) . toBe ( 2 ) ;
775
- } ) ;
776
-
777
- it ( "should fall back to root OAuth discovery when path-aware discovery fails" , async ( ) => {
778
- // First call (OAuth with path) returns 404
779
- mockFetch . mockResolvedValueOnce ( {
780
- ok : false ,
781
- status : 404 ,
782
- } ) ;
783
-
784
- // Second call should be OAuth root fallback
785
- mockFetch . mockResolvedValueOnce ( {
786
- ok : true ,
787
- status : 200 ,
788
- json : async ( ) => validOAuthMetadata ,
789
- } ) ;
790
-
791
- const metadata = await discoverAuthorizationServerMetadata (
792
- "https://mcp.example.com" ,
793
- "https://auth.example.com/tenant1"
794
- ) ;
795
-
796
- expect ( metadata ) . toEqual ( validOAuthMetadata ) ;
797
- const calls = mockFetch . mock . calls ;
798
- expect ( calls . length ) . toBe ( 2 ) ;
799
-
800
- // Should try OAuth with path first
801
- expect ( calls [ 0 ] [ 0 ] . toString ( ) ) . toBe ( "https://auth.example.com/.well-known/oauth-authorization-server/tenant1" ) ;
802
-
803
- // Should fall back to OAuth root discovery
804
- expect ( calls [ 1 ] [ 0 ] . toString ( ) ) . toBe ( "https://auth.example.com/.well-known/oauth-authorization-server" ) ;
805
- } ) ;
806
-
807
- it ( "should try OIDC discovery after OAuth attempts fail" , async ( ) => {
808
- // First call (OAuth with path) returns 404
809
- mockFetch . mockResolvedValueOnce ( {
810
- ok : false ,
811
- status : 404 ,
812
- } ) ;
813
-
814
- // Second call (OAuth root) returns 404
815
- mockFetch . mockResolvedValueOnce ( {
816
- ok : false ,
817
- status : 404 ,
818
- } ) ;
819
-
820
- // Third call (OIDC with path insertion) succeeds
821
- mockFetch . mockResolvedValueOnce ( {
822
- ok : true ,
823
- status : 200 ,
824
- json : async ( ) => validOpenIdMetadata ,
825
- } ) ;
826
-
827
- const metadata = await discoverAuthorizationServerMetadata (
828
- "https://mcp.example.com" ,
829
- "https://auth.example.com/tenant1"
830
- ) ;
831
-
832
- expect ( metadata ) . toEqual ( validOpenIdMetadata ) ;
833
- const calls = mockFetch . mock . calls ;
834
- expect ( calls . length ) . toBe ( 3 ) ;
835
-
836
- // Should try OAuth attempts first
837
- expect ( calls [ 0 ] [ 0 ] . toString ( ) ) . toBe ( "https://auth.example.com/.well-known/oauth-authorization-server/tenant1" ) ;
838
- expect ( calls [ 1 ] [ 0 ] . toString ( ) ) . toBe ( "https://auth.example.com/.well-known/oauth-authorization-server" ) ;
839
-
840
- // Then try OIDC discovery
841
- expect ( calls [ 2 ] [ 0 ] . toString ( ) ) . toBe ( "https://auth.example.com/.well-known/openid-configuration/tenant1" ) ;
842
- } ) ;
843
-
844
- it ( "handles authorization server URL with path in OAuth discovery" , async ( ) => {
845
- mockFetch . mockResolvedValueOnce ( {
846
- ok : true ,
847
- status : 200 ,
848
- json : async ( ) => validOAuthMetadata ,
849
- } ) ;
850
-
851
- const metadata = await discoverAuthorizationServerMetadata (
852
- "https://mcp.example.com" ,
853
- "https://auth.example.com/tenant1"
854
- ) ;
855
-
856
- expect ( metadata ) . toEqual ( validOAuthMetadata ) ;
857
- const calls = mockFetch . mock . calls ;
858
- expect ( calls . length ) . toBe ( 1 ) ;
859
- const [ url ] = calls [ 0 ] ;
860
- expect ( url . toString ( ) ) . toBe ( "https://auth.example.com/.well-known/oauth-authorization-server/tenant1" ) ;
861
- } ) ;
708
+ describe ( "discovery sequence" , ( ) => {
709
+ const testCases = [
710
+ {
711
+ description : "no path - direct OAuth success" ,
712
+ serverUrl : "https://auth.example.com" ,
713
+ responses : [ { success : true , metadata : validOAuthMetadata } ] ,
714
+ expectedPaths : [
715
+ "https://auth.example.com/.well-known/oauth-authorization-server"
716
+ ]
717
+ } ,
718
+ {
719
+ description : "no path - OAuth fails, OIDC succeeds" ,
720
+ serverUrl : "https://auth.example.com" ,
721
+ responses : [
722
+ { success : false , status : 404 } ,
723
+ { success : true , metadata : validOpenIdMetadata }
724
+ ] ,
725
+ expectedPaths : [
726
+ "https://auth.example.com/.well-known/oauth-authorization-server" ,
727
+ "https://auth.example.com/.well-known/openid-configuration"
728
+ ]
729
+ } ,
730
+ {
731
+ description : "with path - path OAuth succeeds" ,
732
+ serverUrl : "https://auth.example.com/tenant1" ,
733
+ responses : [ { success : true , metadata : validOAuthMetadata } ] ,
734
+ expectedPaths : [
735
+ "https://auth.example.com/.well-known/oauth-authorization-server/tenant1"
736
+ ]
737
+ } ,
738
+ {
739
+ description : "with path - path OAuth fails, root OAuth succeeds" ,
740
+ serverUrl : "https://auth.example.com/tenant1" ,
741
+ responses : [
742
+ { success : false , status : 404 } ,
743
+ { success : true , metadata : validOAuthMetadata }
744
+ ] ,
745
+ expectedPaths : [
746
+ "https://auth.example.com/.well-known/oauth-authorization-server/tenant1" ,
747
+ "https://auth.example.com/.well-known/oauth-authorization-server"
748
+ ]
749
+ } ,
750
+ {
751
+ description : "with path - OAuth fails, OIDC path insertion succeeds" ,
752
+ serverUrl : "https://auth.example.com/tenant1" ,
753
+ responses : [
754
+ { success : false , status : 404 } , // OAuth path
755
+ { success : false , status : 404 } , // OAuth root
756
+ { success : true , metadata : validOpenIdMetadata } // OIDC path insertion
757
+ ] ,
758
+ expectedPaths : [
759
+ "https://auth.example.com/.well-known/oauth-authorization-server/tenant1" ,
760
+ "https://auth.example.com/.well-known/oauth-authorization-server" ,
761
+ "https://auth.example.com/.well-known/openid-configuration/tenant1"
762
+ ]
763
+ } ,
764
+ {
765
+ description : "with path - OAuth fails, OIDC path appending succeeds" ,
766
+ serverUrl : "https://auth.example.com/tenant1" ,
767
+ responses : [
768
+ { success : false , status : 404 } , // OAuth path
769
+ { success : false , status : 404 } , // OAuth root
770
+ { success : false , status : 404 } , // OIDC path insertion
771
+ { success : true , metadata : validOpenIdMetadata } // OIDC path appending
772
+ ] ,
773
+ expectedPaths : [
774
+ "https://auth.example.com/.well-known/oauth-authorization-server/tenant1" ,
775
+ "https://auth.example.com/.well-known/oauth-authorization-server" ,
776
+ "https://auth.example.com/.well-known/openid-configuration/tenant1" ,
777
+ "https://auth.example.com/tenant1/.well-known/openid-configuration"
778
+ ]
779
+ }
780
+ ] ;
781
+
782
+ testCases . forEach ( ( { description, serverUrl, responses, expectedPaths } ) => {
783
+ it ( description , async ( ) => {
784
+ // Set up mock responses
785
+ responses . forEach ( response => {
786
+ if ( response . success ) {
787
+ mockFetch . mockResolvedValueOnce ( {
788
+ ok : true ,
789
+ status : 200 ,
790
+ json : async ( ) => response . metadata
791
+ } ) ;
792
+ } else {
793
+ mockFetch . mockResolvedValueOnce ( {
794
+ ok : false ,
795
+ status : response . status || 404
796
+ } ) ;
797
+ }
798
+ } ) ;
862
799
863
- it ( "handles authorization server URL with path in OpenID Connect discovery" , async ( ) => {
864
- // OAuth discovery with path fails
865
- mockFetch . mockResolvedValueOnce ( {
866
- ok : false ,
867
- status : 404 ,
868
- } ) ;
800
+ const metadata = await discoverAuthorizationServerMetadata (
801
+ "https://mcp.example.com" ,
802
+ serverUrl
803
+ ) ;
804
+
805
+ // Verify result
806
+ const successResponse = responses . find ( r => r . success ) ;
807
+ if ( successResponse ) {
808
+ expect ( metadata ) . toEqual ( successResponse . metadata ) ;
809
+ } else {
810
+ expect ( metadata ) . toBeUndefined ( ) ;
811
+ }
869
812
870
- // OAuth discovery at root fails
871
- mockFetch . mockResolvedValueOnce ( {
872
- ok : false ,
873
- status : 404 ,
874
- } ) ;
813
+ // Verify discovery sequence
814
+ const calls = mockFetch . mock . calls ;
815
+ expect ( calls . length ) . toBe ( expectedPaths . length ) ;
875
816
876
- // OpenID Connect discovery succeeds with path insertion
877
- mockFetch . mockResolvedValueOnce ( {
878
- ok : true ,
879
- status : 200 ,
880
- json : async ( ) => validOpenIdMetadata ,
817
+ expectedPaths . forEach ( ( expectedPath , index ) => {
818
+ expect ( calls [ index ] [ 0 ] . toString ( ) ) . toBe ( expectedPath ) ;
819
+ } ) ;
820
+ } ) ;
881
821
} ) ;
882
-
883
- const metadata = await discoverAuthorizationServerMetadata (
884
- "https://mcp.example.com" ,
885
- "https://auth.example.com/tenant1"
886
- ) ;
887
-
888
- expect ( metadata ) . toEqual ( validOpenIdMetadata ) ;
889
- const calls = mockFetch . mock . calls ;
890
- expect ( calls . length ) . toBe ( 3 ) ;
891
-
892
- // First call should be OAuth with path
893
- expect ( calls [ 0 ] [ 0 ] . toString ( ) ) . toBe ( "https://auth.example.com/.well-known/oauth-authorization-server/tenant1" ) ;
894
-
895
- // Second call should be OAuth at root
896
- expect ( calls [ 1 ] [ 0 ] . toString ( ) ) . toBe ( "https://auth.example.com/.well-known/oauth-authorization-server" ) ;
897
-
898
- // Third call should be OpenID Connect with path insertion
899
- expect ( calls [ 2 ] [ 0 ] . toString ( ) ) . toBe ( "https://auth.example.com/.well-known/openid-configuration/tenant1" ) ;
900
822
} ) ;
901
823
902
- it ( "tries multiple OpenID Connect endpoints when path is present" , async ( ) => {
903
- // OAuth discovery with path fails
904
- mockFetch . mockResolvedValueOnce ( {
905
- ok : false ,
906
- status : 404 ,
907
- } ) ;
908
-
909
- // OAuth discovery at root fails
910
- mockFetch . mockResolvedValueOnce ( {
911
- ok : false ,
912
- status : 404 ,
913
- } ) ;
914
-
915
- // First OpenID Connect attempt (path insertion) fails
916
- mockFetch . mockResolvedValueOnce ( {
917
- ok : false ,
918
- status : 404 ,
919
- } ) ;
920
-
921
- // Second OpenID Connect attempt (path prepending) succeeds
922
- mockFetch . mockResolvedValueOnce ( {
923
- ok : true ,
924
- status : 200 ,
925
- json : async ( ) => validOpenIdMetadata ,
926
- } ) ;
927
-
928
- const metadata = await discoverAuthorizationServerMetadata (
929
- "https://mcp.example.com" ,
930
- "https://auth.example.com/tenant1"
931
- ) ;
932
-
933
- expect ( metadata ) . toEqual ( validOpenIdMetadata ) ;
934
- const calls = mockFetch . mock . calls ;
935
- expect ( calls . length ) . toBe ( 4 ) ;
936
-
937
- // First call should be OAuth with path
938
- expect ( calls [ 0 ] [ 0 ] . toString ( ) ) . toBe ( "https://auth.example.com/.well-known/oauth-authorization-server/tenant1" ) ;
939
-
940
- // Second call should be OAuth at root
941
- expect ( calls [ 1 ] [ 0 ] . toString ( ) ) . toBe ( "https://auth.example.com/.well-known/oauth-authorization-server" ) ;
942
-
943
- // Third call should be OpenID Connect with path insertion
944
- expect ( calls [ 2 ] [ 0 ] . toString ( ) ) . toBe ( "https://auth.example.com/.well-known/openid-configuration/tenant1" ) ;
945
-
946
- // Fourth call should be OpenID Connect with path prepending
947
- expect ( calls [ 3 ] [ 0 ] . toString ( ) ) . toBe ( "https://auth.example.com/tenant1/.well-known/openid-configuration" ) ;
948
- } ) ;
949
824
950
825
it ( "throws error when OIDC provider does not support S256 PKCE" , async ( ) => {
951
826
// OAuth discovery fails
0 commit comments