skip to Main Content

I’m using React and IdentityServer for Authentication.
I use ‘code’ grant with ‘PKCE’.
When my React app tries to hit an endpoint, If it gets 401 (UNAUTHORIZED), It’ll start the login process with OIDC client. Everything works very well.
Except after successful login, I’m seeing a lot of errors in my console coming from ‘oidc-client.js.

Here’s a video demonstration –
https://www.youtube.com/watch?v=vzWppuYhrKg

This is my IDP config for this client (Serialized for readability):

{
  "_id": "****",
  "Enabled": true,
  "ProtocolType": "oidc",
  "ClientSecrets": 
  [
    {
      "Value": "****",
      "Type": "SharedSecret"
    }
  ],
  "RequireClientSecret": true,
  "ClientName": "****",
  "RequireConsent": false,
  "AllowRememberConsent": true,
  "AllowedGrantTypes": 
  [
    "authorization_code",
    "client_credentials"
  ],
  "RequirePkce": true,
  "AllowPlainTextPkce": false,
  "RequireRequestObject": false,
  "AllowAccessTokensViaBrowser": false,
  "RedirectUris": 
  [
    "https://localhost:44410/callback",
    "https://authmanager.twileloop.com/callback"
  ],
  "PostLogoutRedirectUris": 
  [
    "https://localhost:44326/signout-callback-oidc"
  ],
  "FrontChannelLogoutSessionRequired": true,
  "BackChannelLogoutSessionRequired": true,
  "AllowOfflineAccess": false,
  "AllowedScopes": 
  [
    "openid",
    "read",
    "write"
  ],
  "AlwaysIncludeUserClaimsInIdToken": false,
  "IdentityTokenLifetime": 3600,
  "AllowedIdentityTokenSigningAlgorithms": [],
  "AccessTokenLifetime": 3600,
  "AuthorizationCodeLifetime": 300,
  "AbsoluteRefreshTokenLifetime": 2592000,
  "SlidingRefreshTokenLifetime": 1296000,
  "RefreshTokenUsage": "OneTimeOnly",
  "UpdateAccessTokenClaimsOnRefresh": false,
  "RefreshTokenExpiration": "Absolute",
  "AccessTokenType": "Jwt",
  "EnableLocalLogin": true,
  "IdentityProviderRestrictions": [],
  "IncludeJwtId": true,
  "Claims": [],
  "AlwaysSendClientClaims": false,
  "ClientClaimsPrefix": "client_",
  "DeviceCodeLifetime": 300,
  "AllowedCorsOrigins": 
  [
    "https://localhost:44410",
    "https://authmanager.twileloop.com"
  ],
  "Properties": {}
}

This is my JavaScript config:

const config = {
    authority: AUTHORITY_PROD,
    client_id: '****',
    redirect_uri: ORGIN + '/callback',
    response_type: 'code',
    scope: 'openid read write',
    post_logout_redirect_uri: ORGIN + '/',
    userStore: new WebStorageStateStore({ store: window.localStorage }),
    automaticSilentRenew: false
};

const userManager = new UserManager(config);

//Login
const login = () => {
    userManager.removeUser();
    userManager.signinRedirect();
};

//Logout
const logout = () => {
    userManager.signoutRedirect();
};

DEMO

  1. This is my application
    enter image description here

  2. The moment I refresh, It tries to hit an API endpoint and gets 401. This will start authentication flow, and I’m getting redirected to ‘https://auth.twileloop.com’ where my IDP is.
    enter image description here

  3. Once I successfully log in, It redirects me back to a callback URL and I get the token and also able to hit API endpoints and get data on console. Well and good
    enter image description here

  4. If I refresh again within some seconds, I started getting continues ‘login-required’ errors
    enter image description here

Why am I getting this error? I found someone saying ‘Chrome Same Site Cookie’ policy is making issue. I tried on Edge but same result.

The fact is – Token is still valid for an hour. I can still use it to hit my API. Token validity is 1hr (60×60 secs). But this error is getting filled continuously every time I refresh the page.

  1. Also, localStorage is getting flooded
    enter image description here

Network Logs
enter image description here

2

Answers


  1. Chosen as BEST ANSWER

    I derived the fix from major points from Gary's answer.

    Posting here as it'll be helpful anyone coming here later.

    1. These changes should go on the client side (React, Angular, SPA's..)

    1. Disable automaticSilentRenew
    2. Disable monitorSession

    Final client config:

    const config = {
        authority: AUTHORITY_PROD,
        client_id: '****',
        redirect_uri: ORGIN + '/callback',
        response_type: 'code',
        scope: 'openid read write',
        post_logout_redirect_uri: ORGIN + '/',
        userStore: new WebStorageStateStore({ store: window.localStorage }),
        automaticSilentRenew: false,
        monitorSession: false,
        client_secret: '****',
    };
    

    2. These changes should go at the server side (Whichever server technology is used)

    I'm using IdentityServer built on .NET 7, So these are the config changes required

    1. Set SameSite cookie policy to NONE
    2. Set SecurePolicy to ALWAYS
     services.AddIdentityServer(options => {
                    //Set cookie lifetime
                    options.Authentication.CookieLifetime = TimeSpan.FromSeconds(config.IdentityServerCookieLifetime);
                })
                //For local testing only
                .AddDeveloperSigningCredential()
                //Configure CORS policy
                .AddCorsPolicyService<MyCORSPolicy>()
                //Fetch OAuth v2 resources from SQLServer
                .AddResourceStore<MyResourceStore>()
                //Fetch OAuth v2 clients from SQLServer
                .AddClientStore<MyClientStore>()
                //Fetch user profiles from SQLServer
                .AddProfileService<ProfileService>();
    
                //Set cookie policy
                services.ConfigureApplicationCookie(options => {
                    options.Cookie.SameSite = SameSiteMode.None;
                    options.Cookie.SecurePolicy = CookieSecurePolicy.Always;
                });
    

  2. SESSION MONITOR

    From your video and network trace I see that OpenID Connect session management is being used. If you look at the OIDC metadata returned from your authorization server, you will see that a check_session_iframe URL field is present. This URL returns some JavaScript code.

    What is happening is that the library is spinning up a hidden iframe that loads this JavaScript code, which then makes an Ajax request to the authorization server every couple of seconds, to check the login state. The intention is that the SSO cookie will be sent in this request.

    However, recent browser restrictions will drop the SSO cookie, since it is from a different domain to the web origin, and thus classified as third-party. The outgoing request includes the prompt=none parameter. Because the SSO cookie is not present, the authorization server returns a login_required error code. You cannot rely on this flow working in modern browsers, so you must disable the sessionMonitor behavior. See the relevant library code.

    STATE STORE

    If you look at local storage more carefully, you will see that there are two types of entry. The one that contains the PKCE code verifier contains only login state that is not needed across multiple browser tabs. Therefore use session storage so that the entries do not build up. This is the approach my code example takes. Meanwhile the user store is where access tokens and user info is stored – this can use a different type of storage.

    FINAL CONFIGURATION

    Update your oidc client configuration with the two new settings here, to solve both of your problems.

    const config = {
        authority: AUTHORITY_PROD,
        client_id: '****',
        redirect_uri: ORGIN + '/callback',
        response_type: 'code',
        scope: 'openid read write',
        post_logout_redirect_uri: ORGIN + '/',
        userStore: new WebStorageStateStore({ store: window.localStorage }),
        stateStore: new WebStorageStateStore({ store: window.sessionStorage }),
        automaticSilentRenew: false,
        sessionMonitor: false
    };
    

    DEBUGGING

    If problems remain, add some console.log statements to the oidc client library itself, eg by editing files in the below folder. Maybe start by editing UserManager.js to see why the library has activated session management:

    node_modules/oidc-client-js
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search