401 Unauthorized error from API after 1 day


We want to allow the user to remain signed on to the web app indefinitely. Currently, the web app would get 401 error from our API server after 3 days, which makes sense, since the access token has a max life of 3 days.

To fix this, I thought about using refresh token and adding rotating refresh tokens for better security. However after adding that, the 401 error would occur after 1 day.

Here are code snippets from the React app:

import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';

import ScopedCssBaseline from '@material-ui/core/ScopedCssBaseline';
import { ThemeProvider } from '@material-ui/core/styles';

import { Auth0Provider } from '@auth0/auth0-react';
import App from './App';
import theme from './components/layout/Theme';

		<ThemeProvider theme={theme}>
				<App />

Inside App, I have a hook that gets the token and other information. A snippet of the hook is:

	const [token, setToken] = useState();

	// Function to retrieve and set tokenn
	const getToken = useCallback(async () => {
		const userToken = await getAccessTokenSilently({
			audience: process.env.REACT_APP_AUTH_AUDIENCE,
			scope: process.env.REACT_APP_AUTH_SCOPE,
	}, [getAccessTokenSilently]);

	const getNewToken = async () => {
		const userToken = await getAccessTokenSilently({
			audience: process.env.REACT_APP_AUTH_AUDIENCE,
			scope: process.env.REACT_APP_AUTH_SCOPE,
	// Upon login, get token, or set 'token' if running in test environment
	useEffect(() => {
		if (
			window.location.hostname === 'localhost' ||
			window.location.hostname === '' ||
			window.location.hostname === '' ||
			process.env.NODE_ENV === 'test'
		) {
		} else if (user) {
	}, [user, getToken]);

	useEffect(() => {
		const timer = setInterval(() => {
			console.log('getting new token');
		}, 60 * 1000);
		return () => clearInterval(timer);
	}, []);

as for the Auth0 dashboard, for the SPA application, I have:
Application Type: Single Page Application
Refresh Token Rotation: ON
Reuse Interval: 600s
Absolute Expiration: ON

And for the API:
Allow Offline Access: ON
Allow Skipping User Consent: ON

What might be going on that might be causing this to occur?

What’s the configuration for the access token lifetime now?
Did you see any activity in the tenant logs for successful refresh token exchanges?
If you set it to a much lower value (say, 10 minutes), does the refresh token flow work?

1 Like

Below is the screenshot for the token expiry of the API, functions-api

Below is the log, which seems like refresh token has been successfully refreshed.

I set both the Token Expiration and Token Expiration For Browser Flows to 600s (or 10min), then:

  1. Log out from web app and religion
  2. I go to the Network tab in Firefox and observed two token responses, one with expires_in: 86400 and the other one expires_in: 600. Both have grant_type: "authorization code". (date was Mon, 27 Sep 2021 13:17:48 GMT)
  3. After 10min (Mon, 27 Sep 2021 13:27:48 GMT), there were an OPTION request and a POST request. The latter gets the refresh token, grant_type: "refresh_token" that expires_in: 600

So it seems like the refresh token is being obtained, but still want to see how it behaves after one day. Previously, when I left the web app on for at least 24h, I would see the splash page of the web app, which means isAuthenticated is false.

Furthermore, is it necessary to keep on calling getAccessTokenSilently to obtain the new access token?

I ran it for a day, here are my findings:

  • token is obtained every 1200 seconds (I changed from 600 to 1200s), only if you logout and login
  • after about a day, the web app automatically go back Splash screen (isAuthenticated is false)
  • it seems like I need to keep calling getAccessTokenSilently to get the updated token

So, how does isAuthenticated work to make it become false, even though refresh tokens are obtained?

You should call getAccessTokenSilently() when you need an access token. The SDK will either return a previously obtained (and still valid) access token, or will try to get a new one (using the refresh token flow, or a silent authentication as a fallback).
So, assuming the SDK is configured to use refresh tokens, getAccessTokenSilently() will:

  • Return a previously obtained refresh token (with a lifetime controlled by the token settings for the API), or
  • Use a refresh token (with a lifetime controlled by the refresh token settings for the app - you should check these values too), or
  • Use a silent token request flow. This flow requires a session cookie sent through an IFRAME (might not work on all browsers, or if not using a custom domain matching the app domain).
    The lifetime of this browser session is controlled by the session settings for the Tenant (under advanced settings).
1 Like