-
Notifications
You must be signed in to change notification settings - Fork 80
Description
Describe the bug
My web application is on a rotating refresh token that, once expired, should return the user to the login screen. This works well enough in Firefox and Safari. However in Chrome it appears to be in the middle of a re-authentication after the expiration. It briefly jumps to the login screen and then right back to the protected home route.
[App.js]
This is the configuration for the OktaAuth instance
const config = {
issuer: process.env.OKTA_ISSUER,
clientId: process.env.OKTA_CLIENT_ID,
redirectUri: window.location.origin + '/oidc/callback',
scopes: ['openid', 'profile', 'email', 'offline_access'],
tokenManager: {
storage: 'sessionStorage'
},
}
const oktaAuth = new OktaAuth(config);
And App function. I'm routing all protected urls though RequireAuth since I can't use SecureRoute with react-router-dom v6. This is based off of the example in the okta-react repo with a few modifications.
const App = () => {
const navigate = useNavigate();
const restoreOriginalUri = async (_oktaAuth, originalUri) => {
navigate(toRelativeUrl(originalUri || '/', window.location.origin));
};
return (
<Security
oktaAuth={oktaAuth}
restoreOriginalUri={restoreOriginalUri}
>
<CacheProvider value={cache}>
<Box
id='app-root-container'
sx={{
display: 'flex',
flexDirection: 'column',
height: '100vh',
m: -1,
}}
>
<Fragment>
<Suspense fallback={<LoadingIcon />}>
<Routes>
<Route path='/' exact={true} element={<RequireAuth />}>
<Route path='' element={<Home />} />
</Route>
<Route path='/login' element={<Login />} />
<Route path='/oidc/callback' element={<LoginCallback />} />
<Route path='*' element={<PageNotFound404 />} />
</Routes>
</Suspense>
</Fragment>
</Box>
</CacheProvider>
</Security>
)
}
[SecureRoute.js]
This is very similar to the sample code. I'm using a notLoggedIn state to facilitate the return to the /login route upon de-authentication. The reason for doing this was because I was getting stuck in an endless loop of going to the /login route.
export const RequireAuth = () => {
const { oktaAuth, authState } = useOktaAuth();
const [ notLoggedIn, setNotLoggedIn ] = useState(false);
useEffect(() => {
if (!authState) {
console.log('authState not initialized.')
return;
}
if (!authState?.isAuthenticated) {
console.log('user not authenticated')
setNotLoggedIn(true);
} else {
console.log('user is authenticated!')
setNotLoggedIn(false)
}
}, [oktaAuth, !!authState, authState?.isAuthenticated]);
if (!authState || !authState?.isAuthenticated) {
if (notLoggedIn) {
console.log("load login page");
return <Navigate to='/login' replace={true} />
} else {
console.log(
"Route not Authenticated. Waiting for authentication page to load."
);
return <LoadingIcon />
}
}
return (<Outlet />);
}
[Login.js] This will check to see if the user is already authenticated. If so it simply jumps to the home screen.
const Login = () => {
const { oktaAuth, authState } = useOktaAuth();
const theme = useTheme();
const handleLogin = async () => {
await oktaAuth.signInWithRedirect();
}
const layout = () => {
if (authState?.isAuthenticated) {
console.log('already authenticated. Going to main page.')
return <Navigate to='/' replace={true} />
} else {
return (
...lots of UI code including a button that uses the handleLogin callback
)
}
}
return layout();
}
This is the post-login console output in Chrome. As you can see fetchRequest is unable to validate the token everything seems to de-authenticate and return to the login page. Then fetchRequest executes again and gets a new token and the whole thin returns to the protected home route.

This is the post-login console output in Firefox. I'm not sure why it shows the GET to v1/authorize in Firefox and not Chrome. I assumed they would both do this. Firefox might just be showing it where Chrome does not. I'm not sure if this is significant. At any rate once the token is expired Firefox returns to login. All session tokes are cleared (except for the ID token).

Hopefully this is good enough to start. Happy to share more details as needed.
Thanks in advance!
Reproduction Steps?
I've uploaded an example repo pared down to just a login route and a protected home route. It won't work as-is but it should provide further context to the overall flow of the application.
Here
SDK Versions
"@emotion/react": "^11.11.0",
"@emotion/styled": "^11.11.0",
"@mui/icons-material": "^5.11.16",
"@mui/material": "^5.13.2",
"@okta/okta-auth-js": "^7.3.0",
"@okta/okta-react": "^6.7.0",
"axios": "^1.4.0",
"core-js": "^3.33.2",
"history": "^5.3.0",
"npm": "^9.6.7",
"prop-types": "^15.8.1",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-router-dom": "^6.11.2"
Additional Information
Here's my Okta SPA configuration:
My authorization server access rules. The token rotation are set to as short an interval as I could set in order to test without waiting forever. If there's a better way to test this I'd love to hear it!