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
-
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.
-
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
-
If I refresh again within some seconds, I started getting continues ‘login-required’ errors
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.
2
Answers
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..)
automaticSilentRenew
monitorSession
Final client config:
2. These changes should go at the server side (Whichever server technology is used)
SameSite
cookie policy toNONE
SecurePolicy
toALWAYS
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 alogin_required
error code. You cannot rely on this flow working in modern browsers, so you must disable thesessionMonitor
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.
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: