ASP.NET Core Authentication System

You typically use the following two ASP.NET Core authentication handlers to implement remote authentication:

  • the OpenID Connect authentication handler to interact with the remote OIDC / OAuth token service, e.g. Duende IdentityServer
  • the cookie handler to do local session management

Furthermore the BFF plumbing relies on the configuration of the ASP.NET Core default authentication schemes. This describes how the two handlers share the work.

OpenID Connect for challenge and signout - cookies for all the other operations:

builder.Services.AddAuthentication(options =>
    {
        options.DefaultScheme = "cookie";
        options.DefaultChallengeScheme = "oidc";
        options.DefaultSignOutScheme = "oidc";
    })
    .AddCookie("cookie", options => { ... })
    .AddOpenIdConnect("oidc", options => { ... });

The OpenID Connect Authentication Handler

The OIDC handler connects the application to the authentication / access token system.

The exact settings depend on the OIDC provider and its configuration settings. We recommend:

  • use authorization code flow with PKCE
  • use a response_mode of query since this plays nicer with SameSite cookies
  • use a strong client secret. Since the BFF can be a confidential client, it is totally possible to use strong client authentication like JWT assertions, JAR or MTLS. Shared secrets work as well of course.
  • turn off inbound claims mapping
  • save the tokens into the authentication session so they can be automatically managed
  • request a refresh token using the offline_access scope
builder.Services.AddAuthentication().AddOpenIdConnect("oidc", options =>
{
    options.Authority = "https://demo.duendesoftware.com";
    
    // confidential client using code flow + PKCE
    options.ClientId = "spa";
    options.ClientSecret = "secret";
    options.ResponseType = "code";

    // query response type is compatible with strict SameSite mode
    options.ResponseMode = "query";

    // get claims without mappings
    options.MapInboundClaims = false;
    options.GetClaimsFromUserInfoEndpoint = true;
    
    // save tokens into authentication session
    // to enable automatic token management
    options.SaveTokens = true;

    // request scopes
    options.Scope.Clear();
    options.Scope.Add("openid");
    options.Scope.Add("profile");
    options.Scope.Add("API");

    // and refresh token
    options.Scope.Add("offline_access");
});

The OIDC handler will use the default sign-in handler (the cookie handler) to establish a session after successful validation of the OIDC response.

The cookie handler is responsible for establishing the session and manage authentication session related data.

Things to consider:

  • determine the session lifetime and if the session lifetime should be sliding or absolute
  • it is recommended to use a cookie name prefix if compatible with your application
  • use the highest available SameSite mode that is compatible with your application, e.g. strict, but at least lax
builder.Services.AddAuthentication().AddCookie("cookie", options =>
{
    // set session lifetime
    options.ExpireTimeSpan = TimeSpan.FromHours(8);
    
    // sliding or absolute
    options.SlidingExpiration = false;

    // host prefixed cookie name
    options.Cookie.Name = "__Host-spa";
    
    // strict SameSite handling
    options.Cookie.SameSite = SameSiteMode.Strict;
});

Choosing between SameSite.Lax and SameSite.Strict

The SameSite cookie is a feature of modern browsers that restricts cookies so that they are only sent to pages originating from the site where the cookie was originally issued. This prevents CSRF attacks and helps with improving privacy, because cross-site requests will no longer implicitly include the user’s credentials.

If you configure SameSiteMode.Strict, this means that if a user originates from an external site and is redirected or linked to the BFF application, then the authentication cookie is not sent automatically. So, the application will consider the user to be not logged in, even though there may be a valid authentication cookie in the cookie jar. If the user refreshes the page, or visits a link on your site that forces a complete page reload, then the authentication cookie will be sent along normally again.

This also happens when you have an identity provider that’s hosted on a different site than the BFF, in combination with SameSiteMode.Strict. After successful authentication at the IdP, the user will be redirected back to the BFF site. The server will then place an authentication cookie in the browser, but the browser will not automatically include it in subsequent requests until the full page is manually reloaded by the user. This means the user appears to still be logged out, even though the cookie is there.

So, if you have an Identity Provider that’s hosted under a different site than your BFF, you may want to configure your cookie policy to be SameSiteMode.Lax.

Chrome will make an exception for cookies set without a SameSite attribute less than 2 minutes ago. Such cookies will also be sent with non-idempotent (e.g. POST) top-level cross-site requests despite normal SameSite=Lax cookies requiring top-level cross-site requests to have a safe (e.g. GET) HTTP method. Support for this intervention (“Lax + POST”) will be removed in the future. (source: chromestatus)