You are experiencing intermittent authentication session loss in your Single Page Application (SPA) when navigating between internal pages without logging out. After successful initial login, the application sometimes behaves as though the user is unauthenticated when moving to another route, even though the Auth0 session should still be valid. A page refresh or new login session can temporarily resolve the issue, making it difficult to reproduce consistently.
This is a well-documented race condition in Auth0 SPAs caused by a timing mismatch between when your application checks authentication state and when the Auth0 SDK finishes restoring the existing session. The solution involves properly managing the SDK initialization lifecycle and using both isLoading and isAuthenticated states together.
[Root Cause]
Your application is checking the authentication state before the Auth0 SDK has finished restoring the existing session from the browser's Auth0 session cookie. This causes the following sequence:
- User navigates to a new route — The route component mounts
- Application checks
isAuthenticatedimmediately — At this point, the Auth0 SDK is still initializing and has not yet restored the session isAuthenticatedreturns false — Because the SDK hasn't finished loading the session yet- Application renders unauthenticated content — Even though a valid Auth0 session exists
- SDK finishes restoring the session — But the component has already rendered incorrectly
- Inconsistent behavior — Sometimes the SDK finishes fast enough; sometimes it doesn't
This is exacerbated by:
- Client-side navigation without full page reloads — The Auth0Provider may not be properly re-initialized during route changes
- Browser restrictions on third-party cookies — Newer browser security features (ITP, SameSite) make silent authentication less reliable
- Auth0 Developer Keys — If you are using social login with Auth0's default developer credentials, sessions will not persist across routes
- Missing refresh token configuration — SPAs without refresh token rotation are more vulnerable to session loss
[Solution]
Step 1: Ensure Auth0Provider wraps your entire application
Verify that your Auth0Provider is at the root level of your application and wraps all routes. It should NOT be recreated during navigation.
Example (React):
import { Auth0Provider } from '@auth0/auth0-react';
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
function App() {
return (
<Auth0Provider
domain={process.env.REACT_APP_AUTH0_DOMAIN}
clientId={process.env.REACT_APP_AUTH0_CLIENT_ID}
authorizationParams={{
redirect_uri: window.location.origin,
}}
>
<Router>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/page2" element={<Page2 />} />
</Routes>
</Router>
</Auth0Provider>
);
}
export default App;
Step 2: Use both isLoading and isAuthenticated together
Never check isAuthenticated alone. Always wait for isLoading to be false before rendering protected content.
Example (React with useAuth0 hook):
import { useAuth0 } from '@auth0/auth0-react';
function ProtectedPage() {
const { isAuthenticated, isLoading, user } = useAuth0();
if (isLoading) {
return <LoadingSpinner />;
}
if (!isAuthenticated) {
return <LoginPrompt />;
}
return <ProtectedContent user={user} />;
}
Step 3: Create a route protection wrapper
Implement a reusable protected route component that properly handles the loading state:
import { useAuth0 } from '@auth0/auth0-react';
import { Navigate } from 'react-router-dom';
function ProtectedRoute({ component: Component }) {
const { isAuthenticated, isLoading } = useAuth0();
if (isLoading) {
return <LoadingSpinner />;
}
return isAuthenticated ? <Component /> : <Navigate to="/login" />;
}
<Route path="/protected" element={<ProtectedRoute component={ProtectedPage} />} />
Step 4: Enable Refresh Token Rotation (recommended)
For more reliable session persistence, enable refresh token rotation in your Auth0 configuration:
Navigate to Auth0 Dashboard → Applications → Your Application → Settings.
Scroll to Advanced Settings → Grant Types.
Enable:
Refresh Tokengrant typeOffline Accessscope in your authorization parameters
Update your Auth0Provider configuration:
<Auth0Provider
domain={process.env.REACT_APP_AUTH0_DOMAIN}
clientId={process.env.REACT_APP_AUTH0_CLIENT_ID}
authorizationParams={{
redirect_uri: window.location.origin,
scope: 'openid profile email offline_access', // Add offline_access
}}
useRefreshTokens={true} // Enable refresh tokens
cacheLocation="localstorage" // Store tokens in localStorage
>
Step 5: Use a Custom Domain (optional but recommended)
If you are using social login, Auth0's default domain may have issues with third-party cookie restrictions. A custom domain improves session reliability:
Navigate to Auth0 Dashboard → Branding → Custom Domains.
Follow the setup instructions to configure a custom domain for your tenant.
Update your Auth0Provider to use the custom domain:
<Auth0Provider
domain="auth.yourdomain.com" // Use custom domain instead of auth0.com
clientId={process.env.REACT_APP_AUTH0_CLIENT_ID}
// ... rest of config
>
Step 6: Verify you are not using Auth0 Developer Keys
If you are using social login (Google, GitHub, etc.), verify that you have configured your own credentials:
Navigate to Auth0 Dashboard → Connections → Social → [Your Provider].
Check that you are NOT using Auth0's default developer keys. If you see a message like "Using Auth0 Development Keys," you must configure your own credentials for the provider.
Developer keys do not persist sessions across routes.
Step 7: Debug the session restoration
To confirm the SDK is properly restoring sessions, add logging:
import { useAuth0 } from '@auth0/auth0-react';
import { useEffect } from 'react';
function DebugAuth() {
const { isAuthenticated, isLoading, user } = useAuth0();
useEffect(() => {
console.log('Auth state:', {
isLoading,
isAuthenticated,
user: user?.email || 'no user',
timestamp: new Date().toISOString(),
});
}, [isLoading, isAuthenticated, user]);
return null;
}
// Add this component inside your Auth0Provider
Open your browser console and navigate between pages. You should see:
- Initial mount:
isLoading: true - After SDK restores session:
isLoading: false, isAuthenticated: true - On route change:
isLoadingshould briefly be true again, then false
If isLoading never becomes true during route changes, the SDK is not re-initializing properly.
Step 8: Check browser cookie settings
If the issue persists, verify that Auth0 session cookies are not being blocked:
- Open browser DevTools → Application → Cookies
- Look for cookies from your Auth0 domain (e.g.,
auth0.comor your custom domain) - Verify the cookies have:
SameSite=NoneandSecureflags (for cross-site requests)- Or
SameSite=Laxfor same-site requests
- If no Auth0 cookies exist, third-party cookies may be blocked in your browser settings
I would recommend reviewing these resources regarding similar issues to the one that you are experiencing:
- Auth0 community: Auth0 not maintaining session in normal browser window
- Auth0 community: How to keep login session when refreshing page in SPA
- Auth0 community: useAuth0().isAuthenticated returns initially false then true
- Auth0 community: Long isLoading state when refreshing page
- Auth0 community: Angular 8 isAuthenticated race condition
- Auth0 community: Auth0 React SDK “Login with Google” doesn’t persist session between routes
Kind Regards,
Nik