Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Keep previous refresh token if not updated during refresh #46

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ public async Task<UserToken> RefreshAccessTokenAsync(
token.Expiration = response.ExpiresIn == 0
? DateTimeOffset.MaxValue
: DateTimeOffset.UtcNow.AddSeconds(response.ExpiresIn);
token.RefreshToken = response.RefreshToken;
token.RefreshToken = response.RefreshToken ?? userToken.RefreshToken;
token.Scope = response.Scope;
}

Expand Down
126 changes: 87 additions & 39 deletions test/Tests/UserTokenManagementTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ public class UserTokenManagementTests : IntegrationTestBase
{
public UserTokenManagementTests() : base("web")
{ }

[Fact]
public async Task Anonymous_user_should_return_user_token_error()
{
Expand All @@ -21,7 +21,7 @@ public async Task Anonymous_user_should_return_user_token_error()

token!.IsError.ShouldBeTrue();
}

[Fact]
public async Task Anonymous_user_should_return_client_token()
{
Expand All @@ -31,7 +31,7 @@ public async Task Anonymous_user_should_return_client_token()
token!.AccessToken.ShouldNotBeNull();
token.AccessTokenType.ShouldBe("Bearer");
token.Expiration.ShouldNotBe(DateTimeOffset.MaxValue);

token.IsError.ShouldBeFalse();
}

Expand All @@ -40,7 +40,7 @@ public async Task Standard_initial_token_response_should_return_expected_values(
{
var mockHttp = new MockHttpMessageHandler();
AppHost.IdentityServerHttpHandler = mockHttp;

var initialTokenResponse = new
{
id_token = IdentityServerHost.CreateIdToken("1", "web"),
Expand All @@ -49,12 +49,12 @@ public async Task Standard_initial_token_response_should_return_expected_values(
expires_in = 3600,
refresh_token = "initial_refresh_token",
};

// response for re-deeming code
mockHttp.When("/connect/token")
.WithFormData("grant_type", "authorization_code")
.Respond("application/json", JsonSerializer.Serialize(initialTokenResponse));

await AppHost.InitializeAsync();
await AppHost.LoginAsync("alice");

Expand All @@ -68,7 +68,7 @@ public async Task Standard_initial_token_response_should_return_expected_values(
token.AccessTokenType.ShouldBe("token_type");
token.RefreshToken.ShouldBe("initial_refresh_token");
token.Expiration.ShouldNotBe(DateTimeOffset.MaxValue);

// 2nd request should not trigger a token request
response = await AppHost.BrowserClient.GetAsync(AppHost.Url("/user_token"));
token = await response.Content.ReadFromJsonAsync<UserToken>();
Expand All @@ -80,26 +80,26 @@ public async Task Standard_initial_token_response_should_return_expected_values(
token.RefreshToken.ShouldBe("initial_refresh_token");
token.Expiration.ShouldNotBe(DateTimeOffset.MaxValue);
}

[Fact]
public async Task Missing_expires_in_should_result_in_long_lived_token()
{
var mockHttp = new MockHttpMessageHandler();
AppHost.IdentityServerHttpHandler = mockHttp;

var initialTokenResponse = new
{
id_token = IdentityServerHost.CreateIdToken("1", "web"),
access_token = "initial_access_token",
token_type = "token_type",
refresh_token = "initial_refresh_token",
};

// response for re-deeming code
mockHttp.When("/connect/token")
.WithFormData("grant_type", "authorization_code")
.Respond("application/json", JsonSerializer.Serialize(initialTokenResponse));

await AppHost.InitializeAsync();
await AppHost.LoginAsync("alice");

Expand All @@ -113,26 +113,26 @@ public async Task Missing_expires_in_should_result_in_long_lived_token()
token.RefreshToken.ShouldBe("initial_refresh_token");
token.Expiration.ShouldBe(DateTimeOffset.MaxValue);
}

[Fact]
public async Task Missing_initial_refresh_token_response_should_return_access_token()
{
var mockHttp = new MockHttpMessageHandler();
AppHost.IdentityServerHttpHandler = mockHttp;

var initialTokenResponse = new
{
id_token = IdentityServerHost.CreateIdToken("1", "web"),
access_token = "initial_access_token",
token_type = "token_type",
expires_in = 3600
};

// response for re-deeming code
mockHttp.When("/connect/token")
.WithFormData("grant_type", "authorization_code")
.Respond("application/json", JsonSerializer.Serialize(initialTokenResponse));

await AppHost.InitializeAsync();
await AppHost.LoginAsync("alice");

Expand All @@ -146,26 +146,26 @@ public async Task Missing_initial_refresh_token_response_should_return_access_to
token.RefreshToken.ShouldBeNull();
token.Expiration.ShouldNotBe(DateTimeOffset.MaxValue);
}

[Fact]
public async Task Missing_initial_refresh_token_and_expired_access_token_should_return_initial_access_token()
{
var mockHttp = new MockHttpMessageHandler();
AppHost.IdentityServerHttpHandler = mockHttp;

var initialTokenResponse = new
{
id_token = IdentityServerHost.CreateIdToken("1", "web"),
access_token = "initial_access_token",
token_type = "token_type",
expires_in = 10
};

// response for re-deeming code
mockHttp.When("/connect/token")
.WithFormData("grant_type", "authorization_code")
.Respond("application/json", JsonSerializer.Serialize(initialTokenResponse));

await AppHost.InitializeAsync();
await AppHost.LoginAsync("alice");

Expand All @@ -179,13 +179,13 @@ public async Task Missing_initial_refresh_token_and_expired_access_token_should_
token.RefreshToken.ShouldBeNull();
token.Expiration.ShouldNotBe(DateTimeOffset.MaxValue);
}

[Fact]
public async Task Short_token_lifetime_should_trigger_refresh()
{
var mockHttp = new MockHttpMessageHandler();
AppHost.IdentityServerHttpHandler = mockHttp;

// short token lifetime should trigger refresh on 1st use
var initialTokenResponse = new
{
Expand All @@ -195,12 +195,12 @@ public async Task Short_token_lifetime_should_trigger_refresh()
expires_in = 10,
refresh_token = "initial_refresh_token",
};

// response for re-deeming code
mockHttp.When("/connect/token")
.WithFormData("grant_type", "authorization_code")
.Respond("application/json", JsonSerializer.Serialize(initialTokenResponse));

// short token lifetime should trigger refresh on 1st use
var refreshTokenResponse = new
{
Expand All @@ -209,13 +209,13 @@ public async Task Short_token_lifetime_should_trigger_refresh()
expires_in = 10,
refresh_token = "refreshed1_refresh_token",
};

// response for refresh 1
mockHttp.When("/connect/token")
.WithFormData("grant_type", "refresh_token")
.WithFormData("refresh_token", "initial_refresh_token")
.Respond("application/json", JsonSerializer.Serialize(refreshTokenResponse));

// short token lifetime should trigger refresh on 2nd use
var refreshTokenResponse2 = new
{
Expand All @@ -224,18 +224,18 @@ public async Task Short_token_lifetime_should_trigger_refresh()
expires_in = 3600,
refresh_token = "refreshed2_refresh_token",
};

// response for refresh 1
mockHttp.When("/connect/token")
.WithFormData("grant_type", "refresh_token")
.WithFormData("refresh_token", "refreshed1_refresh_token")
.Respond("application/json", JsonSerializer.Serialize(refreshTokenResponse2));


// setup host
await AppHost.InitializeAsync();
await AppHost.LoginAsync("alice");

// first request should trigger refresh
var response = await AppHost.BrowserClient.GetAsync(AppHost.Url("/user_token"));
var token = await response.Content.ReadFromJsonAsync<UserToken>();
Expand All @@ -246,7 +246,7 @@ public async Task Short_token_lifetime_should_trigger_refresh()
token.AccessTokenType.ShouldBe("token_type1");
token.RefreshToken.ShouldBe("refreshed1_refresh_token");
token.Expiration.ShouldNotBe(DateTimeOffset.MaxValue);

// second request should trigger refresh
response = await AppHost.BrowserClient.GetAsync(AppHost.Url("/user_token"));
token = await response.Content.ReadFromJsonAsync<UserToken>();
Expand All @@ -257,7 +257,7 @@ public async Task Short_token_lifetime_should_trigger_refresh()
token.AccessTokenType.ShouldBe("token_type2");
token.RefreshToken.ShouldBe("refreshed2_refresh_token");
token.Expiration.ShouldNotBe(DateTimeOffset.MaxValue);

// third request should not trigger refresh
response = await AppHost.BrowserClient.GetAsync(AppHost.Url("/user_token"));
token = await response.Content.ReadFromJsonAsync<UserToken>();
Expand All @@ -275,7 +275,7 @@ public async Task Resources_get_distinct_tokens()
{
var mockHttp = new MockHttpMessageHandler();
AppHost.IdentityServerHttpHandler = mockHttp;

// no resource specified
var initialTokenResponse = new
{
Expand All @@ -288,7 +288,7 @@ public async Task Resources_get_distinct_tokens()
mockHttp.When("/connect/token")
.WithFormData("grant_type", "authorization_code")
.Respond("application/json", JsonSerializer.Serialize(initialTokenResponse));

// resource 1 specified
var resource1TokenResponse = new
{
Expand All @@ -301,7 +301,7 @@ public async Task Resources_get_distinct_tokens()
.WithFormData("grant_type", "refresh_token")
.WithFormData("resource", "urn:api1")
.Respond("application/json", JsonSerializer.Serialize(resource1TokenResponse));

// resource 2 specified
var resource2TokenResponse = new
{
Expand All @@ -314,11 +314,11 @@ public async Task Resources_get_distinct_tokens()
.WithFormData("grant_type", "refresh_token")
.WithFormData("resource", "urn:api2")
.Respond("application/json", JsonSerializer.Serialize(resource2TokenResponse));

// setup host
await AppHost.InitializeAsync();
await AppHost.LoginAsync("alice");

// first request - no resource
var response = await AppHost.BrowserClient.GetAsync(AppHost.Url("/user_token"));
var token = await response.Content.ReadFromJsonAsync<UserToken>();
Expand All @@ -328,7 +328,7 @@ public async Task Resources_get_distinct_tokens()
token.AccessToken.ShouldBe("access_token_without_resource");
token.RefreshToken.ShouldBe("initial_refresh_token");
token.Expiration.ShouldNotBe(DateTimeOffset.MaxValue);

// second request - with resource api1
response = await AppHost.BrowserClient.GetAsync(AppHost.Url("/user_token_with_resource/urn:api1"));
token = await response.Content.ReadFromJsonAsync<UserToken>();
Expand All @@ -346,7 +346,55 @@ public async Task Resources_get_distinct_tokens()
token.ShouldNotBeNull();
token.IsError.ShouldBeFalse();
token.AccessToken.ShouldBe("urn:api2_access_token");
token.RefreshToken.ShouldBe("initial_refresh_token");
token.RefreshToken.ShouldBe("initial_refresh_token");
token.Expiration.ShouldNotBe(DateTimeOffset.MaxValue);
}
}

[Fact]
public async Task Refresh_responses_without_refresh_token_use_old_refresh_token()
{
var mockHttp = new MockHttpMessageHandler();
AppHost.IdentityServerHttpHandler = mockHttp;

// short token lifetime should trigger refresh on 1st use
var initialTokenResponse = new
{
id_token = IdentityServerHost.CreateIdToken("1", "web"),
access_token = "initial_access_token",
token_type = "token_type",
expires_in = 10,
refresh_token = "initial_refresh_token",
};

// response for re-deeming code
mockHttp.When("/connect/token")
.WithFormData("grant_type", "authorization_code")
.Respond("application/json", JsonSerializer.Serialize(initialTokenResponse));

// note lack of refresh_token
var refreshTokenResponse = new
{
access_token = "refreshed1_access_token",
token_type = "token_type1",
expires_in = 3600,
};

// response for refresh
mockHttp.When("/connect/token")
.WithFormData("grant_type", "refresh_token")
.WithFormData("refresh_token", "initial_refresh_token")
.Respond("application/json", JsonSerializer.Serialize(refreshTokenResponse));

// setup host
await AppHost.InitializeAsync();
await AppHost.LoginAsync("alice");

// first request should trigger refresh
var response = await AppHost.BrowserClient.GetAsync(AppHost.Url("/user_token"));
var token = await response.Content.ReadFromJsonAsync<UserToken>();

token.ShouldNotBeNull();
token.IsError.ShouldBeFalse();
token.RefreshToken.ShouldBe("initial_refresh_token");
}
}
Loading