Quantcast
Channel: Active questions tagged blazor - Stack Overflow
Viewing all articles
Browse latest Browse all 4844

Blazor Web App and Duende IdentityServer keeps getting logged out

$
0
0

I've set up the following,

  • Duende IdentityServer (IDP)
  • API
  • Blazor Web App (.Net 8)The Web App consists of a Server and Client project.

When logging in I get authenticated and able to retrieve data from the API. All is good. The problem is that I get logged out if the user is idle for about 5 minutes.

I've set up offline_access for the use of reqest token, but whatever changes I try - I still get logged off having 5 min of idle time.

I've even set the access token lifetime to 2 hours on the IDP config as a workaround, but that seems to have no effect. A little bit of idle time and I'm routed back to the login page of the IDP.

AccessTokenLifetime = 7200
IdentityTokenLifetime = 7200

Not quite sure where to look anymore. Anyone who can shed some light on what I am missing?

Here's some of the most relevant setup

--------- Duende IDP Config --------------------------

new Client{    ClientId = "test.blazor.webapp",    ClientName = "TestApp",    RequireClientSecret = true,    AllowedGrantTypes = GrantTypes.Code,    RedirectUris = configuration.GetSection("BlazorWebApp:RedirectUris").Get<string[]>(),    FrontChannelLogoutUri = configuration.GetSection("BlazorWebApp:FrontChannelLogoutUri").Get<string>(),    PostLogoutRedirectUris = configuration.GetSection("BlazorWebApp:PostLogoutRedirectUris").Get<string[]>(),    AccessTokenLifetime = 7200,               IdentityTokenLifetime = 7200,          AllowOfflineAccess = true,    AllowedScopes = { "openid", "profile", "roles", "testapi" }},

------------Server Blazor-----------------------------------

builder.Services.AddRazorComponents()    .AddInteractiveServerComponents()    .AddInteractiveWebAssemblyComponents();builder.Services.ConfigureAuthentication(builder);builder.Services.ConfigureCookieOidcRefresh(CookieAuthenticationDefaults.AuthenticationScheme, OpenIdConnectDefaults.AuthenticationScheme);builder.Services.AddDistributedMemoryCache();builder.Services.AddCascadingAuthenticationState();builder.Services.AddScoped<AuthenticationStateProvider, PersistingAuthenticationStateProvider>();builder.Services.AddAuthorization();

------------Server Blazor (ConfigureAuthentication)-----------------------------------

.AddOpenIdConnect(OpenIdConnectDefaults.AuthenticationScheme, options =>{  options.Authority = builder.Configuration.GetSection("IDP:Authority").Get<string>();  options.ClientId = "test.blazor.webapp";  options.GetClaimsFromUserInfoEndpoint = true;  options.MapInboundClaims = false;   options.TokenValidationParameters.NameClaimType = JwtRegisteredClaimNames.Name;  options.TokenValidationParameters.RoleClaimType = "role";  options.ResponseType = OpenIdConnectResponseType.Code;  options.SaveTokens = false;    options.Scope.Add(OpenIdConnectScope.OfflineAccess);  options.Scope.Add(OpenIdConnectScope.OpenIdProfile);  options.Scope.Add("roles");  options.Scope.Add("testapi");  options.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;  options.CallbackPath = new PathString("/signin-oidc");  options.SignedOutCallbackPath = new PathString("/signout-callback-oidc");  options.RemoteSignOutPath = new PathString("/signout-oidc");});  builder.Services.AddOpenIdConnectAccessTokenManagement();  return services;}

----------------Client Blazor -------------------------------

var builder = WebAssemblyHostBuilder.CreateDefault(args);builder.Services.AddAuthorizationCore();builder.Services.AddCascadingAuthenticationState();builder.Services.AddSingleton<AuthenticationStateProvider, PersistentAuthenticationStateProvider>();builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });await builder.Build().RunAsync();

--- PersistentAuthenticationStateProvider.cs (blazor client) ---

internal sealed class PersistentAuthenticationStateProvider : AuthenticationStateProvider{  private static readonly Task<AuthenticationState> defaultUnauthenticatedTask =      Task.FromResult(new AuthenticationState(new ClaimsPrincipal(new ClaimsIdentity())));  private readonly Task<AuthenticationState> authenticationStateTask = defaultUnauthenticatedTask;  public PersistentAuthenticationStateProvider(PersistentComponentState state)  {    if (!state.TryTakeFromJson<UserInfo>(nameof(UserInfo), out var userInfo) || userInfo is null)    {      return;    }    authenticationStateTask = Task.FromResult(new AuthenticationState(userInfo.ToClaimsPrincipal()));  }

public override Task GetAuthenticationStateAsync() => authenticationStateTask;}

--- PersistingAuthenticationStateProvider.cs (blazor server) -----

internal sealed class PersistingAuthenticationStateProvider : AuthenticationStateProvider, IHostEnvironmentAuthenticationStateProvider, IDisposable{    private const string CLAIM_NAME = "name";    private const string CLAIM_NAMEIDENTIFIER = "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier";    private readonly IAppUsersApiService _appUsersApiService;    private readonly HttpClient _httpClient;    private readonly IUserApiService _userApiService;    private readonly PersistentComponentState persistentComponentState;    private readonly PersistingComponentStateSubscription subscription;    private Task<AuthenticationState>? authenticationStateTask;    public PersistingAuthenticationStateProvider(PersistentComponentState state, HttpClient httpClient, IUserApiService userApiService, IAppUsersApiService appUsersApiService)    {        persistentComponentState = state;        subscription = state.RegisterOnPersisting(OnPersistingAsync, RenderMode.InteractiveWebAssembly);        _httpClient = httpClient ?? throw new ArgumentNullException(nameof(httpClient));        _userApiService = userApiService ?? throw new ArgumentNullException(nameof(userApiService));        _appUsersApiService = appUsersApiService ?? throw new ArgumentNullException(nameof(appUsersApiService));    }    public async override Task<AuthenticationState> GetAuthenticationStateAsync()    {        if (authenticationStateTask == null)        {            throw new InvalidOperationException(                $"Do not call {nameof(GetAuthenticationStateAsync)} " +"outside of the DI scope for a Razor component. " +"Typically, this means you can call it only within a Razor component or inside another DI service that is resolved for a Razor component."            );        }        else        {            if (authenticationStateTask.Result?.User?.Identity?.IsAuthenticated == true)            {                // Check if user exists, if not create it                try                {                    var user = await _appUsersApiService.Get();                }                catch (HttpRequestException e)                {                    if (e.StatusCode == System.Net.HttpStatusCode.NotFound)                    {                        //User does not exist, create it                        var email = authenticationStateTask.Result.User.Claims.FirstOrDefault(c => c.Type == CLAIM_NAME)?.Value;                        var userId = (authenticationStateTask.Result.User.Claims.FirstOrDefault(c => c.Type == CLAIM_NAMEIDENTIFIER)?.Value);                        if(userId == null)                        {                            userId = (authenticationStateTask.Result.User.Claims.FirstOrDefault(c => c.Type == "sub")?.Value);                        }                        if(userId == null)                        {                            throw new Exception("User id is null or empty");                        }                                                var newUser = new AppUserForCreateDto                        {                            Email = email,                            IdentityId = Guid.Parse(userId)                        };                        await _userApiService.CreateUser(newUser);                    }                    else                    {                        throw;                    }                }            }        }        return authenticationStateTask.Result;    }    //public override Task<AuthenticationState> GetAuthenticationStateAsync() =>    //    authenticationStateTask ?? throw new InvalidOperationException(    //        $"Do not call {nameof(GetAuthenticationStateAsync)} " +    //        "outside of the DI scope for a Razor component. " +    //        "Typically, this means you can call it only within a Razor component or inside another DI service that is resolved for a Razor component."    //    );    public void SetAuthenticationState(Task<AuthenticationState> task)    {        authenticationStateTask = task;    }    private async Task OnPersistingAsync()    {        var authenticationState = await GetAuthenticationStateAsync();        var principal = authenticationState.User;        if (principal.Identity?.IsAuthenticated == true)        {            persistentComponentState.PersistAsJson(nameof(UserInfo), UserInfo.FromClaimsPrincipal(principal));        }    }    public void Dispose()    {        subscription.Dispose();    }}

After more examination, it seems to happen when the server shutdown due to user idle time (which is set to 5 minutes). When restarting on the next request it does not seem to have it's refresh_token available anymore.


Viewing all articles
Browse latest Browse all 4844

Trending Articles



<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>