I have a Blazor server-side app and in Startup.cs
I have the following:
services.AddSingleton<ITicketStore, CookieMemoryTicketStore>();
services.AddOptions<CookieAuthenticationOptions>(CookieAuthenticationDefaults.AuthenticationScheme)
.Configure<ITicketStore>((options, store) =>
{
options.ExpireTimeSpan = TimeSpan.FromDays(14);
options.SlidingExpiration = true;
options.SessionStore = store;
});
I use a ITicketStore
implementation to store the cookies using MemoryCache
. and then the authentication set up:
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
})
.AddCookie(CookieAuthenticationDefaults.AuthenticationScheme)
.AddOpenIdConnect(OpenIdConnectDefaults.AuthenticationScheme, options =>
{
options.Authority = "authority";
options.ClientId = "clientid";
options.ClientSecret = "clientsecret";
options.ResponseType = OpenIdConnectResponseType.Code;
options.ResponseMode = OpenIdConnectResponseMode.FormPost;
options.GetClaimsFromUserInfoEndpoint = true;
options.MapInboundClaims = false;
options.SaveTokens = true;
options.UseTokenLifetime = false;
options.UseSecurityTokenValidator = true;
options.Scope.Add(OpenIdConnectScope.OpenIdProfile);
options.Scope.Add(OpenIdConnectScope.Email);
options.Scope.Add(OpenIdConnectScope.OfflineAccess);
options.TokenValidationParameters = new TokenValidationParameters
{
NameClaimType = "name",
RoleClaimType = "role"
};
});
services.AddAuthorizationCore();
And in my _Host.cshtml
I do:
var tokens = new InitialApplicationState
{
AccessToken = await HttpContext.GetTokenAsync(Token.Access),
IdToken = await HttpContext.GetTokenAsync(Token.Id),
RefreshToken = await HttpContext.GetTokenAsync(Token.Refresh),
};
And the InitialApplicationState
is then used in App.razor
.
And during my login process (on login redirect from the auth provider), I start with:
var oidcOptions = new OidcClientOptions
{
Authority = "issuer",
ClientId = "clientid",
LoadProfile = false,
Scope = StandardScopes.OpenId
};
var client = new OidcClient(oidcOptions);
var state = await client.GetUserInfoAsync(tokenProvider.AccessToken, ct);
Using the above everything works fine for a few hours. Then all of a sudden, when the user is automatically authenticated (his authenication cookies are still stored in the browser), the call to GetUserInfoAsync
fails with an exception because tokenProvider.AccessToken
is null
.
The only workaround so far is to delete all the site cookies and then authentication/authorization works again.
NOTE
The project was originally created with NET5 but the target framework currently used in NET8. However, it does not use the NET8-specific features.
Has anyone encountered this problem? Is the above code missing some steps?
UPDATE
When I do (in _Host.cshtml):
var authState = await HttpContext.AuthenticateAsync();
I get Failure
with the message 'Unprotect ticket failed' and it enters an infinite loop refreshing the page.
I added the following cookie event:
options.Events.OnSignedIn = context =>
{
context.Properties.IsPersistent = true;
return Task.CompletedTask;
};
The Properties
has 11 items and there I can see the access and refresh tokens correctly.