@@ -58,6 +58,9 @@ impl<'c> AsyncHttpClient<'c> for OAuthReqwestClient {
5858
5959const DEFAULT_EXCHANGE_URL : & str = "http://localhost" ;
6060
61+ /// Default OIDC Dynamic Client Registration `application_type` (SEP-837)
62+ const DEFAULT_APPLICATION_TYPE : & str = "native" ;
63+
6164/// Stored credentials for OAuth2 authorization
6265#[ derive( Clone , Serialize , Deserialize ) ]
6366#[ non_exhaustive]
@@ -423,6 +426,7 @@ pub struct OAuthClientConfig {
423426 pub client_secret : Option < String > ,
424427 pub scopes : Vec < String > ,
425428 pub redirect_uri : String ,
429+ pub application_type : Option < String > ,
426430}
427431
428432impl OAuthClientConfig {
@@ -432,6 +436,7 @@ impl OAuthClientConfig {
432436 client_secret : None ,
433437 scopes : Vec :: new ( ) ,
434438 redirect_uri : redirect_uri. into ( ) ,
439+ application_type : Some ( DEFAULT_APPLICATION_TYPE . to_string ( ) ) ,
435440 }
436441 }
437442
@@ -444,6 +449,12 @@ impl OAuthClientConfig {
444449 self . scopes = scopes;
445450 self
446451 }
452+
453+ /// Set the OIDC Dynamic Client Registration `application_type` (SEP-837), e.g. `"native"` or `"web"`
454+ pub fn with_application_type ( mut self , application_type : impl Into < String > ) -> Self {
455+ self . application_type = Some ( application_type. into ( ) ) ;
456+ self
457+ }
447458}
448459
449460// add type aliases for oauth2 types
@@ -613,6 +624,8 @@ pub struct AuthorizationManager {
613624 www_auth_scopes : RwLock < Vec < String > > ,
614625 /// scopes_supported from protected resource metadata (RFC 9728)
615626 resource_scopes : RwLock < Vec < String > > ,
627+ /// OIDC Dynamic Client Registration `application_type` (SEP-837)
628+ application_type : Option < String > ,
616629}
617630
618631#[ derive( Debug , Clone , Serialize , Deserialize ) ]
@@ -624,6 +637,8 @@ pub(crate) struct ClientRegistrationRequest {
624637 pub response_types : Vec < String > ,
625638 #[ serde( skip_serializing_if = "Option::is_none" ) ]
626639 pub scope : Option < String > ,
640+ #[ serde( skip_serializing_if = "Option::is_none" ) ]
641+ pub application_type : Option < String > ,
627642}
628643
629644#[ derive( Debug , Clone , Serialize , Deserialize ) ]
@@ -707,6 +722,7 @@ impl AuthorizationManager {
707722 scope_upgrade_config : ScopeUpgradeConfig :: default ( ) ,
708723 www_auth_scopes : RwLock :: new ( Vec :: new ( ) ) ,
709724 resource_scopes : RwLock :: new ( Vec :: new ( ) ) ,
725+ application_type : Some ( DEFAULT_APPLICATION_TYPE . to_string ( ) ) ,
710726 } ;
711727
712728 Ok ( manager)
@@ -800,6 +816,11 @@ impl AuthorizationManager {
800816 return Err ( AuthError :: NoAuthorizationSupport ) ;
801817 }
802818
819+ // SEP-837: only override application_type when the config sets one
820+ if let Some ( application_type) = & config. application_type {
821+ self . application_type = Some ( application_type. clone ( ) ) ;
822+ }
823+
803824 let metadata = self . metadata . as_ref ( ) . unwrap ( ) ;
804825
805826 let auth_url = AuthUrl :: new ( metadata. authorization_endpoint . clone ( ) )
@@ -890,6 +911,7 @@ impl AuthorizationManager {
890911 } ;
891912 self . validate_server_metadata ( "code" ) ?;
892913
914+ let application_type = self . application_type . clone ( ) ;
893915 let registration_request = ClientRegistrationRequest {
894916 client_name : name. to_string ( ) ,
895917 redirect_uris : vec ! [ redirect_uri. to_string( ) ] ,
@@ -904,6 +926,7 @@ impl AuthorizationManager {
904926 } else {
905927 Some ( scopes. join ( " " ) )
906928 } ,
929+ application_type : application_type. clone ( ) ,
907930 } ;
908931
909932 let response = match self
@@ -958,6 +981,7 @@ impl AuthorizationManager {
958981 client_secret : reg_response. client_secret . filter ( |s| !s. is_empty ( ) ) ,
959982 redirect_uri : redirect_uri. to_string ( ) ,
960983 scopes : scopes. iter ( ) . map ( |s| s. to_string ( ) ) . collect ( ) ,
984+ application_type,
961985 } ;
962986
963987 self . configure_client ( config. clone ( ) ) ?;
@@ -972,6 +996,8 @@ impl AuthorizationManager {
972996 client_secret : None ,
973997 scopes : vec ! [ ] ,
974998 redirect_uri : self . base_url . to_string ( ) ,
999+ // keep the manager's current application_type
1000+ application_type : None ,
9751001 } ;
9761002 self . configure_client ( config)
9771003 }
@@ -2140,12 +2166,14 @@ impl AuthorizationSession {
21402166 client_metadata_url
21412167 ) ) ) ;
21422168 }
2143- // SEP-991: URL-based Client IDs - use URL as client_id directly
2169+ // SEP-991: URL-based Client IDs - use URL as client_id directly.
2170+ // SEP-837: match the hosted client-metadata.json application_type ("native")
21442171 OAuthClientConfig {
21452172 client_id : client_metadata_url. to_string ( ) ,
21462173 client_secret : None ,
21472174 scopes : scopes. iter ( ) . map ( |s| s. to_string ( ) ) . collect ( ) ,
21482175 redirect_uri : redirect_uri. to_string ( ) ,
2176+ application_type : Some ( DEFAULT_APPLICATION_TYPE . to_string ( ) ) ,
21492177 }
21502178 } else {
21512179 // Fallback to dynamic registration
@@ -3117,6 +3145,7 @@ mod tests {
31173145 client_secret : Some ( "my-secret" . to_string ( ) ) ,
31183146 scopes : vec ! [ ] ,
31193147 redirect_uri : "http://localhost/callback" . to_string ( ) ,
3148+ application_type : None ,
31203149 }
31213150 }
31223151
@@ -3678,6 +3707,7 @@ mod tests {
36783707 token_endpoint_auth_method : "none" . to_string ( ) ,
36793708 response_types : vec ! [ "code" . to_string( ) ] ,
36803709 scope : Some ( "read write" . to_string ( ) ) ,
3710+ application_type : None ,
36813711 } ;
36823712 let json = serde_json:: to_value ( & req) . unwrap ( ) ;
36833713 assert_eq ! ( json[ "scope" ] , "read write" ) ;
@@ -3692,11 +3722,59 @@ mod tests {
36923722 token_endpoint_auth_method : "none" . to_string ( ) ,
36933723 response_types : vec ! [ "code" . to_string( ) ] ,
36943724 scope : None ,
3725+ application_type : None ,
36953726 } ;
36963727 let json = serde_json:: to_value ( & req) . unwrap ( ) ;
36973728 assert ! ( !json. as_object( ) . unwrap( ) . contains_key( "scope" ) ) ;
36983729 }
36993730
3731+ // -- ClientRegistrationRequest application_type (SEP-837) --
3732+
3733+ #[ test]
3734+ fn client_registration_request_includes_application_type_when_present ( ) {
3735+ let req = super :: ClientRegistrationRequest {
3736+ client_name : "test" . to_string ( ) ,
3737+ redirect_uris : vec ! [ "http://localhost/callback" . to_string( ) ] ,
3738+ grant_types : vec ! [ "authorization_code" . to_string( ) ] ,
3739+ token_endpoint_auth_method : "none" . to_string ( ) ,
3740+ response_types : vec ! [ "code" . to_string( ) ] ,
3741+ scope : None ,
3742+ application_type : Some ( "native" . to_string ( ) ) ,
3743+ } ;
3744+ let json = serde_json:: to_value ( & req) . unwrap ( ) ;
3745+ assert_eq ! ( json[ "application_type" ] , "native" ) ;
3746+ }
3747+
3748+ #[ test]
3749+ fn client_registration_request_omits_application_type_when_none ( ) {
3750+ let req = super :: ClientRegistrationRequest {
3751+ client_name : "test" . to_string ( ) ,
3752+ redirect_uris : vec ! [ "http://localhost/callback" . to_string( ) ] ,
3753+ grant_types : vec ! [ "authorization_code" . to_string( ) ] ,
3754+ token_endpoint_auth_method : "none" . to_string ( ) ,
3755+ response_types : vec ! [ "code" . to_string( ) ] ,
3756+ scope : None ,
3757+ application_type : None ,
3758+ } ;
3759+ let json = serde_json:: to_value ( & req) . unwrap ( ) ;
3760+ assert ! ( !json. as_object( ) . unwrap( ) . contains_key( "application_type" ) ) ;
3761+ }
3762+
3763+ // -- OAuthClientConfig application_type (SEP-837) --
3764+
3765+ #[ test]
3766+ fn oauth_client_config_defaults_application_type_to_native ( ) {
3767+ let config = super :: OAuthClientConfig :: new ( "client-id" , "http://127.0.0.1:8080/callback" ) ;
3768+ assert_eq ! ( config. application_type. as_deref( ) , Some ( "native" ) ) ;
3769+ }
3770+
3771+ #[ test]
3772+ fn oauth_client_config_with_application_type_overrides_default ( ) {
3773+ let config = super :: OAuthClientConfig :: new ( "client-id" , "https://app.example.com/callback" )
3774+ . with_application_type ( "web" ) ;
3775+ assert_eq ! ( config. application_type. as_deref( ) , Some ( "web" ) ) ;
3776+ }
3777+
37003778 // -- client credentials (SEP-1046) --
37013779
37023780 #[ tokio:: test]
0 commit comments