OAuth is the industry standard for authorization, allowing users to log in via third-party services like Google, Facebook, or GitHub without exposing their credentials to your application. Here’s how to implement OAuth in React:
1. OAuth Flow Overview
Authorization Code Flow (Recommended)
- User clicks “Login with [Provider]”
- Redirect to provider’s authorization page
- User authenticates and consents
- Redirect back to your app with authorization code
- Exchange code for tokens (server-side)
- Use tokens to access protected resources
2. Client-Side Implementation
Using react-oauth2-pkce (PKCE Flow)
npm install react-oauth2-pkce
import { AuthProvider, useAuth } from 'react-oauth2-pkce';
const authConfig = {
clientId: 'YOUR_CLIENT_ID',
authorizationEndpoint: 'https://provider.com/oauth2/authorize',
tokenEndpoint: 'https://provider.com/oauth2/token',
redirectUri: 'http://localhost:3000/callback',
scope: 'openid profile email',
};
function App() {
return (
<AuthProvider authConfig={authConfig}>
<AuthContent />
</AuthProvider>
);
}
function AuthContent() {
const { authService } = useAuth();
const login = async () => {
authService.authorize();
};
const logout = () => {
authService.logout();
};
if (authService.isPending()) {
return <div>Loading...</div>;
}
if (!authService.isAuthenticated()) {
return (
<div>
<button onClick={login}>Login</button>
</div>
);
}
return (
<div>
<p>Welcome {authService.getUser().name}!</p>
<button onClick={logout}>Logout</button>
</div>
);
}
3. Server-Side Flow (More Secure)
Frontend Component
function OAuthLogin() {
const handleLogin = (provider) => {
// Redirect to your backend OAuth endpoint
window.location.href = `http://your-backend.com/auth/${provider}`;
};
return (
<div>
<button onClick={() => handleLogin('google')}>
Login with Google
</button>
<button onClick={() => handleLogin('github')}>
Login with GitHub
</button>
</div>
);
}
Callback Handler
import { useEffect } from 'react';
import { useNavigate, useLocation } from 'react-router-dom';
function OAuthCallback() {
const location = useLocation();
const navigate = useNavigate();
useEffect(() => {
const params = new URLSearchParams(location.search);
const code = params.get('code');
const error = params.get('error');
if (error) {
console.error('OAuth error:', error);
navigate('/login');
return;
}
if (code) {
// Exchange code for token
fetch('/api/auth/token', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ code }),
})
.then(response => response.json())
.then(data => {
// Store tokens and user data
localStorage.setItem('access_token', data.access_token);
localStorage.setItem('user', JSON.stringify(data.user));
navigate('/dashboard');
})
.catch(err => {
console.error('Token exchange failed:', err);
navigate('/login');
});
}
}, [location, navigate]);
return <div>Processing login...</div>;
}
4. Popular OAuth Providers
Google OAuth
function GoogleLogin() {
const handleGoogleLogin = () => {
const clientId = 'YOUR_GOOGLE_CLIENT_ID';
const redirectUri = encodeURIComponent('http://localhost:3000/auth/google/callback');
const scope = encodeURIComponent('openid profile email');
const url = `https://accounts.google.com/o/oauth2/v2/auth?client_id=${clientId}&redirect_uri=${redirectUri}&response_type=code&scope=${scope}&access_type=offline&prompt=consent`;
window.location.href = url;
};
return <button onClick={handleGoogleLogin}>Login with Google</button>;
}
GitHub OAuth
function GitHubLogin() {
const handleGitHubLogin = () => {
const clientId = 'YOUR_GITHUB_CLIENT_ID';
const redirectUri = encodeURIComponent('http://localhost:3000/auth/github/callback');
const scope = encodeURIComponent('user:email');
const url = `https://github.com/login/oauth/authorize?client_id=${clientId}&redirect_uri=${redirectUri}&scope=${scope}`;
window.location.href = url;
};
return <button onClick={handleGitHubLogin}>Login with GitHub</button>;
}
5. Token Management
Secure Storage
// Secure token storage with httpOnly cookies is preferred
// If using localStorage:
const storeTokens = (accessToken, refreshToken) => {
localStorage.setItem('access_token', accessToken);
localStorage.setItem('refresh_token', refreshToken);
};
const getAccessToken = () => {
return localStorage.getItem('access_token');
};
const clearTokens = () => {
localStorage.removeItem('access_token');
localStorage.removeItem('refresh_token');
};
Token Refresh
async function refreshAccessToken(refreshToken) {
try {
const response = await fetch('/api/auth/refresh', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ refresh_token: refreshToken }),
});
const data = await response.json();
storeTokens(data.access_token, data.refresh_token);
return data.access_token;
} catch (error) {
clearTokens();
throw error;
}
}
// Axios interceptor example
axios.interceptors.response.use(
response => response,
async error => {
const originalRequest = error.config;
if (error.response.status === 401 && !originalRequest._retry) {
originalRequest._retry = true;
const refreshToken = localStorage.getItem('refresh_token');
const newToken = await refreshAccessToken(refreshToken);
originalRequest.headers.Authorization = `Bearer ${newToken}`;
return axios(originalRequest);
}
return Promise.reject(error);
}
);
6. OAuth with Firebase
import { getAuth, GoogleAuthProvider, signInWithPopup } from 'firebase/auth';
function FirebaseOAuth() {
const auth = getAuth();
const signInWithGoogle = () => {
const provider = new GoogleAuthProvider();
signInWithPopup(auth, provider)
.then((result) => {
// User signed in
const credential = GoogleAuthProvider.credentialFromResult(result);
const token = credential.accessToken;
const user = result.user;
})
.catch((error) => {
console.error('Login error:', error);
});
};
return <button onClick={signInWithGoogle}>Sign in with Google</button>;
}
7. Best Practices
- Security:
- Always use HTTPS
- Implement PKCE for public clients
- Store tokens securely (preferably in httpOnly cookies)
- Validate ID tokens on the server
- UX:
- Handle errors gracefully
- Provide clear loading states
- Implement session timeout handling
- Offer multiple OAuth providers
- Performance:
- Lazy load OAuth libraries when needed
- Cache user info appropriately
- Minimize required scopes
- Compliance:
- Follow provider’s branding guidelines
- Implement proper logout functionality
- Provide privacy policy and terms links
8. Troubleshooting Common Issues
CORS Problems
// Backend should allow your frontend origin
const corsOptions = {
origin: ['http://localhost:3000', 'https://your-production-domain.com'],
credentials: true
};
app.use(cors(corsOptions));
State Parameter
// Always include state parameter to prevent CSRF
const generateState = () => {
return Math.random().toString(36).substring(2, 15) +
Math.random().toString(36).substring(2, 15);
};
const handleLogin = () => {
const state = generateState();
localStorage.setItem('oauth_state', state);
const url = `https://provider.com/oauth?client_id=YOUR_ID&redirect_uri=...&state=${state}`;
window.location.href = url;
};
// In callback handler
const params = new URLSearchParams(location.search);
const state = params.get('state');
const savedState = localStorage.getItem('oauth_state');
if (state !== savedState) {
// Possible CSRF attack
console.error('State mismatch');
return;
}
Token Expiration
// Decode JWT to check expiration
function isTokenExpired(token) {
if (!token) return true;
try {
const payload = JSON.parse(atob(token.split('.')[1]));
return payload.exp * 1000 < Date.now();
} catch (e) {
return true;
}
}
OAuth provides a secure way to authenticate users without handling passwords directly. Choose the implementation that best fits your security requirements and application architecture. For most production applications, the server-side flow with PKCE is recommended for better security.