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

Allow SaveTokens to be set to false #140

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
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
8 changes: 8 additions & 0 deletions src/Auth0.AspNetCore.Authentication/Auth0WebAppOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -151,5 +151,13 @@ public class Auth0WebAppOptions
/// Set the target to the current scheme to disable forwarding and allow normal processing.
/// </summary>
public string? ForwardSignOut { get; set; }

/// <summary>
/// Defines whether access and refresh tokens should be stored in the <see cref="AuthenticationProperties"/>
/// after a successful authorization. This property is set to <c>true</c> 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.
/// </summary>
public bool SaveTokens { get; set; } = true;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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<HttpRequestMessage>(me => me.IsTokenEndPoint()
&& me.HasGrantType("refresh_token")
&& me.HasClientSecret()),
ItExpr.IsAny<CancellationToken>());
}
}
}

[Fact]
public async void Should_Call_On_Access_Token_Missing()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,18 @@ public IActionResult AccessDenied()
{
return View();
}

public IActionResult Tokens()
{
var authItems = HttpContext.Features.Get<IAuthenticateResultFeature>()?.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<string, string> ObjectToDictionary(object values)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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";

/// <summary>
Expand Down