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

Conversation

Des-Webster
Copy link

Description

The current SDK defaults the SetTokens property to true, which stores the ID, access, and refresh tokens in the Authentication cookie. This behaviour is the opposite of how the OpenIdConnect SDK works and increases the size of the cookies.

This PR allows users to configure the SaveTokens property via Auth0WebAppOptions.cs. The default value of this property is true, which ensures there are no breaking changes for current implementations.

References

https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.builder.openidconnectoptions

image

Testing

To test this feature works, I followed the structure of some of the other integration tests that sign the user into the application. The easiest way to prove whether the tokens were present in the authorisation cookie was to create another method on the Account controller and access HttpContext directly. I created two tests: -

  • Don't Save - I set the SaveTokens to false using Auth0WebAppOptions.
  • Save - I don't set anything to prove there are no breaking changes.

@frederikprijck
Copy link
Member

frederikprijck commented May 21, 2024

What would happen when SaveTokens is set to false, and the SDK is configured to useWithAccessToken? Can we add a test that ensures this still works?

For context, mostly talking about this code:

if (context.Properties.Items.TryGetValue(".Token.access_token", out _))
{
if (optionsWithAccessToken.UseRefreshTokens)
{
if (context.Properties.Items.TryGetValue(".Token.refresh_token", out var refreshToken))
{
var now = DateTimeOffset.Now;
var expiresAt = DateTimeOffset.Parse(context.Properties.Items[".Token.expires_at"]!);
var leeway = 60;
var difference = DateTimeOffset.Compare(expiresAt, now.AddSeconds(leeway));
var isExpired = difference <= 0;
if (isExpired && !string.IsNullOrWhiteSpace(refreshToken))
{
var result = await RefreshTokens(options, refreshToken, oidcOptions.Backchannel);
if (result != null)
{
context.Properties.UpdateTokenValue("access_token", result.AccessToken);
if (!string.IsNullOrEmpty(result.RefreshToken))
{
context.Properties.UpdateTokenValue("refresh_token", result.RefreshToken);
}
context.Properties.UpdateTokenValue("id_token", result.IdToken);
context.Properties.UpdateTokenValue("expires_at", DateTimeOffset.Now.AddSeconds(result.ExpiresIn).ToString("o"));
}
else
{
context.Properties.UpdateTokenValue("refresh_token", null!);
}
context.ShouldRenew = true;
}
}
else
{
if (optionsWithAccessToken.Events?.OnMissingRefreshToken != null)
{
await optionsWithAccessToken.Events.OnMissingRefreshToken(context.HttpContext);
}
}
}
}
else
{
if (CodeResponseTypes.Contains(options.ResponseType!))
{
if (optionsWithAccessToken.Events?.OnMissingAccessToken != null)
{
await optionsWithAccessToken.Events.OnMissingAccessToken(context.HttpContext);
}
}
}
}

@Des-Webster
Copy link
Author

What would happen when SaveTokens is set to false, and the SDK is configured to useWithAccessToken? Can we add a test that ensures this still works?

For context, mostly talking about this code:

if (context.Properties.Items.TryGetValue(".Token.access_token", out _))
{
if (optionsWithAccessToken.UseRefreshTokens)
{
if (context.Properties.Items.TryGetValue(".Token.refresh_token", out var refreshToken))
{
var now = DateTimeOffset.Now;
var expiresAt = DateTimeOffset.Parse(context.Properties.Items[".Token.expires_at"]!);
var leeway = 60;
var difference = DateTimeOffset.Compare(expiresAt, now.AddSeconds(leeway));
var isExpired = difference <= 0;
if (isExpired && !string.IsNullOrWhiteSpace(refreshToken))
{
var result = await RefreshTokens(options, refreshToken, oidcOptions.Backchannel);
if (result != null)
{
context.Properties.UpdateTokenValue("access_token", result.AccessToken);
if (!string.IsNullOrEmpty(result.RefreshToken))
{
context.Properties.UpdateTokenValue("refresh_token", result.RefreshToken);
}
context.Properties.UpdateTokenValue("id_token", result.IdToken);
context.Properties.UpdateTokenValue("expires_at", DateTimeOffset.Now.AddSeconds(result.ExpiresIn).ToString("o"));
}
else
{
context.Properties.UpdateTokenValue("refresh_token", null!);
}
context.ShouldRenew = true;
}
}
else
{
if (optionsWithAccessToken.Events?.OnMissingRefreshToken != null)
{
await optionsWithAccessToken.Events.OnMissingRefreshToken(context.HttpContext);
}
}
}
}
else
{
if (CodeResponseTypes.Contains(options.ResponseType!))
{
if (optionsWithAccessToken.Events?.OnMissingAccessToken != null)
{
await optionsWithAccessToken.Events.OnMissingAccessToken(context.HttpContext);
}
}
}
}

Hi, I was wondering whether the new test was sufficient to pass your approval process? I understand it may take a while to pass your internal approval process.

@frederikprijck
Copy link
Member

frederikprijck commented Jun 12, 2024

Thanks for adding that test.

It looks like what this test is covering, is:

  • if SaveTokens is set to false, we ignore the fact that the user also set UseRefreshTokens to true.

Do you think this is expected for a user? Why can they not use a refresh token when SaveTokens is set to false?

I think it makes sense for the SDK to support setting SaveTokens to false, but I believe there is more to it than just passing along the value for SaveTokens. I believe we either need to:

  • Ensure the user can not set UseRefreshTokens to true, when SaveTokens is set to true
  • Or, the SDK can handle a way from combining the two (SaveTokens set to true, UseRefreshTokens set to true).

Option 1 should probably be sufficient tho.

@Des-Webster
Copy link
Author

Des-Webster commented Jun 14, 2024

The problem we have is that we want to use refresh tokens, but we don't want the sdk to automatically refresh them. We are working on a microservices platform where: -

  • Backend services are written in .Net
  • React frontend & GraphQL to retrieve data from the services
  • Api Gateway which blocks calls without access token

All of the above components need access to the tokens, so it doesn't make sense for us to store these in the .Net authentication cookie of our 'Auth' service. As we can't currently control this, we hit header size errors as we pass our tokens and the .Net authentication cookie.

We built our own mechanism to refresh the token, as the user only uses the Auth service for login and profile management.

@frederikprijck
Copy link
Member

Thanks for sharing that. That does make sense.

I wonder, that in this scenario, would it work for you to add the following after adding our SDK?

services.Configure<OpenIdConnectOptions>(Auth0Constants.AuthenticationScheme, options => {
      options.SaveTokens = false;
});

If the above works, that would be an easy workaround.

@Des-Webster
Copy link
Author

I will give it a test today and get back to you.

@Des-Webster
Copy link
Author

Des-Webster commented Jun 14, 2024

I can confirm that the above workaround solves our problem. We will use this, as it means we can stop using our custom version of the sdk. Thanks for the help.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants