diff --git a/src/Auth0.AspNetCore.Authentication/Auth0WebAppOptions.cs b/src/Auth0.AspNetCore.Authentication/Auth0WebAppOptions.cs
index e72fdd4..bdf1324 100644
--- a/src/Auth0.AspNetCore.Authentication/Auth0WebAppOptions.cs
+++ b/src/Auth0.AspNetCore.Authentication/Auth0WebAppOptions.cs
@@ -151,5 +151,13 @@ public class Auth0WebAppOptions
/// Set the target to the current scheme to disable forwarding and allow normal processing.
///
public string? ForwardSignOut { get; set; }
+
+ ///
+ /// Defines whether access and refresh tokens should be stored in the
+ /// after a successful authorization. This property is set to true by default to
+ /// ensure there are no breaking changes with previous versions of the sdk. Storing tokens will
+ /// increase the size of the final authentication cookie.
+ ///
+ public bool SaveTokens { get; set; } = true;
}
}
diff --git a/src/Auth0.AspNetCore.Authentication/AuthenticationBuilderExtensions.cs b/src/Auth0.AspNetCore.Authentication/AuthenticationBuilderExtensions.cs
index 6aa9c0b..e079773 100644
--- a/src/Auth0.AspNetCore.Authentication/AuthenticationBuilderExtensions.cs
+++ b/src/Auth0.AspNetCore.Authentication/AuthenticationBuilderExtensions.cs
@@ -91,7 +91,7 @@ private static void ConfigureOpenIdConnect(OpenIdConnectOptions oidcOptions, Aut
oidcOptions.Scope.Clear();
oidcOptions.Scope.AddRange(auth0Options.Scope.Split(" "));
oidcOptions.CallbackPath = new PathString(auth0Options.CallbackPath ?? Auth0Constants.DefaultCallbackPath);
- oidcOptions.SaveTokens = true;
+ oidcOptions.SaveTokens = auth0Options.SaveTokens;
oidcOptions.ResponseType = auth0Options.ResponseType ?? oidcOptions.ResponseType;
oidcOptions.Backchannel = auth0Options.Backchannel!;
oidcOptions.MaxAge = auth0Options.MaxAge;
diff --git a/tests/Auth0.AspNetCore.Authentication.IntegrationTests/Auth0MiddlewareTests.cs b/tests/Auth0.AspNetCore.Authentication.IntegrationTests/Auth0MiddlewareTests.cs
index 025542c..abb649a 100644
--- a/tests/Auth0.AspNetCore.Authentication.IntegrationTests/Auth0MiddlewareTests.cs
+++ b/tests/Auth0.AspNetCore.Authentication.IntegrationTests/Auth0MiddlewareTests.cs
@@ -20,6 +20,9 @@
using System.Linq;
using Auth0.AspNetCore.Authentication.Exceptions;
using Microsoft.AspNetCore.Authentication.Cookies;
+using Moq;
+using Moq.Protected;
+using System.Threading;
namespace Auth0.AspNetCore.Authentication.IntegrationTests
{
@@ -1080,6 +1083,111 @@ public async Task Should_Allow_Custom_Token_Validation()
}
}
}
+
+ [Fact]
+ public async Task Should_Allow_Configuring_SaveTokens_To_False()
+ {
+ var nonce = "";
+ var configuration = TestConfiguration.GetConfiguration();
+ var domain = configuration["Auth0:Domain"];
+ var clientId = configuration["Auth0:ClientId"];
+ var mockHandler = new OidcMockBuilder()
+ .MockOpenIdConfig()
+ .MockJwks()
+ .MockToken(() => JwtUtils.GenerateToken(1, $"https://{domain}/", clientId, null, nonce), (me) => me.HasAuth0ClientHeader())
+ .Build();
+
+ using (var server = TestServerBuilder.CreateServer(opt =>
+ {
+ opt.SaveTokens = false;
+ opt.Backchannel = new HttpClient(mockHandler.Object);
+ }))
+ {
+ 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));
+
+ // Retrieve logged in cookies
+ setCookie = Assert.Single(callbackResponse.Headers, h => h.Key == "Set-Cookie");
+ var tokens = new HttpRequestMessage(HttpMethod.Get, $"{TestServerBuilder.Host}/{TestServerBuilder.Tokens}");
+
+ // Pass along the Authentication cooke so we can validate whether the tokens exist
+ var tokenResponse = (await client.SendAsync(tokens, setCookie.Value));
+ var tokenContent = await tokenResponse.Content.ReadAsStringAsync();
+
+ tokenContent.Should().Be("TokensExist=False");
+ }
+ }
+ }
+
+ [Fact]
+ public async Task Should_Have_SaveTokens_To_True()
+ {
+ var nonce = "";
+ var configuration = TestConfiguration.GetConfiguration();
+ var domain = configuration["Auth0:Domain"];
+ var clientId = configuration["Auth0:ClientId"];
+ var mockHandler = new OidcMockBuilder()
+ .MockOpenIdConfig()
+ .MockJwks()
+ .MockToken(() => JwtUtils.GenerateToken(1, $"https://{domain}/", clientId, null, nonce), (me) => me.HasAuth0ClientHeader())
+ .Build();
+
+ using (var server = TestServerBuilder.CreateServer(opt =>
+ {
+ opt.Backchannel = new HttpClient(mockHandler.Object);
+ }))
+ {
+ 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));
+
+ // Retrieve logged in cookies
+ setCookie = Assert.Single(callbackResponse.Headers, h => h.Key == "Set-Cookie");
+ var tokens = new HttpRequestMessage(HttpMethod.Get, $"{TestServerBuilder.Host}/{TestServerBuilder.Tokens}");
+
+ // Pass along the Authentication cooke so we can validate whether the tokens exist
+ var tokenResponse = (await client.SendAsync(tokens, setCookie.Value));
+ var tokenContent = await tokenResponse.Content.ReadAsStringAsync();
+
+ tokenContent.Should().Be("TokensExist=True");
+ }
+ }
+ }
[Fact]
public async void Should_Refresh_Access_Token_When_Expired()
@@ -1429,6 +1537,71 @@ public async void Should_Not_Refresh_Access_Token_When_Not_Expired()
}
}
+ [Fact]
+ public async void Should_Not_Refresh_Access_Token_When_Expired_SaveTokens_False()
+ {
+ var nonce = "";
+ var configuration = TestConfiguration.GetConfiguration();
+ var domain = configuration["Auth0:Domain"];
+ var clientId = configuration["Auth0:ClientId"];
+
+ var mockHandler = new OidcMockBuilder()
+ .MockOpenIdConfig()
+ .MockJwks()
+ .MockToken(() => JwtUtils.GenerateToken(1, $"https://{domain}/", clientId, null, nonce, DateTime.Now.AddSeconds(20)), (me) => me.HasGrantType("authorization_code"), 20)
+ .MockToken(() => JwtUtils.GenerateToken(1, $"https://{domain}/", clientId, null, null, DateTime.Now.AddSeconds(20)), (me) => me.HasGrantType("refresh_token") && me.HasClientSecret(),
+ 20)
+ .Build();
+
+ using (var server = TestServerBuilder.CreateServer(opts =>
+ {
+ opts.ClientSecret = "123";
+ opts.Backchannel = new HttpClient(mockHandler.Object);
+ opts.SaveTokens = false;
+ }, opts =>
+ {
+ opts.Audience = "123";
+ 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));
+
+ callbackResponse.Headers.Location.OriginalString.Should().Be("/");
+
+ var response = await client.SendAsync($"{TestServerBuilder.Host}/{TestServerBuilder.Process}", callbackResponse.Headers.GetValues("Set-Cookie"));
+
+ mockHandler
+ .Protected()
+ .Verify(
+ "SendAsync",
+ Times.Never(),
+ ItExpr.Is(me => me.IsTokenEndPoint()
+ && me.HasGrantType("refresh_token")
+ && me.HasClientSecret()),
+ ItExpr.IsAny());
+ }
+ }
+ }
+
[Fact]
public async void Should_Call_On_Access_Token_Missing()
{
diff --git a/tests/Auth0.AspNetCore.Authentication.IntegrationTests/Controllers/AccountController.cs b/tests/Auth0.AspNetCore.Authentication.IntegrationTests/Controllers/AccountController.cs
index 98dc8a1..7fe95a1 100644
--- a/tests/Auth0.AspNetCore.Authentication.IntegrationTests/Controllers/AccountController.cs
+++ b/tests/Auth0.AspNetCore.Authentication.IntegrationTests/Controllers/AccountController.cs
@@ -101,6 +101,18 @@ public IActionResult AccessDenied()
{
return View();
}
+
+ public IActionResult Tokens()
+ {
+ var authItems = HttpContext.Features.Get()?.AuthenticateResult?.Properties?.Items;
+ if (authItems == null) return BadRequest("Error with authentication result object.");
+ if (authItems.ContainsKey(".Token.access_token")
+ && authItems.ContainsKey(".Token.refresh_token")
+ && authItems.ContainsKey(".Token.id_token"))
+ return Ok($"TokensExist=True");
+ else
+ return Ok($"TokensExist=False");
+ }
private Dictionary ObjectToDictionary(object values)
{
diff --git a/tests/Auth0.AspNetCore.Authentication.IntegrationTests/Infrastructure/TestServerBuilder.cs b/tests/Auth0.AspNetCore.Authentication.IntegrationTests/Infrastructure/TestServerBuilder.cs
index 701636f..29c5be7 100644
--- a/tests/Auth0.AspNetCore.Authentication.IntegrationTests/Infrastructure/TestServerBuilder.cs
+++ b/tests/Auth0.AspNetCore.Authentication.IntegrationTests/Infrastructure/TestServerBuilder.cs
@@ -23,6 +23,7 @@ internal class TestServerBuilder
public static readonly string Process = "Process";
public static readonly string Logout = "Account/Logout";
public static readonly string Callback = "Callback";
+ public static readonly string Tokens = "Account/Tokens";
public static readonly string ExtraProviderScheme = "ExtraProviderScheme";
///