Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Authentication cookie disappears in react SPA after some time

We have an SPA, written in React together with ASP.net core for hosting.

To authenticate the app, we are using IdentityServer4 and use a cookie. The client is configured according to the sample, described here: https://github.com/IdentityServer/IdentityServer4/tree/main/samples/Quickstarts/4_JavaScriptClient/src

For authenticating a user, everythings works fine. It will be redirected to the login page. After signing in, redirection to the SPA is done. The authentiation cookie is set as expected with:

  • HttpOnly = true
  • Secure = true
  • SameSite = None
  • Expires / Max-age = one week from login time

The cookie is used also in other MVC (.net core and MVC 5) applications for authentication reasons. In the SPA, we are also using SignalR, which needs the cookie for authentication.

Our issue:

After about 30 minutes of idle time in the browser and either doing a refresh or navigating, the authentication cookie (and only that, other remains) is disappearing from the browser automatically. Then the user has to sign in again. Why does this happen together with the SPA?

Code

Complete code can be found in github

Client

Snippets of UserService.ts

const openIdConnectConfig: UserManagerSettings = {
  authority: baseUrls.person,
  client_id: "js",
  redirect_uri: joinUrl(baseUrls.spa, "signincallback"),
  response_type: "code",
  scope: "openid offline_access profile Person.Api Translation.Api",
  post_logout_redirect_uri: baseUrls.spa,
  automaticSilentRenew: true
};

export const getUserService = asFactory(() => {
  const userManager = new UserManager(openIdConnectConfig);
  return createInstance(createStateHandler(defaultUserState), userManager, createSignInProcess(userManager));
}, sameInstancePerSameArguments());

Server

Snipped of Startup.cs

public void ConfigureServices(IServiceCollection services)
{
    Log.Information($"Start configuring services. Environment: {_environment.EnvironmentName}");
    services.AddControllersWithViews();

    services.AddIdentity<LoginInputModel, RoleDto>()
            .AddDefaultTokenProviders();

    var certificate = LoadSigningCertificate();
    var identityServerBuilder = services.AddIdentityServer(options =>
                                                           {
                                                               options.Events.RaiseErrorEvents = true;
                                                               options.Events.RaiseInformationEvents = true;
                                                               options.Events.RaiseFailureEvents = true;
                                                               options.Events.RaiseSuccessEvents = true;
                                                           })
                                        .AddSigningCredential(certificate)
                                        .AddProfileService<ProfileService>()
                                        .AddInMemoryIdentityResources(Config.Ids)
                                        .AddInMemoryApiResources(Config.Apis)
                                        .AddInMemoryClients(new ClientConfigLoader().LoadClients(Configuration));

    if (_environment.IsDevelopment())
    {
        identityServerBuilder.AddDeveloperSigningCredential();
    }

    services.Configure<CookiePolicyOptions>(options =>
                                            {
                                                options.CheckConsentNeeded = context => false;
                                                options.MinimumSameSitePolicy = SameSiteMode.None;
                                            });

    services.AddDataProtection()
            .PersistKeysToFileSystem(new DirectoryInfo(_sharedAuthTicketKeys))
            .SetApplicationName("SharedCookieApp");

    services.AddAsposeMailLicense(Configuration);
    var optionalStartupSettings = SetupStartupSettings();
    if (optionalStartupSettings.IsSome)
    {
        var settings = optionalStartupSettings.Value;

        services.ConfigureApplicationCookie(options =>
                                            {
                                                options.AccessDeniedPath = new PathString("/Account/AccessDenied");
                                                options.Cookie.Name = ".AspNetCore.Auth.Cookie";
                                                options.Cookie.Path = "/";
                                                options.Cookie.HttpOnly = true;
                                                options.Cookie.SecurePolicy = CookieSecurePolicy.Always;
                                                options.LoginPath = new PathString("/account/login");
                                                options.Cookie.SameSite = SameSiteMode.None;
                                            });

        var authBuilder = services.AddAuthentication(options => { options.DefaultAuthenticateScheme = "Identity.Application"; });
        authBuilder = ConfigureSaml2(authBuilder, settings);
        authBuilder = ConfigureGoogle(authBuilder);
        authBuilder.AddCookie();
    }
    else
    {
        throw new InvalidOperationException($"Startup settings are not configured in appsettings.json.");
    }

    SetupEntityFramework(services);
}

Snippet of identity server client config from appsettings.json

{
    "Enabled": true,
    "ClientId": "js",
    "ClientName": "JavaScript Client",
    "AllowedGrantTypes": [ "authorization_code" ],
    "RequirePkce": true,
    "RequireClientSecret": false,
    "RedirectUris": [ "https://dev.myCompany.ch/i/signincallback", "https://dev.myCompany.com/i/signincallback", "https://dev.myCompany.de/i/signincallback" ],
    "PostLogoutRedirectUris": [ "https://dev.myCompany.ch/i/", "https://dev.myCompany.com/i/", "https://dev.myCompany.de/i/" ],
    "AllowedCorsOrigins": [],
    "AllowedScopes": [ "openid", "offline_access", "profile", "Translation.Api", "Person.Api" ],
    "RequireConsent": false,
    "AllowOfflineAccess": true
}

Update

In the meantime I discovered that the cookie, while requesting https://ourdomain/.well-known/openid-configuration after 30 minutes idle time, has lost the values of Domain, Path, Expires/Max-Age, HttpOnly, Secure and SameSite.None. Those values have definitely been set after signing in. The response cookie has the value of Expires/Max-Age set to a time in the past and therefore the cookie will be dropped by the browser. Network traffic

Has anyone an idea, why those values got lost after some time?

like image 245
Peter Avatar asked Jun 22 '20 10:06

Peter


People also ask

How do you protect authentication cookies?

But, we can explicitly modify Cookie headers to make them protected against such attacks. For example, Cookies can be easily protected against XSS attacks by using HttpOnly attribute when setting the Cookie headers. Also, we can use SameSite attribute in the cookie header to prevent CSRF attacks effectively.

How do I recover cookie react?

Getting the cookie with React hooks First, import the CookiesProvider component from the react-cookie package and wrap your root app component with it. import React from "react"; import ReactDOM from "react-dom"; import { CookiesProvider } from "react-cookie";import App from "./App"; const rootElement = document.

What is stored in authentication cookie?

What is Cookie-based Authentication? Cookies are pieces of data used to identify the user and their preferences. The browser returns the cookie to the server every time the page is requested. Specific cookies like HTTP cookies are used to perform cookie-based authentication to maintain the session for each user.

Which authentication uses cookies for user authentication?

Cookie authentication uses HTTP cookies to authenticate client requests and maintain session information. It works as follows: The client sends a login request to the server.

How do I Secure cookies for my react app?

The React application will hit the Express server for all endpoints. With this method, your front end app is on the same domain, and has a server, allowing you to secure cookies with HttpOnly, Secure, and Same Site options.

How do I serve a react app from Express server?

The Express server will serve the React SPA from all routes, except those that begin with /api. The React application will hit the Express server for all endpoints. With this method, your front end app is on the same domain, and has a server, allowing you to secure cookies with HttpOnly, Secure, and Same Site options.

How does the react spa work with express?

A real-world example of the setup: The Express server will serve the React SPA from all routes, except those that begin with /api. The React application will hit the Express server for all endpoints.


1 Answers

Finally, I figured out how to tackle this.

It had to do with the configuration of IdentityServer. The missing part was the method AddAspNetIdentity<LoginInputModel>().

Before:

var certificate = LoadSigningCertificate();
var identityServerBuilder = services.AddIdentityServer(options =>
                                                       {
                                                           options.Events.RaiseErrorEvents = true;
                                                           options.Events.RaiseInformationEvents = true;
                                                           options.Events.RaiseFailureEvents = true;
                                                           options.Events.RaiseSuccessEvents = true;
                                                       })
                                    .AddSigningCredential(certificate)
                                    .AddProfileService<ProfileService>()
                                    .AddInMemoryIdentityResources(Config.Ids)
                                    .AddInMemoryApiResources(Config.Apis)
                                    .AddInMemoryClients(new ClientConfigLoader().LoadClients(Configuration));

Now:

var certificate = LoadSigningCertificate();
var identityServerBuilder = services.AddIdentityServer(options =>
                                                       {
                                                           options.Events.RaiseErrorEvents = true;
                                                           options.Events.RaiseInformationEvents = true;
                                                           options.Events.RaiseFailureEvents = true;
                                                           options.Events.RaiseSuccessEvents = true;
                                                       })
                                    .AddSigningCredential(certificate)
                                    .AddAspNetIdentity<LoginInputModel>()
                                    .AddProfileService<ProfileService>()
                                    .AddInMemoryIdentityResources(Config.Ids)
                                    .AddInMemoryApiResources(Config.Apis)
                                    .AddInMemoryClients(new ClientConfigLoader().LoadClients(Configuration));

With that additional configuration line, Identity Server is handling the cookie correctly.

like image 169
Peter Avatar answered Oct 04 '22 09:10

Peter