DuendeSoftware / Support

Support for Duende Software products
20 stars 0 forks source link

Blazor Web App and Duende IdentityServer keeps getting logged out #1358

Closed obrungot closed 4 weeks ago

obrungot commented 1 month ago

Which version of Duende IdentityServer are you using? 7

Which version of .NET are you using? 8

Describe the bug I've set up the following,

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>(),
        Pos
```tLogoutRedirectUris = 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();


**Expected behavior**

I expect that a refresh token is used, that keeps the user logged in until he logs out. As it is now, he's redirected back to log-in at identityserver after 5 minutes idle time.
RolandGuijt commented 1 month ago

The lifetimes of the session cookie and the tokens don't have anything to do with each other. In addition the refresh token is used to refresh the access token, not the session. So we have to focus on how the session is configured here. Can you please share how the cookie scheme (CookieAuthenticationDefaults.AuthenticationScheme) is setup? It might be in the ConfigureAuthentication method.

obrungot commented 1 month ago

The server (shared hosting) was set to shutdown on 5 minutes user idle time, which probably caused the issue (alongside with me not grasping fully the session cookie and the tokens). Increasing the idle time, resolved the my immediate issue having the user being forced to sign in every 5 minutes of idle time.

I guess storing tokens in persistent cookies having them persists even if the session is ended is bad practice (security). Btw, love your courses on PluralSight 👍

Here's the setup

builder.Services.AddAuthentication(options =>
{
    options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
    options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
})
    .AddCookie(CookieAuthenticationDefaults.AuthenticationScheme, options =>
    {
        options.Events.OnSigningOut = async context =>
        {
            await context.HttpContext.RevokeRefreshTokenAsync();
        };
    })
    .AddOpenIdConnect(OpenIdConnectDefaults.AuthenticationScheme, options =>
    {
        options.Authority = builder.Configuration.GetSection("IDP:Authority").Get<string>();
        options.ClientId = "blazor.webapp.authentication";
        options.ClientSecret = "secret";
        options.GetClaimsFromUserInfoEndpoint = true;
        options.MapInboundClaims = true; //May need to set to true if you are using custom claims
        options.TokenValidationParameters.NameClaimType = JwtRegisteredClaimNames.Name;

        options.TokenValidationParameters.RoleClaimType = "role";
        options.ResponseType = OpenIdConnectResponseType.Code;
        options.SaveTokens = true;

        //options.Scope.Clear();
        options.Scope.Add(OpenIdConnectScope.OfflineAccess);
        options.Scope.Add(OpenIdConnectScope.OpenIdProfile);

        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();

builder.Services.AddDistributedMemoryCache();

builder.Services.AddCascadingAuthenticationState();
builder.Services.AddScoped<AuthenticationStateProvider, PersistingAuthenticationStateProvider>();

builder.Services.AddAuthorization();

builder.Services.AddHttpClient("api", options =>
{
    options.BaseAddress = new Uri(builder.Configuration["BaseAddress"]);
});
RolandGuijt commented 4 weeks ago

Thanks! Appreciate it. I have the feeling we can close this issue. If you have something to add please reopen.