@@ -201,6 +201,8 @@ pub struct StoredCredentials {
201201 pub granted_scopes : Vec < String > ,
202202 #[ serde( default ) ]
203203 pub token_received_at : Option < u64 > ,
204+ #[ serde( default ) ]
205+ pub issuer : Option < String > ,
204206}
205207
206208impl std:: fmt:: Debug for StoredCredentials {
@@ -213,6 +215,7 @@ impl std::fmt::Debug for StoredCredentials {
213215 )
214216 . field ( "granted_scopes" , & self . granted_scopes )
215217 . field ( "token_received_at" , & self . token_received_at )
218+ . field ( "issuer" , & self . issuer )
216219 . finish ( )
217220 }
218221}
@@ -230,8 +233,14 @@ impl StoredCredentials {
230233 token_response,
231234 granted_scopes,
232235 token_received_at,
236+ issuer : None ,
233237 }
234238 }
239+
240+ pub fn with_issuer ( mut self , issuer : Option < String > ) -> Self {
241+ self . issuer = issuer;
242+ self
243+ }
235244}
236245
237246/// Trait for storing and retrieving OAuth2 credentials
@@ -1088,6 +1097,49 @@ impl AuthorizationManager {
10881097 self . metadata = Some ( metadata) ;
10891098 }
10901099
1100+ if let ( Some ( stored_issuer) , Some ( current_issuer) ) =
1101+ ( stored. issuer . as_deref ( ) , self . metadata_issuer ( ) . as_deref ( ) )
1102+ {
1103+ // A CIMD client ID is the client's metadata URL, so it is
1104+ // portable across authorization servers and exempt here.
1105+ if stored_issuer != current_issuer {
1106+ if is_https_url ( & stored. client_id ) {
1107+ // A CIMD client ID is the client's metadata URL, so it is
1108+ // portable across authorization servers — but the tokens
1109+ // were minted by the previous AS and must not be reused.
1110+ tracing:: warn!(
1111+ stored_issuer,
1112+ current_issuer,
1113+ "authorization server issuer changed; discarding tokens but keeping portable CIMD client ID"
1114+ ) ;
1115+ self . credential_store
1116+ . save (
1117+ StoredCredentials :: new (
1118+ stored. client_id . clone ( ) ,
1119+ None ,
1120+ vec ! [ ] ,
1121+ None ,
1122+ )
1123+ . with_issuer ( self . metadata_issuer ( ) ) ,
1124+ )
1125+ . await ?;
1126+ self . configure_client_id ( & stored. client_id ) ?;
1127+ return Ok ( false ) ;
1128+ }
1129+
1130+ tracing:: warn!(
1131+ stored_issuer,
1132+ current_issuer,
1133+ "authorization server issuer changed; clearing stored credentials bound to the previous issuer"
1134+ ) ;
1135+ self . credential_store . clear ( ) . await ?;
1136+ return Err ( AuthError :: AuthorizationServerMismatch {
1137+ expected_issuer : stored_issuer. to_string ( ) ,
1138+ received_issuer : current_issuer. to_string ( ) ,
1139+ } ) ;
1140+ }
1141+ }
1142+
10911143 self . configure_client_id ( & stored. client_id ) ?;
10921144 return Ok ( true ) ;
10931145 }
@@ -1703,6 +1755,7 @@ impl AuthorizationManager {
17031755 token_response : Some ( token_result. clone ( ) ) ,
17041756 granted_scopes,
17051757 token_received_at : Some ( Self :: now_epoch_secs ( ) ) ,
1758+ issuer : self . metadata_issuer ( ) ,
17061759 } ;
17071760 self . credential_store . save ( stored) . await ?;
17081761
@@ -1716,6 +1769,10 @@ impl AuthorizationManager {
17161769 . as_secs ( )
17171770 }
17181771
1772+ fn metadata_issuer ( & self ) -> Option < String > {
1773+ self . metadata . as_ref ( ) . and_then ( |m| m. issuer . clone ( ) )
1774+ }
1775+
17191776 /// Proactive refresh buffer: refresh tokens this many seconds before they expire
17201777 /// to avoid races between token retrieval and the actual HTTP request.
17211778 const REFRESH_BUFFER_SECS : u64 = 30 ;
@@ -1838,6 +1895,7 @@ impl AuthorizationManager {
18381895 token_response : Some ( token_result. clone ( ) ) ,
18391896 granted_scopes,
18401897 token_received_at : Some ( Self :: now_epoch_secs ( ) ) ,
1898+ issuer : self . metadata_issuer ( ) ,
18411899 } ;
18421900 self . credential_store . save ( stored) . await ?;
18431901
@@ -2658,6 +2716,7 @@ impl AuthorizationManager {
26582716 token_response : Some ( token_result. clone ( ) ) ,
26592717 granted_scopes,
26602718 token_received_at : Some ( Self :: now_epoch_secs ( ) ) ,
2719+ issuer : self . metadata_issuer ( ) ,
26612720 } ;
26622721 self . credential_store . save ( stored) . await ?;
26632722
@@ -2780,6 +2839,7 @@ impl AuthorizationManager {
27802839 token_response : Some ( token_result. clone ( ) ) ,
27812840 granted_scopes,
27822841 token_received_at : Some ( Self :: now_epoch_secs ( ) ) ,
2842+ issuer : self . metadata_issuer ( ) ,
27832843 } ;
27842844 self . credential_store . save ( stored) . await ?;
27852845
@@ -3170,17 +3230,18 @@ impl OAuthState {
31703230
31713231 * manager. current_scopes . write ( ) . await = granted_scopes. clone ( ) ;
31723232
3233+ let metadata = manager. discover_metadata ( ) . await ?;
3234+ manager. metadata = Some ( metadata) ;
3235+
31733236 let stored = StoredCredentials {
31743237 client_id : client_id. to_string ( ) ,
31753238 token_response : Some ( credentials) ,
31763239 granted_scopes,
31773240 token_received_at : Some ( AuthorizationManager :: now_epoch_secs ( ) ) ,
3241+ issuer : manager. metadata_issuer ( ) ,
31783242 } ;
31793243 manager. credential_store . save ( stored) . await ?;
31803244
3181- let metadata = manager. discover_metadata ( ) . await ?;
3182- manager. metadata = Some ( metadata) ;
3183-
31843245 manager. configure_client_id ( client_id) ?;
31853246
31863247 * self = OAuthState :: Authorized ( manager) ;
@@ -4887,6 +4948,7 @@ mod tests {
48874948 token_response : Some ( token_response) ,
48884949 granted_scopes : vec ! [ ] ,
48894950 token_received_at : None ,
4951+ issuer : None ,
48904952 } ;
48914953 let debug_output = format ! ( "{:?}" , creds) ;
48924954
@@ -5864,6 +5926,7 @@ mod tests {
58645926 token_response : Some ( make_token_response ( "my-access-token" , Some ( 3600 ) ) ) ,
58655927 granted_scopes : vec ! [ ] ,
58665928 token_received_at : Some ( AuthorizationManager :: now_epoch_secs ( ) ) ,
5929+ issuer : None ,
58675930 } ;
58685931 manager. credential_store . save ( stored) . await . unwrap ( ) ;
58695932
@@ -5881,6 +5944,7 @@ mod tests {
58815944 token_response : Some ( make_token_response ( "stale-token" , Some ( 3600 ) ) ) ,
58825945 granted_scopes : vec ! [ ] ,
58835946 token_received_at : Some ( AuthorizationManager :: now_epoch_secs ( ) - 7200 ) ,
5947+ issuer : None ,
58845948 } ;
58855949 manager. credential_store . save ( stored) . await . unwrap ( ) ;
58865950
@@ -5899,6 +5963,7 @@ mod tests {
58995963 token_response : Some ( make_token_response ( "no-expiry-token" , None ) ) ,
59005964 granted_scopes : vec ! [ ] ,
59015965 token_received_at : None ,
5966+ issuer : None ,
59025967 } ;
59035968 manager. credential_store . save ( stored) . await . unwrap ( ) ;
59045969
@@ -5916,6 +5981,7 @@ mod tests {
59165981 token_response : Some ( make_token_response ( "almost-expired" , Some ( 3600 ) ) ) ,
59175982 granted_scopes : vec ! [ ] ,
59185983 token_received_at : Some ( AuthorizationManager :: now_epoch_secs ( ) - 3590 ) ,
5984+ issuer : None ,
59195985 } ;
59205986 manager. credential_store . save ( stored) . await . unwrap ( ) ;
59215987
@@ -5934,6 +6000,7 @@ mod tests {
59346000 token_response : Some ( make_token_response ( "stale-token" , Some ( 3600 ) ) ) ,
59356001 granted_scopes : vec ! [ ] ,
59366002 token_received_at : Some ( AuthorizationManager :: now_epoch_secs ( ) - 7200 ) ,
6003+ issuer : None ,
59376004 } ;
59386005 manager. credential_store . save ( stored) . await . unwrap ( ) ;
59396006
@@ -6235,6 +6302,7 @@ mod tests {
62356302 ) ) ,
62366303 granted_scopes : vec ! [ ] ,
62376304 token_received_at : Some ( AuthorizationManager :: now_epoch_secs ( ) ) ,
6305+ issuer : None ,
62386306 } )
62396307 . await
62406308 . unwrap ( ) ;
@@ -6263,6 +6331,7 @@ mod tests {
62636331 token_response : None ,
62646332 granted_scopes : vec ! [ ] ,
62656333 token_received_at : None ,
6334+ issuer : None ,
62666335 } ;
62676336 manager. credential_store . save ( stored) . await . unwrap ( ) ;
62686337
@@ -6283,6 +6352,7 @@ mod tests {
62836352 token_response : Some ( make_token_response ( "old-token" , Some ( 3600 ) ) ) ,
62846353 granted_scopes : vec ! [ ] ,
62856354 token_received_at : Some ( AuthorizationManager :: now_epoch_secs ( ) ) ,
6355+ issuer : None ,
62866356 } ;
62876357 manager. credential_store . save ( stored) . await . unwrap ( ) ;
62886358
@@ -6373,6 +6443,7 @@ mod tests {
63736443 ) ) ,
63746444 granted_scopes : vec ! [ ] ,
63756445 token_received_at : Some ( AuthorizationManager :: now_epoch_secs ( ) ) ,
6446+ issuer : None ,
63766447 } )
63776448 . await
63786449 . unwrap ( ) ;
@@ -6578,6 +6649,7 @@ mod tests {
65786649 ) ) ,
65796650 granted_scopes : vec ! [ "read" . to_string( ) , "write" . to_string( ) ] ,
65806651 token_received_at : Some ( AuthorizationManager :: now_epoch_secs ( ) ) ,
6652+ issuer : None ,
65816653 } ;
65826654 manager. credential_store . save ( stored) . await . unwrap ( ) ;
65836655
@@ -6615,6 +6687,7 @@ mod tests {
66156687 ) ) ,
66166688 granted_scopes : vec ! [ ] ,
66176689 token_received_at : Some ( AuthorizationManager :: now_epoch_secs ( ) ) ,
6690+ issuer : None ,
66186691 } ;
66196692 manager. credential_store . save ( stored) . await . unwrap ( ) ;
66206693
@@ -6652,6 +6725,7 @@ mod tests {
66526725 ) ) ,
66536726 granted_scopes : vec ! [ "read" . to_string( ) ] ,
66546727 token_received_at : Some ( AuthorizationManager :: now_epoch_secs ( ) ) ,
6728+ issuer : None ,
66556729 } ;
66566730 manager. credential_store . save ( stored) . await . unwrap ( ) ;
66576731
@@ -6689,6 +6763,7 @@ mod tests {
66896763 ) ) ,
66906764 granted_scopes : vec ! [ ] ,
66916765 token_received_at : Some ( AuthorizationManager :: now_epoch_secs ( ) ) ,
6766+ issuer : None ,
66926767 } ;
66936768 manager. credential_store . save ( stored) . await . unwrap ( ) ;
66946769
@@ -6727,6 +6802,7 @@ mod tests {
67276802 ) ) ,
67286803 granted_scopes : vec ! [ ] ,
67296804 token_received_at : Some ( AuthorizationManager :: now_epoch_secs ( ) ) ,
6805+ issuer : None ,
67306806 } ;
67316807 manager. credential_store . save ( stored) . await . unwrap ( ) ;
67326808
@@ -6790,6 +6866,7 @@ mod tests {
67906866 ) ) ,
67916867 granted_scopes : vec ! [ ] ,
67926868 token_received_at : Some ( AuthorizationManager :: now_epoch_secs ( ) ) ,
6869+ issuer : None ,
67936870 } ;
67946871 manager. credential_store . save ( stored) . await . unwrap ( ) ;
67956872
0 commit comments