Skip to content
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 @@ -159,7 +159,7 @@
var issuer = $"https://{options.Domain}/";
var sid = context.Principal?.FindFirst("sid")?.Value;

var isLoggedOut = await logoutTokenHandler.IsLoggedOutAsync(issuer, sid);

Check warning on line 162 in src/Auth0.AspNetCore.Authentication/AuthenticationBuilderExtensions.cs

View workflow job for this annotation

GitHub Actions / build (net8.0)

Possible null reference argument for parameter 'sid' in 'Task<bool> ILogoutTokenHandler.IsLoggedOutAsync(string issuer, string sid)'.

Check warning on line 162 in src/Auth0.AspNetCore.Authentication/AuthenticationBuilderExtensions.cs

View workflow job for this annotation

GitHub Actions / build (net8.0)

Possible null reference argument for parameter 'sid' in 'Task<bool> ILogoutTokenHandler.IsLoggedOutAsync(string issuer, string sid)'.

Check warning on line 162 in src/Auth0.AspNetCore.Authentication/AuthenticationBuilderExtensions.cs

View workflow job for this annotation

GitHub Actions / build (net6.0)

Possible null reference argument for parameter 'sid' in 'Task<bool> ILogoutTokenHandler.IsLoggedOutAsync(string issuer, string sid)'.

Check warning on line 162 in src/Auth0.AspNetCore.Authentication/AuthenticationBuilderExtensions.cs

View workflow job for this annotation

GitHub Actions / build (net6.0)

Possible null reference argument for parameter 'sid' in 'Task<bool> ILogoutTokenHandler.IsLoggedOutAsync(string issuer, string sid)'.

Check warning on line 162 in src/Auth0.AspNetCore.Authentication/AuthenticationBuilderExtensions.cs

View workflow job for this annotation

GitHub Actions / build (net7.0)

Possible null reference argument for parameter 'sid' in 'Task<bool> ILogoutTokenHandler.IsLoggedOutAsync(string issuer, string sid)'.

Check warning on line 162 in src/Auth0.AspNetCore.Authentication/AuthenticationBuilderExtensions.cs

View workflow job for this annotation

GitHub Actions / build (net7.0)

Possible null reference argument for parameter 'sid' in 'Task<bool> ILogoutTokenHandler.IsLoggedOutAsync(string issuer, string sid)'.

if (isLoggedOut)
{
Expand Down Expand Up @@ -210,7 +210,8 @@
}
else
{
context.Properties.UpdateTokenValue("refresh_token", null!);
// Remove the refresh token when refresh fails to ensure OnMissingRefreshToken is called on subsequent requests
context.Properties.Items.Remove(".Token.refresh_token");
}

context.ShouldRenew = true;
Expand Down Expand Up @@ -248,7 +249,7 @@
{
if (oidcOptions.Configuration == null)
{
oidcOptions.Configuration = await oidcOptions.ConfigurationManager.GetConfigurationAsync(context.RequestAborted);

Check warning on line 252 in src/Auth0.AspNetCore.Authentication/AuthenticationBuilderExtensions.cs

View workflow job for this annotation

GitHub Actions / build (net8.0)

Dereference of a possibly null reference.

Check warning on line 252 in src/Auth0.AspNetCore.Authentication/AuthenticationBuilderExtensions.cs

View workflow job for this annotation

GitHub Actions / build (net8.0)

Dereference of a possibly null reference.

Check warning on line 252 in src/Auth0.AspNetCore.Authentication/AuthenticationBuilderExtensions.cs

View workflow job for this annotation

GitHub Actions / build (net6.0)

Dereference of a possibly null reference.

Check warning on line 252 in src/Auth0.AspNetCore.Authentication/AuthenticationBuilderExtensions.cs

View workflow job for this annotation

GitHub Actions / build (net6.0)

Dereference of a possibly null reference.

Check warning on line 252 in src/Auth0.AspNetCore.Authentication/AuthenticationBuilderExtensions.cs

View workflow job for this annotation

GitHub Actions / build (net7.0)

Dereference of a possibly null reference.

Check warning on line 252 in src/Auth0.AspNetCore.Authentication/AuthenticationBuilderExtensions.cs

View workflow job for this annotation

GitHub Actions / build (net7.0)

Dereference of a possibly null reference.
}

var additionalConfiguration = oidcOptions.Configuration.AdditionalData;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1758,5 +1758,86 @@ public async Task Should_Clear_Cookies_When_Logging_Out_Using_Custom_Cookie_Sche
}
}
}

[Fact]
public async Task Should_Call_OnMissingRefreshToken_After_Refresh_Fails()
{
var nonce = "";
var configuration = TestConfiguration.GetConfiguration();
var domain = configuration["Auth0:Domain"];
var clientId = configuration["Auth0:ClientId"];
var onMissingRefreshTokenCalled = false;

var mockHandler = new OidcMockBuilder()
.MockOpenIdConfig()
.MockJwks()
// Mock initial token with very short expiration (1 second) to trigger refresh on second request
.MockToken(() => JwtUtils.GenerateToken(1, $"https://{domain}/", clientId, null, nonce, DateTime.UtcNow.AddSeconds(20)), (me) => me.HasGrantType("authorization_code"), 1)
// Mock the refresh token endpoint to fail
.MockToken(() => JwtUtils.GenerateToken(1, $"https://{domain}/", clientId, null, null, DateTime.UtcNow.AddSeconds(20)), (me) => me.HasGrantType("refresh_token"), 20, true, HttpStatusCode.BadRequest)
.Build();

using (var server = TestServerBuilder.CreateServer(opts =>
{
opts.ClientSecret = "123";
opts.Backchannel = new HttpClient(mockHandler.Object);
}, opts =>
{
opts.Audience = "123";
opts.Events = new Auth0WebAppWithAccessTokenEvents
{
OnMissingRefreshToken = (context) =>
{
onMissingRefreshTokenCalled = true;
context.Response.Redirect("http://missing.rt/");
return Task.CompletedTask;
}
};
opts.UseRefreshTokens = true;
}))
{
using (var client = server.CreateClient())
{
var loginResponse = (await client.SendAsync($"{TestServerBuilder.Host}/{TestServerBuilder.Login}"));
var setCookie = Assert.Single(loginResponse.Headers, h => h.Key == "Set-Cookie");

var queryParameters = UriUtils.GetQueryParams(loginResponse.Headers.Location);

// Keep track of the nonce as we need to:
// - Send it to the `/oauth/token` endpoint
// - Include it in the generated ID Token
nonce = queryParameters["nonce"];

// Keep track of the state as we need to:
// - Send it to the `/oauth/token` endpoint
var state = queryParameters["state"];

var message = new HttpRequestMessage(HttpMethod.Get, $"{TestServerBuilder.Host}/{TestServerBuilder.Callback}?state={state}&nonce={nonce}&code=123");

// Pass along the Set-Cookies to ensure `Nonce` and `Correlation` cookies are set.
var callbackResponse = (await client.SendAsync(message, setCookie.Value));

// Wait for token to expire (1 second + some buffer)
await Task.Delay(2000);

// First request after token expires - this will trigger refresh (which will fail), clearing the refresh token
var firstResponse = await client.SendAsync($"{TestServerBuilder.Host}/{TestServerBuilder.Process}", callbackResponse.Headers.GetValues("Set-Cookie"));
var firstContent = JObject.Parse(await firstResponse.Content.ReadAsStringAsync());

// Verify refresh token was cleared after failed refresh
firstContent.GetValue("RefreshToken").Value<string>().Should().BeNull();
onMissingRefreshTokenCalled.Should().BeFalse();

// Second request - now OnMissingRefreshToken should be called since refresh token is missing
var secondResponse = await client.SendAsync($"{TestServerBuilder.Host}/{TestServerBuilder.Process}", firstResponse.Headers.GetValues("Set-Cookie"));

// Verify OnMissingRefreshToken was called
onMissingRefreshTokenCalled.Should().BeTrue();
secondResponse.Headers.Location.AbsoluteUri.Should().Be("http://missing.rt/");

mockHandler.Verify();
}
}
}
}
}
Loading