Authentication
Handling authentication is a critical part of any application that communicates with a secure API. Hyper Fetch provides a powerful and flexible way to manage authentication flows, including attaching tokens to requests and automatically refreshing them when they expire.
- How to automatically add authentication details (like tokens) to your requests.
- How to implement a seamless token refresh mechanism.
- How to prevent common issues like infinite refresh loops.
Authenticating Requests
To automatically attach authentication details like a Bearer token to your requests, you can use the onAuth method on
your client instance. This method registers a callback that will be executed for any request that has the auth: true
option set.
1. Configure the onAuth Callback
In your client configuration, use the onAuth method to define how to retrieve and attach your authentication token. In
this example, we'll get the token from localStorage.
import { createClient } from "@hyper-fetch/core";
const TOKEN_STORAGE_KEY = "auth_token";
export const client = createClient({
url: "https://api.example.com",
}).onAuth((request) => {
const authToken = localStorage.getItem(TOKEN_STORAGE_KEY);
// If the token exists, add it to the request headers
if (authToken) {
return request.setHeaders({
...request.headers,
Authorization: `Bearer ${authToken}`,
});
}
return request;
});
2. Mark Requests as Authenticated
For the onAuth callback to run, you must set the auth option to true when creating a request. This tells the
client that the request requires authentication.
const getUsers = client.createRequest()({
endpoint: "/users",
method: "GET",
auth: true, // This request will now go through the onAuth callback
});
// When you send this request, it will automatically have the Authorization header.
getUsers.send();
Refreshing Tokens
Access tokens are often short-lived for security reasons. When a token expires, the API will typically return a
401 Unauthorized error. We can gracefully handle this by using the onError client interceptor to refresh the token
and retry the original request.
To avoid getting stuck in an infinite loop of refresh attempts, we'll use the request.used property to ensure we only
try to refresh the token once per request.
Example
Here's how you can set up an onError interceptor to handle token refreshing:
import { createClient } from "@hyper-fetch/core";
const TOKEN_STORAGE_KEY = "auth_token";
const REFRESH_TOKEN_STORAGE_KEY = "refresh_token";
export const client = createClient({
url: "https://api.example.com",
})
.onAuth((request) => {
// ... same onAuth logic as before
const authToken = localStorage.getItem(TOKEN_STORAGE_KEY);
if (authToken) {
return request.setHeaders({
...request.headers,
Authorization: `Bearer ${authToken}`,
});
}
return request;
})
.onError(async (response, request) => {
const status = response.status;
const refreshToken = localStorage.getItem(REFRESH_TOKEN_STORAGE_KEY);
// If the error is 401, we have a refresh token, and we haven't tried refreshing yet
if (status === 401 && refreshToken && !request.used) {
// Create a request to your refresh token endpoint
const refreshTokenRequest = client.createRequest<{ response: { token: string; refreshToken: string } }>()({
endpoint: "/auth/refresh",
method: "POST",
});
// Send the refresh token to get a new pair of tokens
const { data, error } = await refreshTokenRequest.setPayload({ refreshToken }).send();
if (data) {
// Save the new tokens
localStorage.setItem(TOKEN_STORAGE_KEY, data.token);
localStorage.setItem(REFRESH_TOKEN_STORAGE_KEY, data.refreshToken);
// Retry the original request.
// setUsed(true) marks the request to prevent infinite loops.
return request.setUsed(true).send();
}
}
// If it's not a 401 or refreshing fails, return the original error response
return response;
});
In the example above:
- We check if the response status is
401, if arefreshTokenis available, and ifrequest.usedisfalse. - We create and send a new request to our
/auth/refreshendpoint. - If we successfully get new tokens, we store them in
localStorage. - We then retry the original request using
request.send(). Crucially, we callrequest.setUsed(true)first to prevent this logic from running again if the retry also fails. - If any of these conditions are not met, we simply pass through the original error response.
Cookie-Based Authentication
If your API uses cookies for authentication (e.g., httpOnly session cookies), Hyper Fetch handles them automatically
out of the box. The underlying browser fetch API or libraries like axios manage cookies seamlessly.
When your server responds with a Set-Cookie header, the browser securely stores the cookie and automatically includes
it in all subsequent requests to the same domain. You don't need any extra configuration in Hyper Fetch for this to
work.
API Key Authentication
For services that authenticate via an API key in headers or query parameters, you can use the onAuth callback just
like with bearer tokens:
export const client = createClient({
url: "https://api.example.com",
}).onAuth((request) => {
return request.setHeaders({
...request.headers,
"X-API-Key": process.env.API_KEY,
});
});
// Mark the request as requiring authentication
const getWeather = client.createRequest()({
endpoint: "/weather",
auth: true,
});
- Bearer tokens: Attach JWT or OAuth2 access tokens to every authenticated request.
- Token refresh: Automatically retry on 401 by refreshing the token in
onError. - API keys: Add static or environment-based API keys to request headers.
- Cookie auth: Works out of the box with no configuration needed.
- Multi-tenant auth: Switch tokens based on the request's endpoint or custom properties.
You've learned how to build a robust authentication flow with Hyper-Fetch!
- You can use the
.onAuth()client method to attach tokens to requests marked withauth: true. - You can use the
.onError()client method to intercept failed requests and handle token refreshing. - You can prevent infinite refresh loops by using the
.setUsed(true)method before retrying a request.
