Overview
Authentication in React applications typically involves managing user tokens, protecting routes, and coordinating authentication state across the application.
This guide covers token-based authentication using localStorage, React Router actions, and route protection strategies.
Create a flexible authentication form supporting both login and signup:
import {
Form ,
Link ,
useSearchParams ,
useActionData ,
useNavigation ,
} from 'react-router-dom' ;
function AuthForm () {
const data = useActionData ();
const navigation = useNavigation ();
const [ searchParams ] = useSearchParams ();
const isLogin = searchParams . get ( 'mode' ) === 'login' ;
const isSubmitting = navigation . state === 'submitting' ;
return (
< Form method = "post" >
< h1 > { isLogin ? 'Log in' : 'Create a new user' } </ h1 >
{ data && data . errors && (
< ul >
{ Object . values ( data . errors ). map (( err ) => (
< li key = { err } > { err } </ li >
)) }
</ ul >
) }
{ data && data . message && < p > { data . message } </ p > }
< p >
< label htmlFor = "email" > Email </ label >
< input id = "email" type = "email" name = "email" required />
</ p >
< p >
< label htmlFor = "password" > Password </ label >
< input id = "password" type = "password" name = "password" required />
</ p >
< div >
< Link to = { `?mode= ${ isLogin ? 'signup' : 'login' } ` } >
{ isLogin ? 'Create new user' : 'Login' }
</ Link >
< button disabled = { isSubmitting } >
{ isSubmitting ? 'Submitting...' : 'Save' }
</ button >
</ div >
</ Form >
);
}
export default AuthForm ;
Use query parameters (?mode=login or ?mode=signup) to toggle between login and signup modes on the same page.
Authentication Action
Handle authentication requests using React Router actions:
import { json , redirect } from 'react-router-dom' ;
export async function action ({ request }) {
const searchParams = new URL ( request . url ). searchParams ;
const mode = searchParams . get ( 'mode' ) || 'login' ;
if ( mode !== 'login' && mode !== 'signup' ) {
throw json ({ message: 'Unsupported mode.' }, { status: 422 });
}
const data = await request . formData ();
const authData = {
email: data . get ( 'email' ),
password: data . get ( 'password' ),
};
const response = await fetch ( 'http://localhost:8080/' + mode , {
method: 'POST' ,
headers: {
'Content-Type' : 'application/json' ,
},
body: JSON . stringify ( authData ),
});
if ( response . status === 422 || response . status === 401 ) {
return response ;
}
if ( ! response . ok ) {
throw json ({ message: 'Could not authenticate user.' }, { status: 500 });
}
const resData = await response . json ();
const token = resData . token ;
localStorage . setItem ( 'token' , token );
return redirect ( '/' );
}
This example uses localStorage for token storage. For production applications, consider security implications and potentially use httpOnly cookies for sensitive tokens.
Token Management
Create utility functions to manage authentication tokens:
// util/auth.js
import { redirect } from 'react-router-dom' ;
export function getAuthToken () {
const token = localStorage . getItem ( 'token' );
return token ;
}
export function tokenLoader () {
return getAuthToken ();
}
export function checkAuthLoader () {
const token = getAuthToken ();
if ( ! token ) {
return redirect ( '/auth' );
}
return null ;
}
These loader functions can be attached to routes to provide authentication checks and data.
Route Protection
Protect routes by adding authentication loaders:
Protected Routes
Public Routes
import { checkAuthLoader } from './util/auth' ;
const router = createBrowserRouter ([
{
path: '/' ,
element: < RootLayout /> ,
children: [
{
path: 'events/new' ,
element: < NewEventPage /> ,
loader: checkAuthLoader , // Protect this route
action: newEventAction ,
},
{
path: 'events/:eventId/edit' ,
element: < EditEventPage /> ,
loader: checkAuthLoader , // Protect this route
action: editEventAction ,
},
],
},
]);
Conditional UI Rendering
Show/hide UI elements based on authentication state:
import { useRouteLoaderData } from 'react-router-dom' ;
function MainNavigation () {
const token = useRouteLoaderData ( 'root' );
return (
< nav >
< ul >
< li >< Link to = "/" > Home </ Link ></ li >
< li >< Link to = "/events" > Events </ Link ></ li >
{ token && (
<>
< li >< Link to = "/events/new" > New Event </ Link ></ li >
< li >
< Form method = "post" action = "/logout" >
< button > Logout </ button >
</ Form >
</ li >
</>
) }
{ ! token && (
< li >< Link to = "/auth?mode=login" > Login </ Link ></ li >
) }
</ ul >
</ nav >
);
}
useRouteLoaderData allows you to access loader data from parent routes using their route ID.
Logout Implementation
Implement logout functionality:
Logout Action
Route Configuration
Usage in Component
// pages/Logout.js
import { redirect } from 'react-router-dom' ;
export function action () {
localStorage . removeItem ( 'token' );
return redirect ( '/' );
}
Authenticated API Requests
Include authentication tokens in API requests:
import { getAuthToken } from './auth' ;
export async function createEvent ( eventData ) {
const token = getAuthToken ();
const response = await fetch ( 'http://localhost:8080/events' , {
method: 'POST' ,
headers: {
'Content-Type' : 'application/json' ,
'Authorization' : 'Bearer ' + token ,
},
body: JSON . stringify ( eventData ),
});
if ( ! response . ok ) {
throw new Error ( 'Failed to create event' );
}
return response . json ();
}
Include the token in the Authorization header as a Bearer token for API authentication.
Token Expiration
Handle token expiration and automatic logout:
export function getTokenDuration () {
const storedExpirationDate = localStorage . getItem ( 'expiration' );
const expirationDate = new Date ( storedExpirationDate );
const now = new Date ();
const duration = expirationDate . getTime () - now . getTime ();
return duration ;
}
export function getAuthToken () {
const token = localStorage . getItem ( 'token' );
if ( ! token ) {
return null ;
}
const tokenDuration = getTokenDuration ();
if ( tokenDuration < 0 ) {
return 'EXPIRED' ;
}
return token ;
}
export function tokenLoader () {
const token = getAuthToken ();
if ( token === 'EXPIRED' ) {
localStorage . removeItem ( 'token' );
localStorage . removeItem ( 'expiration' );
return null ;
}
return token ;
}
Auto-Logout Timer
Implement automatic logout when token expires:
import { useEffect } from 'react' ;
import { useNavigate , useLoaderData , useSubmit } from 'react-router-dom' ;
import { getTokenDuration } from './util/auth' ;
function App () {
const token = useLoaderData ();
const submit = useSubmit ();
useEffect (() => {
if ( ! token ) {
return ;
}
if ( token === 'EXPIRED' ) {
submit ( null , { action: '/logout' , method: 'post' });
return ;
}
const tokenDuration = getTokenDuration ();
const logoutTimer = setTimeout (() => {
submit ( null , { action: '/logout' , method: 'post' });
}, tokenDuration );
return () => {
clearTimeout ( logoutTimer );
};
}, [ token , submit ]);
return < RouterProvider router = { router } /> ;
}
Security Best Practices
Development : localStorage is convenient for development and non-sensitive applications.Production : Consider using httpOnly cookies for storing authentication tokens to prevent XSS attacks:// Server sets httpOnly cookie
res . cookie ( 'token' , token , {
httpOnly: true ,
secure: true ,
sameSite: 'strict' ,
maxAge: 3600000 ,
});
Always validate tokens on the server side: // Server-side validation
const isValidToken = await verifyJWT ( token );
if ( ! isValidToken ) {
return res . status ( 401 ). json ({ message: 'Invalid token' });
}
Handle authentication errors gracefully: if ( response . status === 401 ) {
// Token expired or invalid
localStorage . removeItem ( 'token' );
return redirect ( '/auth?error=session-expired' );
}
Common Patterns
Persistent Login Store token expiration time: const expirationTime = new Date ();
expirationTime . setHours (
expirationTime . getHours () + 1
);
localStorage . setItem (
'expiration' ,
expirationTime . toISOString ()
);
Refresh Tokens Implement token refresh mechanism: async function refreshToken () {
const response = await fetch (
'/api/refresh' ,
{
method: 'POST' ,
credentials: 'include' ,
}
);
return response . json ();
}
Routing Learn about React Router setup and navigation
Deployment Deploy authenticated applications securely