@@ -183,10 +183,18 @@ public async Task Missing_initial_refresh_token_and_expired_access_token_should_
183
183
[ Fact ]
184
184
public async Task Short_token_lifetime_should_trigger_refresh ( )
185
185
{
186
+ // This test makes an initial token request using code flow and then
187
+ // refreshes the token a couple of times.
188
+
189
+ // We mock the expiration of the first few token responses to be short
190
+ // enough that we will automatically refresh immediately when attempting
191
+ // to use the tokens, while the final response gets a long refresh time,
192
+ // allowing us to verify that the token is not refreshed.
193
+
186
194
var mockHttp = new MockHttpMessageHandler ( ) ;
187
195
AppHost . IdentityServerHttpHandler = mockHttp ;
188
196
189
- // short token lifetime should trigger refresh on 1st use
197
+ // Respond to code flow with a short token lifetime so that we trigger refresh on 1st use
190
198
var initialTokenResponse = new
191
199
{
192
200
id_token = IdentityServerHost . CreateIdToken ( "1" , "web" ) ,
@@ -195,37 +203,31 @@ public async Task Short_token_lifetime_should_trigger_refresh()
195
203
expires_in = 10 ,
196
204
refresh_token = "initial_refresh_token" ,
197
205
} ;
198
-
199
- // response for re-deeming code
200
206
mockHttp . When ( "/connect/token" )
201
207
. WithFormData ( "grant_type" , "authorization_code" )
202
208
. Respond ( "application/json" , JsonSerializer . Serialize ( initialTokenResponse ) ) ;
203
209
204
- // short token lifetime should trigger refresh on 1st use
210
+ // Respond to refresh with a short token lifetime so that we trigger another refresh on 2nd use
205
211
var refreshTokenResponse = new
206
212
{
207
213
access_token = "refreshed1_access_token" ,
208
214
token_type = "token_type1" ,
209
215
expires_in = 10 ,
210
216
refresh_token = "refreshed1_refresh_token" ,
211
217
} ;
212
-
213
- // response for refresh 1
214
218
mockHttp . When ( "/connect/token" )
215
219
. WithFormData ( "grant_type" , "refresh_token" )
216
220
. WithFormData ( "refresh_token" , "initial_refresh_token" )
217
221
. Respond ( "application/json" , JsonSerializer . Serialize ( refreshTokenResponse ) ) ;
218
222
219
- // short token lifetime should trigger refresh on 2nd use
223
+ // Respond to second refresh with a long token lifetime so that we don't trigger another refresh on 3rd use
220
224
var refreshTokenResponse2 = new
221
225
{
222
226
access_token = "refreshed2_access_token" ,
223
227
token_type = "token_type2" ,
224
228
expires_in = 3600 ,
225
229
refresh_token = "refreshed2_refresh_token" ,
226
230
} ;
227
-
228
- // response for refresh 1
229
231
mockHttp . When ( "/connect/token" )
230
232
. WithFormData ( "grant_type" , "refresh_token" )
231
233
. WithFormData ( "refresh_token" , "refreshed1_refresh_token" )
@@ -397,4 +399,29 @@ public async Task Refresh_responses_without_refresh_token_use_old_refresh_token(
397
399
token . IsError . ShouldBeFalse ( ) ;
398
400
token . RefreshToken . ShouldBe ( "initial_refresh_token" ) ;
399
401
}
402
+
403
+ [ Fact ]
404
+ public async Task Multiple_users_have_distinct_tokens_across_refreshes ( )
405
+ {
406
+ // setup host
407
+ AppHost . ClientId = "web.short" ;
408
+ await AppHost . InitializeAsync ( ) ;
409
+ await AppHost . LoginAsync ( "alice" ) ;
410
+
411
+ var firstResponse = await AppHost . BrowserClient . GetAsync ( AppHost . Url ( "/call_api" ) ) ;
412
+ var firstToken = await firstResponse . Content . ReadFromJsonAsync < TokenEchoResponse > ( ) ;
413
+ var secondResponse = await AppHost . BrowserClient . GetAsync ( AppHost . Url ( "/call_api" ) ) ;
414
+ var secondToken = await secondResponse . Content . ReadFromJsonAsync < TokenEchoResponse > ( ) ;
415
+ firstToken . ShouldNotBeNull ( ) ;
416
+ secondToken . ShouldNotBeNull ( ) ;
417
+ secondToken . sub . ShouldBe ( firstToken . sub ) ;
418
+ secondToken . token . ShouldNotBe ( firstToken . token ) ;
419
+
420
+ await AppHost . LoginAsync ( "bob" ) ;
421
+ var thirdResponse = await AppHost . BrowserClient . GetAsync ( AppHost . Url ( "/call_api" ) ) ;
422
+ var thirdToken = await thirdResponse . Content . ReadFromJsonAsync < TokenEchoResponse > ( ) ;
423
+ thirdToken . ShouldNotBeNull ( ) ;
424
+ thirdToken . sub . ShouldNotBe ( secondToken . sub ) ;
425
+ thirdToken . token . ShouldNotBe ( firstToken . token ) ;
426
+ }
400
427
}
0 commit comments