Search code examples
c#reactjs.netidentityserver4openid-connect

oidc-client.min.js - Uncaught (in promise) ErrorResponse: login_required


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


Solution

  • 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