Secure API Authentication in React Applications
When using any API in our system from a React application, it's crucial to avoid placing authentication tokens directly in your frontend code. This page outlines secure approaches for implementing API authentication in React applications.
The Problem with Client-Side Authentication
Including Bearer tokens directly in frontend JavaScript (as shown in some examples) exposes sensitive credentials:
// ⚠️ INSECURE APPROACH - DO NOT USE
fetch(`https://<PA_END_POINT>/3.0/recommendations?${params.toString()}`, {
headers: {
'Authorization': 'Bearer YOUR_ACCESS_TOKEN', // Exposed in client-side code!
'Content-Type': 'application/json'
}
})
This approach is insecure because:
- The token is visible in your source code
- Anyone can extract and use the token for unauthorized API access
- If token permissions are broad, this creates significant security risks
Secure Authentication Options for React Applications
1. Create a Backend Proxy Service
Implement a simple server that forwards requests to the API:
// server.js (Node.js/Express example)
const express = require('express');
const axios = require('axios');
const app = express();
const PORT = process.env.PORT || 3001;
app.use(express.json());
app.get('/api/recommendations', async (req, res) => {
try {
const response = await axios.get(
`https://<PA_END_POINT>/3.0/recommendations?${new URLSearchParams(req.query)}`,
{
headers: {
'Authorization': `Bearer ${process.env.API_TOKEN}`, // Securely stored on server
'Content-Type': 'application/json'
}
}
);
res.json(response.data);
} catch (error) {
res.status(error.response?.status || 500).json({
error: error.response?.data || 'Failed to fetch data'
});
}
});
app.listen(PORT, () => {
console.log(`Server running on port ${PORT}`);
});
Then in your React component:
// React component
function ProductRecommendations({ productId }) {
const [recommendations, setRecommendations] = useState([]);
useEffect(() => {
// Call your own API endpoint instead
fetch(`/api/recommendations?currentUrl=${window.location.href}&refId=${productId}`)
.then(res => res.json())
.then(data => setRecommendations(data))
.catch(err => console.error('Error:', err));
}, [productId]);
// Render recommendations...
}
2. Using Next.js API Routes
If you're using Next.js, you can create API routes to handle authentication:
// pages/api/recommendations.js
export default async function handler(req, res) {
const { productId, url, ...otherParams } = req.query;
// Build parameters
const params = new URLSearchParams({
currentUrl: url || req.headers.referer,
refId: productId,
...otherParams
});
try {
const response = await fetch(
`https://<PA_END_POINT>/3.0/recommendations?${params.toString()}`,
{
headers: {
'Authorization': `Bearer ${process.env.API_TOKEN}`, // Stored as env variable
'Content-Type': 'application/json'
}
}
);
const data = await response.json();
res.status(200).json(data);
} catch (error) {
res.status(500).json({ error: 'Failed to fetch data' });
}
}
Then in your React component:
// React component
import { useState, useEffect } from 'react';
function ProductRecommendations({ productId }) {
const [recommendations, setRecommendations] = useState([]);
const [loading, setLoading] = useState(true);
useEffect(() => {
// Call your own API endpoint instead of the external API directly
fetch(`/api/recommendations?productId=${productId}&expandProductDetails=true`)
.then(res => res.json())
.then(data => {
setRecommendations(data);
setLoading(false);
})
.catch(err => {
console.error('Error:', err);
setLoading(false);
});
}, [productId]);
// Render recommendations...
}
3. Serverless Functions
For static React applications built with tools like Vite or similar, you can use serverless functions:
Using Netlify Functions:
// netlify/functions/recommendations.js
const axios = require('axios');
exports.handler = async function(event, context) {
const params = new URLSearchParams(event.queryStringParameters);
try {
const response = await axios.get(
`https://<PA_END_POINT>/3.0/recommendations?${params.toString()}`,
{
headers: {
'Authorization': `Bearer ${process.env.API_TOKEN}`,
'Content-Type': 'application/json'
}
}
);
return {
statusCode: 200,
body: JSON.stringify(response.data)
};
} catch (error) {
return {
statusCode: error.response?.status || 500,
body: JSON.stringify({
error: error.response?.data || 'Failed to fetch data'
})
};
}
};
4. Edge Functions/Middleware
Modern hosting platforms offer edge functions that run closer to users:
Using Cloudflare Workers:
// worker.js
addEventListener('fetch', event => {
event.respondWith(handleRequest(event.request));
});
async function handleRequest(request) {
const url = new URL(request.url);
// Handle recommendations API requests
if (url.pathname === '/api/recommendations') {
// Get query parameters
const params = new URLSearchParams(url.search);
// Create a new request to the actual API
const apiUrl = new URL('https://<PA_END_POINT>/3.0/recommendations');
apiUrl.search = params.toString();
const apiRequest = new Request(apiUrl, {
headers: {
'Authorization': `Bearer ${API_TOKEN}`, // Set in Worker environment variables
'Content-Type': 'application/json'
}
});
return fetch(apiRequest);
}
// For other routes, pass through
return fetch(request);
}
Best Practices
-
Store tokens securely:
- Use environment variables on your server/serverless platform
- Never commit tokens to version control
-
Implement proper CORS:
- Configure your backend to only accept requests from your frontend domain
- Prevents unauthorized domains from using your API proxy
-
Consider caching:
- Implement server-side caching for API responses to reduce API calls
- Use appropriate cache expiration based on content type
-
Add rate limiting:
- Protect your backend from abuse with rate limiting
- Prevents excessive calls to the APIs
-
Monitor and log:
- Track usage patterns and errors
- Set up alerts for unusual activity that might indicate token misuse
By implementing one of these approaches, you can securely use any API in our system from your React application without exposing sensitive credentials.