React Token Auth
The `react-token-auth` library, currently at version 2.3.8, offers a specialized solution for managing JWT `accessToken` and `refreshToken` pairs within React applications. Its primary function is to abstract the complexities of token storage (e.g., `localStorage` for web or custom async storage for React Native), synchronize the application's authentication state, and automatically handle token expiration and refreshing. A key feature is its `onUpdateToken` callback, which allows developers to integrate with their backend's token refresh endpoint, and its mechanism to prevent concurrent token update requests. While the author acknowledges that cookie-based sessions are generally preferred for web security, `react-token-auth` addresses specific use cases where client-side JWT storage is a requirement. The library provides a concise API including `createAuthProvider` to configure the system, `useAuth` to access the authentication state in components, and `authFetch` to automatically attach tokens to requests. The project appears actively maintained with regular updates and ships with TypeScript type definitions, making it suitable for modern React development practices.
Common errors
-
TypeError: Cannot read properties of undefined (reading 'accessToken')
cause The `getAccessToken` property in `createAuthProvider` is misconfigured, or the session object passed to `login` is `null`/`undefined` or lacks the expected `accessToken` field.fixVerify that your `Session` type correctly matches the structure of tokens returned by your backend and that `getAccessToken` accurately points to the access token. Ensure `login()` is called with a valid session object. -
Uncaught (in promise) TypeError: Failed to fetch
cause A network request initiated by `onUpdateToken`, `login`, `logout`, or `authFetch` failed due to network connectivity issues, incorrect URL, CORS policies, or an unreachable server.fixCheck your network connection and server status. Verify the URLs used in `fetch` calls. Ensure your server correctly handles CORS preflight requests and has the expected endpoints (`/login`, `/update-token`, etc.). -
ReferenceError: localStorage is not defined
cause The library is configured to use `localStorage` but is being run in a server-side rendering (SSR) environment or React Native without a compatible storage shim.fixWhen using `react-token-auth` in SSR or React Native, provide a custom `storage` implementation to `createAuthProvider` that defers to `localStorage` only on the client or uses an appropriate async storage solution. -
RangeError: Maximum call stack size exceeded
cause Often indicative of an infinite loop within the token refresh logic. This can happen if `onUpdateToken` repeatedly fails in a way that triggers itself again without a circuit breaker or proper error exit.fixReview the `onUpdateToken` implementation to ensure it has robust error handling and a clear exit strategy for persistent failures, preventing recursive calls without resolution. Implement safeguards like retry limits.
Warnings
- gotcha Storing JWT tokens in `localStorage` is generally considered less secure than HTTP-only cookie sessions due to vulnerability to Cross-Site Scripting (XSS) attacks. If an attacker injects malicious JavaScript, they can access and steal the tokens.
- gotcha The `onUpdateToken` function must correctly handle API responses and always return a new session object with `accessToken` and `refreshToken`. Incorrect responses (e.g., malformed JSON, missing tokens) can lead to an unauthenticated state or errors.
- gotcha Failing to properly handle errors or network issues within the `onUpdateToken` callback can lead to an infinite loop of refresh attempts or leave the user in a permanently unauthenticated state without clear recovery.
Install
-
npm install react-token-auth -
yarn add react-token-auth -
pnpm add react-token-auth
Imports
- createAuthProvider
const { createAuthProvider } = require('react-token-auth');import { createAuthProvider } from 'react-token-auth'; - useAuth
import { useAuth } from 'react-token-auth'; - login
import { login } from 'react-token-auth';
Quickstart
import { createAuthProvider } from 'react-token-auth';
import React, { FormEvent, useEffect } from 'react';
import { BrowserRouter, Switch, Route, Redirect } from 'react-router-dom'; // Assuming react-router-dom for routing context
// Define the shape of your session object
type Session = { accessToken: string; refreshToken: string };
// 1. Create the auth provider instance
export const { useAuth, authFetch, login, logout } = createAuthProvider<Session>({
getAccessToken: session => session.accessToken,
// Use localStorage for web applications. Ensure it's available (e.g., client-side).
storage: typeof window !== 'undefined' ? localStorage : undefined,
onUpdateToken: async (token: { refreshToken: string }) => {
// This function is called when the accessToken needs to be refreshed.
// It must return a new session object { accessToken: string; refreshToken: string }
try {
const response = await fetch('/update-token', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ refreshToken: token.refreshToken }),
});
if (!response.ok) {
const errorData = await response.json();
console.error('Token refresh failed:', errorData);
throw new Error('Failed to refresh token');
}
return response.json();
} catch (error) {
console.error('Network or server error during token refresh:', error);
throw error;
}
},
// Optional: Callback when a session is loaded from storage (hydration)
onHydratation: session => {
console.log('Session hydrated:', session);
},
});
// A dummy component for registration
const Register = () => <div>Register Page</div>;
// 2. Example Login Component
const Login = () => {
const onSubmit = async (e: FormEvent) => {
e.preventDefault();
// Simulate a login API call
try {
const response = await fetch('/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ username: 'testuser', password: 'password' }), // Replace with actual credentials
});
if (!response.ok) {
const errorData = await response.json();
console.error('Login failed:', errorData);
alert('Login failed!');
return;
}
const session = await response.json();
login(session); // Save the new session
alert('Logged in successfully!');
} catch (error) {
console.error('Network error during login:', error);
alert('Network error during login!');
}
};
return (
<form onSubmit={onSubmit}>
<h2>Login</h2>
<input type="text" placeholder="Username" />
<input type="password" placeholder="Password" />
<button type="submit">Login</button>
</form>
);
};
// A dummy dashboard component
const Dashboard = () => {
const handleLogout = () => {
logout();
alert('Logged out!');
};
// Example of using authFetch to make authenticated requests
const fetchData = async () => {
try {
const response = await authFetch('/api/protected-data');
const data = await response.json();
console.log('Protected data:', data);
alert('Fetched protected data: ' + JSON.stringify(data));
} catch (error) {
console.error('Failed to fetch protected data:', error);
alert('Failed to fetch protected data. Maybe token expired or invalid.');
}
};
return (
<div>
<h2>Dashboard</h2>
<button onClick={handleLogout}>Logout</button>
<button onClick={fetchData}>Fetch Protected Data</button>
</div>
);
};
// 3. Main Router component using useAuth hook to manage routing based on auth state
const AppRouter = () => {
const [logged, session] = useAuth(); // Get current auth state
useEffect(() => {
console.log('Auth state changed:', logged, session);
}, [logged, session]);
return (
<BrowserRouter>
<Switch>
{!logged ? (
<>
<Route path="/register" component={Register} />
<Route path="/login" component={Login} />
<Redirect to="/login" />
</>
) : (
<>
<Route path="/dashboard" component={Dashboard} exact />
<Redirect to="/dashboard" />
</>
)}
</Switch>
</BrowserRouter>
);
};
// To run this example in a real React app, you would render <AppRouter /> in your ReactDOM.render()
// For demonstration purposes, we omit the ReactDOM.render() call.