How can I attach my JWT to every axios call? #3550
-
Question 💬I am building a Next.js application. I am using NextAuth for authentication and Axios for API calls. The problem I am facing is that I want to attach the JWT with every Axios call I make. I have an Axios instance created as:
I can get the jwt from getSession() function. But the problem is that function is asynchronous. If I try to get JWT from this from the getSession() function, I always get a "Promise" instead of value. I can't use the useSession() hook as this is a plain Javascript function instead of React component. PS: I am using Strapi which sends the JWT after successful login. How to reproduce ☕️N/A Contributing 🙌🏽No, I am afraid I cannot help regarding this |
Beta Was this translation helpful? Give feedback.
Replies: 15 comments 54 replies
-
You can make the code you have use
|
Beta Was this translation helpful? Give feedback.
-
@jaketoolson thanks for the great help. It fixed my issue. For future readers, this is the Axios instance I am using and it is working fine for me.
|
Beta Was this translation helpful? Give feedback.
-
Hi there, I've found a solution that works great for me : I have a simple axios instance and a // http.ts
import axios from 'axios'
const http = axios.create({
baseURL: process.env.NEXT_PUBLIC_API_PATH,
})
const setToken = (token: string) => {
http.defaults.headers.common['Authorization'] = `Bearer ${token}`
}
export { http, setToken } Then I have a wrapper component that manage my session, when // SessionLoader.tsx
import { useSession } from 'next-auth/react'
import { useRouter } from 'next/router'
import { setToken } from '../utils/http.utils'
const SessionLoader = ({ children }: { children: React.ReactNode }) => {
const router = useRouter()
const session = useSession()
if (session.status === 'loading') {
return <div className="loading" />
}
if (session.status === 'authenticated') {
setToken(session.data.user.access_token)
}
if (router.pathname === '/auth/login' && session.status === 'authenticated') {
router.push('/admin')
}
return <>{children}</>
}
export default SessionLoader I've stress tested this solution in my project and it work like a charm ! @always-maap maybe it can help you :) |
Beta Was this translation helpful? Give feedback.
-
I'm doing what @pvandamme showed, thank you for that. export const useAuth = () => {
const { data, status } = useSession();
if (status !== "loading") {
if (status === "authenticated") {
setAuthToken(data.accessToken);
} else if (status === "unauthenticated") {
setAuthToken("");
}
}
return {
session: data,
loading: status === "loading",
isAuthenticated: status === "authenticated",
};
}; Also setting my token like this: const setAuthToken = (token) => {
if (!!token) {
axiosInstance.defaults.headers.common["Authorization"] = `Bearer ${token}`;
} else {
delete axiosInstance.defaults.headers.common["Authorization"];
}
}; |
Beta Was this translation helpful? Give feedback.
-
I recommend using secure, http only cookies to store access token and use it for axios |
Beta Was this translation helpful? Give feedback.
-
For anyone running into the same issue, I found the above solutions not to be sufficient (getSession is called on every Axios request, and it didn't work with refreshed tokens). See my implementation of an Axios client. Multiple calls to the server (getSession) are avoided by checking if the session is still valid. This also works for tokens that have been silently refreshed.
|
Beta Was this translation helpful? Give feedback.
-
How can I switch between server-side and client-side requests? I'm using Next.js 13 with Should I validate if in the client |
Beta Was this translation helpful? Give feedback.
-
I managed to arrive at this solution by handling tokens both in the client components and on the server side with next 13. I'm not calling getSession with each request only when I identify that the Authorization header of the axios instance is empty. My api client:
my next auth config:
This way I guarantee that the token will always be set in the axios instance both on the client and on the front, without making many calls to getSession. |
Beta Was this translation helpful? Give feedback.
-
I'm building a Next.js application using Axios for API calls. I've created a separate service for Axios on the client-side and an authService for handling login. The login method takes email and password, hits a login endpoint, and returns the authToken and refreshToken. I can then store the accessToken in cookies (or somewhere else), and this works fine. However, after the token expires, the interceptor refreshes the token and makes subsequent API calls. Even though the refresh token provides the correct response, I suspect there might be an issue with the cookies. I'm not sure what's causing the problem. `import axios from "axios"; const apiService = axios.create({ apiService.interceptors.request.use(
}, apiService.interceptors.response.use(
}, async function refreshAuthToken(refreshToken: string) { const ApiService = { async post(endpoint: string, data = {}, config = {}) { async postFormData(endpoint: string, formData: FormData, token: string) { async put(endpoint: string, data = {}, config = {}) { async patch(endpoint: string, data = {}, config = {}) { async delete(endpoint: string, config = {}) { |
Beta Was this translation helpful? Give feedback.
-
everybody here overcomplicates this to the extreme: import { useAuth } from "@clerk/clerk-react"
import axios from "axios"
axios.defaults.baseURL = env.API_BASE_URL
let usedInterceptor = false
export function useInterceptor() {
const { getToken } = useAuth()
if (usedInterceptor) return
axios.interceptors.request.use(async (config) => {
const token = await getToken()
config.headers["Content-Type"] = "application/json"
config.headers.Authorization = `Bearer ${token}`
return config
})
usedInterceptor = true
} To use in client side react. This is the default axios client, but you can tweak this to put your custom client if u want. |
Beta Was this translation helpful? Give feedback.
-
This is the solution I arrived at, I will be accepting feedback and improvements, Thank you! api.ts const apiUrl = 'http://api:80'; let isRefreshing = false; const processQueue = (error, token = null) => {
}; const setupAxiosInstance = async (req) => {
}; export default setupAxiosInstance; // auth.config.ts const TOKEN_EXPIRATION_MARGIN = 1 * 60 * 1000; export const authConfig = {
}, export async function refreshAccessToken(token) {
} catch (error) {
} // auth.ts async function getUser(accessToken: string): Promise {
} export const {
});` // middleware.ts const { auth } = NextAuth(authConfig); export default auth((req) => { const isAuthenticated = !!req.auth; const isPublicRoute = PUBLIC_ROUTES.includes(nextUrl.pathname); if (isPublicRoute && isAuthenticated) if (!isAuthenticated && !isPublicRoute) export const config = {
|
Beta Was this translation helpful? Give feedback.
-
I might be a bit late to the party but here's how I solved this in both client and server sideAXIOS INSTANCE
REQUEST INTERCEPTOR
In case there's a edge case or if there's a better way please let me know. Otherwise works like a charm. |
Beta Was this translation helpful? Give feedback.
-
I have gathered different parts of the above discussions and implemented the solution as follows. Enjoy. package.json: "next": "14.2.3",
"next-auth": "^4.24.7",
"react": "^18" Providers: <SessionNextProvider>
<SessionGuard>
<AxiosProvider>
{children}
</AxiosProvider>
</SessionGuard>
</SessionNextProvider> Session Next Provider: 'use client';
import { SessionProvider } from 'next-auth/react';
import { ReactNode } from 'react';
export default function SessionNextProvider({ children }: { children: ReactNode }) {
return <SessionProvider refetchInterval={30}>{children}</SessionProvider>;
} Session Guard: 'use client';
import { useSession } from 'next-auth/react';
import { ReactNode, useEffect } from 'react';
import { setLastSession } from './AxiosProvider';
export default function SessionGuard({ children }: { children: ReactNode }) {
const { data, status } = useSession();
useEffect(() => {
if(!data) return;
setLastSession(data as any);
}, [data]);
if(status === 'loading') return null;
return <>{children};</>;
} Axios Provider: 'use client';
import { instance } from '@/(core)/api';
import { Session } from 'next-auth';
export const setLastSession = (session: Session) => {
lastSession = session;
};
let lastSession: Session | null = null;
const AxiosProvider = ({ children }: { children: React.ReactNode }) => {
instance.interceptors.request.use(
config => {
if (!lastSession) {
return Promise.reject(new Error('Try again later'));
}
config.headers.Authorization = `Bearer ${lastSession.accessToken}`;
return config;
},
error => {
return Promise.reject(error);
}
);
instance.interceptors.response.use(
response => {
return response;
},
error => {
return Promise.reject(error);
}
);
return <>{children}</>;
};
export default AxiosProvider; |
Beta Was this translation helpful? Give feedback.
-
Not sure if this helps anyone but here's how i used it // axios.ts import axios from 'axios'; export const axiosWithAuth = async () => { const isServer = typeof window === 'undefined'; let session = null; if (isServer) { if (session) { return instance; then in your client or server components you can do like this:- |
Beta Was this translation helpful? Give feedback.
-
Call the session in a server component, then pass it to a client component, finally assign the token from session to axios. // ServerComponent.js
export default async function ServerComponent(children) {
const session = await getSession();
return <ClientComponent session={session}>{children}</ClientComponent>
}
// ClientComponent.js
'use client'
import { SessionProvider} from 'next-auth/react'
import { axios } from "axios"
export default async function ClientComponent(session, children) {
axios.defaults.headers.common['Authorization'] = `Bearer ${session?.tokens.access}`
return <SessionProvider session={session}>{children}</SessionProvider>
} |
Beta Was this translation helpful? Give feedback.
useSession()
can only be used for client side.getSession
is used for client and server side.You can make the code you have use
async/await
: