Axios with OAuth 2.0 PKCE Flow Guide
This article explains how to configure and use the Axios HTTP client to interact with APIs protected by OAuth 2.0 using the Proof Key for Code Exchange (PKCE) flow. It covers how Axios facilitates token exchange, automatically attaches Bearer tokens via request interceptors, and seamlessly handles token expiration and refreshes using response interceptors.
The Role of Axios in OAuth 2.0 PKCE
Axios does not natively implement OAuth 2.0 specifications out of the box; instead, it serves as the transport layer to execute the HTTP requests required by the PKCE flow.
The PKCE flow consists of two main phases where Axios is utilized:
- Token Acquisition: Sending a
POSTrequest with the authorization code and cryptographiccode_verifierto the authorization server's token endpoint to receive anaccess_tokenandrefresh_token. - Authenticated Requests: Attaching the received
access_tokento subsequent requests directed at protected resource endpoints.
// Example: Exchanging the authorization code for tokens
async function exchangeCodeForTokens(code, codeVerifier) {
const response = await axios.post('https://auth.example.com/oauth/token', {
grant_type: 'authorization_code',
client_id: 'your-client-id',
code_verifier: codeVerifier,
code: code,
redirect_uri: 'https://yourapp.com/callback'
}, {
headers: { 'Content-Type': 'application/x-www-form-urlencoded' }
});
return response.data; // contains access_token, refresh_token, expires_in
}Attaching Tokens with Request Interceptors
Once the access token is acquired, every request to a protected
endpoint must include it in the Authorization header as a
Bearer token. Axios request interceptors automate this by injecting the
token before any HTTP request is dispatched.
import axios from 'axios';
const apiClient = axios.create({
baseURL: 'https://api.example.com'
});
apiClient.interceptors.request.use((config) => {
const token = getStoredAccessToken(); // Retrieve from secure memory or storage
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
}, (error) => {
return Promise.reject(error);
});Handling Token Refresh with Response Interceptors
Access tokens are short-lived. When an access token expires,
protected endpoints return a 401 Unauthorized HTTP status
code. Axios response interceptors can catch this specific error, use the
stored refresh_token to request a new access token, update
the headers, and replay the original request transparently.
apiClient.interceptors.response.use(
(response) => response,
async (error) => {
const originalRequest = error.config;
// Check if error is 401 and the request hasn't been retried yet
if (error.response?.status === 401 && !originalRequest._retry) {
originalRequest._retry = true;
try {
const refreshToken = getStoredRefreshToken();
const response = await axios.post('https://auth.example.com/oauth/token', {
grant_type: 'refresh_token',
client_id: 'your-client-id',
refresh_token: refreshToken
}, {
headers: { 'Content-Type': 'application/x-www-form-urlencoded' }
});
const newAccessToken = response.data.access_token;
storeNewTokens(response.data);
// Update the authorization header and retry original request
originalRequest.headers.Authorization = `Bearer ${newAccessToken}`;
return apiClient(originalRequest);
} catch (refreshError) {
// Refresh token failed or expired; redirect to login
logoutUser();
return Promise.reject(refreshError);
}
}
return Promise.reject(error);
}
);Best Practices for Axios with PKCE
- Manage Request Queues During Refresh: If multiple
requests trigger a
401concurrently, ensure your interceptor queues outgoing requests so only a single refresh token call is made to the identity provider. - Token Storage: In browser environments, store
tokens securely (such as in-memory variables or secure,
HttpOnly,SameSitecookies) rather than plainlocalStorageto mitigate Cross-Site Scripting (XSS) risks. - Axios Instances: Always use dedicated Axios
instances (
axios.create()) instead of the globalaxiosobject so that auth interceptors apply strictly to intended API endpoints.