@@ -477,6 +477,9 @@ pub enum AuthError {
477477 #[ error( "Metadata error: {0}" ) ]
478478 MetadataError ( String ) ,
479479
480+ #[ error( "Authorization server does not support the required PKCE code challenge method (S256)" ) ]
481+ PkceUnsupported ,
482+
480483 #[ error( "URL parse error: {0}" ) ]
481484 UrlError ( #[ from] url:: ParseError ) ,
482485
@@ -1159,18 +1162,21 @@ impl AuthorizationManager {
11591162 }
11601163 }
11611164
1162- // for PKCE, we always send s256 since oauth 2.1 requires servers to support it,
1163- // but warn if the server metadata suggests otherwise
1165+ // The client always sends an S256 challenge. A server that advertises
1166+ // methods without S256 can't do the flow we require, so refuse it. A
1167+ // server that omits the field is tolerated: it usually means the server
1168+ // didn't advertise PKCE, not that it lacks S256.
11641169 match & metadata. code_challenge_methods_supported {
11651170 Some ( methods) if !methods. iter ( ) . any ( |m| m == "S256" ) => {
1171+ return Err ( AuthError :: PkceUnsupported ) ;
1172+ }
1173+ None => {
11661174 warn ! (
1167- ?methods,
1168- "server does not advertise S256 in code_challenge_methods_supported, \
1169- proceeding with S256 anyway as oauth 2.1 requires it. \
1170- The server is not compliant with the specification!"
1175+ "authorization server metadata omits code_challenge_methods_supported; \
1176+ proceeding with an S256 challenge anyway"
11711177 ) ;
11721178 }
1173- _ => { }
1179+ Some ( _ ) => { }
11741180 }
11751181
11761182 Ok ( ( ) )
@@ -4307,19 +4313,43 @@ mod tests {
43074313 assert ! ( manager. validate_server_metadata( "code" ) . is_err( ) ) ;
43084314 }
43094315
4310- #[ tokio:: test]
4311- async fn test_validate_as_metadata_passes_without_pkce_s256 ( ) {
4312- let mut manager = AuthorizationManager :: new ( "https://example.com" )
4313- . await
4314- . unwrap ( ) ;
4315- let metadata = AuthorizationMetadata {
4316+ fn as_metadata_with_pkce ( methods : Option < Vec < String > > ) -> AuthorizationMetadata {
4317+ AuthorizationMetadata {
43164318 authorization_endpoint : "https://auth.example.com/authorize" . to_string ( ) ,
43174319 token_endpoint : "https://auth.example.com/token" . to_string ( ) ,
43184320 response_types_supported : Some ( vec ! [ "code" . to_string( ) ] ) ,
4319- code_challenge_methods_supported : Some ( vec ! [ "plain" . to_string ( ) ] ) ,
4321+ code_challenge_methods_supported : methods ,
43204322 ..Default :: default ( )
4321- } ;
4322- manager. set_metadata ( metadata) ;
4323+ }
4324+ }
4325+
4326+ #[ tokio:: test]
4327+ async fn test_validate_as_metadata_rejects_without_pkce_s256 ( ) {
4328+ let mut manager = AuthorizationManager :: new ( "https://example.com" )
4329+ . await
4330+ . unwrap ( ) ;
4331+ manager. set_metadata ( as_metadata_with_pkce ( Some ( vec ! [ "plain" . to_string( ) ] ) ) ) ;
4332+ assert ! ( matches!(
4333+ manager. validate_server_metadata( "code" ) ,
4334+ Err ( AuthError :: PkceUnsupported )
4335+ ) ) ;
4336+ }
4337+
4338+ #[ tokio:: test]
4339+ async fn test_validate_as_metadata_allows_absent_pkce_methods_by_default ( ) {
4340+ let mut manager = AuthorizationManager :: new ( "https://example.com" )
4341+ . await
4342+ . unwrap ( ) ;
4343+ manager. set_metadata ( as_metadata_with_pkce ( None ) ) ;
4344+ assert ! ( manager. validate_server_metadata( "code" ) . is_ok( ) ) ;
4345+ }
4346+
4347+ #[ tokio:: test]
4348+ async fn test_validate_as_metadata_passes_with_pkce_s256 ( ) {
4349+ let mut manager = AuthorizationManager :: new ( "https://example.com" )
4350+ . await
4351+ . unwrap ( ) ;
4352+ manager. set_metadata ( as_metadata_with_pkce ( Some ( vec ! [ "S256" . to_string( ) ] ) ) ) ;
43234353 assert ! ( manager. validate_server_metadata( "code" ) . is_ok( ) ) ;
43244354 }
43254355
0 commit comments