I am using the nextjs auth0 package ( "@auth0/nextjs-auth0": "^4.0.2", ) and have an implementation where we update the auth0 session object with what we need for our api, i.e additional access and refresh tokens.
The problem I am having is when I need to refresh the access token with our refresh token. This we want to do either within 5 mintues of the access token expiring or when getting a 401, then update the same session with the new tokens. Since the Session
data object is a plain object, it seems ok to do it this way.
The problem is that I get the following error after the browser is stale and I reload it. However refreshing works when testing and lowering the expiration. Next complains that we have to update the cookie in a server action or component, but this logic sits in a file with 'use server' at the top.
'use server';
import { auth0 } from '../auth0-config';
import { tokenToExpire } from '../auth-utils';
import { NextResponse } from 'next/server';
import { SessionData } from '@auth0/nextjs-auth0/types';
const BASE_URL = process.env.FISHDATA_API_BASE;
export const fetchApi = async (
endpoint: string,
options: FetchOptions = {}
): Promise<Response> => {
const MAX_RETRIES = 3;
const retryCount = options.retryCount || 0;
if (retryCount >= MAX_RETRIES) {
return NextResponse.rewrite(new URL('/login', process.env.NEXT_PUBLIC_APP_BASE_URL));
}
let session = await auth0.getSession();
if (!session) {
return NextResponse.rewrite(new URL('/login', process.env.NEXT_PUBLIC_APP_BASE_URL));
}
if (!session?.fishdata_access_token) {
throw new AuthError('No access token found', 401);
}
// Always check if token needs refresh
if (tokenToExpire(session.fishdata_access_token as string)) {
await refreshToken(session);
session = await auth0.getSession();
if (!session?.fishdata_access_token) {
throw new AuthError('Token refresh failed', 401);
}
}
const requestHeaders: HeadersInit = {
'Content-Type': 'application/json',
Authorization: `Bearer ${session.fishdata_access_token}`,
...(options.headers || {})
};
// Make the API request
const response = await fetch(`${BASE_URL}${endpoint}`, {
...options,
headers: requestHeaders
});
// Handle 401 responses
if (response.status === 401) {
await refreshToken(session);
return fetchApi(endpoint, {
...options,
retryCount: retryCount + 1
});
}
return response;
};
const refreshToken = async (session: SessionData) => {
try {
const refreshResponse = await fetch(`${BASE_URL}/auth/refresh`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ refresh_token: session.fishdata_refresh_token })
});
const host = process.env.NEXT_PUBLIC_APP_BASE_URL;
if (!refreshResponse.ok) {
return NextResponse.rewrite(new URL('/login', host));
}
const data = await refreshResponse.json();
if (!data.access_token) {
throw new Error('No access token in refresh response');
}
// Update the session with new tokens
await auth0.updateSession({
...session,
fishdata_access_token: data.access_token,
fishdata_refresh_token: data.refresh_token
});
return { success: true };
} catch (error) {
console.error('Token refresh failed:', error);
throw error;
} finally {
return NextResponse.rewrite(new URL('/login', process.env.NEXT_PUBLIC_APP_BASE_URL));
}
};