Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Identity Server 4 - Log User Out when Idle

Hej Community,

I got stuck and I need some advice or pointer to a solution. I have a fairly simple Identity Server 4 Setup:

  1. Identity Server 4 with ASP.NET Identity
  2. ASP.NET Core 2.2 MVC Client

I would like to automatically log out the user after 10 minutes of inactivity. In the sample below I used 10 s to make testing somewhat quicker. The authentication, redirect and user enforced logout work as expected and like a charm using the code below. However, when the user idles for longer than the set 10 s, the user is still signed in and is not redirected to the login page at the IDS host.

The MVC client is setup using Hybrid Grant as:

Client Definition

var mvcClient = new Client
{
    ClientId = "account-mvc",
    ClientName = "Account MVC",
    ClientUri = "https://localhost:5002",

    AllowedGrantTypes = GrantTypes.Hybrid,
    ClientSecrets = { new Secret("secret".Sha256()) },

    EnableLocalLogin = true,
    RequireConsent = false,
    AllowOfflineAccess = false,
    AccessTokenLifetime = 10,   // 10 s by intention
    IdentityTokenLifetime = 10, // 10 s by intention

    RedirectUris = "https://localhost:5002/signin-oidc",
    PostLogoutRedirectUris = "https://localhost:5002/signout-callback-oidc",
    FrontChannelLogoutUri = "https://localhost:5002/signout-oidc",

    AllowedScopes =
    {
        IdentityServerConstants.StandardScopes.OpenId,
        IdentityServerConstants.StandardScopes.Profile,
        IdentityServerConstants.StandardScopes.Email,
    },
};

Identity Server Options

services.AddIdentityServer(options =>
{
    options.Authentication.CheckSessionCookieName = "auth-cookie";
    options.Authentication.CookieLifetime = new System.TimeSpan(0, 0, 10);
    options.Authentication.CookieSlidingExpiration = false;

    options.Csp.Level = IdentityServer4.Models.CspLevel.Two;

    options.Events.RaiseErrorEvents = true;
    options.Events.RaiseInformationEvents = true;
    options.Events.RaiseFailureEvents = true;
    options.Events.RaiseSuccessEvents = true;
})
.Add... // Left out for brevity

In the Startup of the MVC client I add:

MVC Client Startup

services.AddAuthentication(options =>
        {
            options.DefaultScheme = "Cookies";
            options.DefaultChallengeScheme = "oidc";
        })
        .AddCookie("Cookies", options => 
        {
            options.ExpireTimeSpan = TimeSpan.FromSeconds(10);
            options.SlidingExpiration = false;
            options.Cookie.Name = "mvc-cookie";
        })
        .AddOpenIdConnect("oidc", options =>
        {
            options.SignInScheme = "Cookies";

            options.Authority = "https://localhost:5001/";
            options.ClientId = "account-mvc";
            options.ClientSecret = "secret";
            options.ResponseType = "code id_token";

            options.SaveTokens = true;
            options.GetClaimsFromUserInfoEndpoint = true;

            options.Scope.Add("openid");
            options.Scope.Add("profile");
            options.Scope.Add("email");
        });

and added app.UseAuthentication() in the Configure method.

Question

How can I make sure that the user is signed out at the Identity Server once the session timed out? Any hint and help appreciated!

like image 729
mmr Avatar asked Mar 04 '23 13:03

mmr


1 Answers

After more debugging I found that the cookie lifetime of the MVC client worked as intended with a sliding expiration. However, once the cookie expired, the Idenity Server (IDS) was contacted and the cookie was refreshed as the session was still alive / active at the IDS.

I figured two solutions from which I decided to use Solution 1 for now and see if it is the most suitable in the long-run.

If anyone has a comment or recommendation towards security and best practice please comment or post another solution.

Solution 1: Maximum SSO lifetime of client

The Client property UserSsoLifetime (available in Identity Server 4 from v2.3) can be used to set the maximum time until a user must re-authenticate to use the client. So for the sample of the question the only required addition is in the Client Definition by adding UserSsoLifetime = 10, such as,

Client Definition

var mvcClient = new Client
{
    ClientId = "account-mvc",
    ClientName = "Account MVC",
    ClientUri = "https://localhost:5002",

    AllowedGrantTypes = GrantTypes.Hybrid,
    ClientSecrets = { new Secret("secret".Sha256()) },

    EnableLocalLogin = true,
    RequireConsent = false,
    AllowOfflineAccess = false,
    UserSsoLifetime = 10,       // <- HERE
    AccessTokenLifetime = 10,   // 10 s by intention
    IdentityTokenLifetime = 10, // 10 s by intention

    RedirectUris = "https://localhost:5002/signin-oidc",
    PostLogoutRedirectUris = "https://localhost:5002/signout-callback-oidc",
    FrontChannelLogoutUri = "https://localhost:5002/signout-oidc",

    AllowedScopes =
    {
        IdentityServerConstants.StandardScopes.OpenId,
        IdentityServerConstants.StandardScopes.Profile,
        IdentityServerConstants.StandardScopes.Email,
    },
};

This will force the user to re-authenticate after 10 s of inactivity.

Solution 2: OIDC Property

This SO question solves the issue with an OIDC property which can be used to force the user to re-authenticate via the login prompt once the session expired - see @Scotty Brady's answer.

So for the example denoted in the question should look like the following. Note that only the MVC client needs changes, namely the Cookie lifetime was removed and OIDC options were added forcing the re-authentication and to use the token lifetime from IDS (each line marked with a // <- HERE). This way, the cookie settings from IDS are used (sliding lifetime of 10s).

MVC Client Startup

services.AddAuthentication(options =>
        {
            options.DefaultScheme = "Cookies";
            options.DefaultChallengeScheme = "oidc";
        })
        .AddCookie("Cookies") // <- HERE
        .AddOpenIdConnect("oidc", options =>
        {
            options.SignInScheme = "Cookies";

            options.Authority = "https://localhost:5001/";
            options.ClientId = "account-mvc";
            options.ClientSecret = "secret";
            options.ResponseType = "code id_token";

            options.UseTokenLifetime = true; // <- HERE
            options.SaveTokens = true;
            options.GetClaimsFromUserInfoEndpoint = true;

            options.Events.OnRedirectToIdentityProvider = context => // <- HERE
            {                                                        // <- HERE
                context.ProtocolMessage.Prompt = "login";            // <- HERE
                return Task.CompletedTask;                           // <- HERE
            };                                                       // <- HERE

            options.Scope.Add("openid");
            options.Scope.Add("profile");
            options.Scope.Add("email");
        });
like image 175
mmr Avatar answered Mar 16 '23 21:03

mmr