Where should I store the logic for JWT refresh
Unanswered
American Chinchilla posted this in #help-forum
American ChinchillaOP
I'm new using NextJS, and I'm a little bit confused. I'm using a Django backend that provides the endpoints for the accessToken and refreshToken. So in the middleware, I want to check if the user is authenticated, for that I use the request cookies to see if the accessToken exist, if not I check if the refreshToken exist, if it does, I want to refresh it, using the endpoint that Django provides. But I'm not sure where does that logic should be, in the proxy.ts? in the /api directory?
69 Replies
to check server side authentication, you can use the factory pattern.
It would be like you can create a function, which checks auth and executes another function, which returns the response
It would be like you can create a function, which checks auth and executes another function, which returns the response
Kawakawa
Can you clarify about this proxy.ts? More Clealry? It's purpose and something like that.
I want to check if the user is authenticated, for that I use the request cookies to see if the accessToken exist, if not I check if the refreshToken exist, if it does, I want to refresh it, using the endpoint that Django provides.
updating cookies can only be done in 2 places:
- server action and
- api/route handlers (route.ts file)
but not page.tsx or server components
however, getting cookies can be done in all places
that being said, you must ensure that your logic fits this constraint and after that can always implement them to where it is needed.
so the most obvious choice for me is to make a reusable function in
/src/lib/auth.ts or /src/app/auth.ts (wherever) that you can use in your page.tsx or server actions or api/ folders or route.ts.just like Anay said, proxy.ts isn't supposed to be used as primary way to validate auth. Its best used as naive/simple checking since its run at every incoming call.
@Kawakawa Can you clarify about this proxy.ts? More Clealry? It's purpose and something like that.
proxy.ts is just a simple script file that runs at every network request. its intended use is only for simple short running operations like modifying headers and naive request filterings. so best to not use fetching or db calls in this file.
@alfonsüs ardani proxy.ts is just a simple script file that runs at every network request. its intended use is only for simple short running operations like modifying headers and naive request filterings. so best to not use fetching or db calls in this file.
Kawakawa
But we can validate the auth in this proxy.ts(middleware.ts)?
@Kawakawa But we can validate the auth in this proxy.ts(middleware.ts)?
best practices: you can do it naively, as a first-line of defense, but not fully.
such as, checking existence of the token without validating the token and without calling db
if no token prsence, then block request
that kind of thing, is fast and doesnt take long time, which is what i meant by naive checking
@alfonsüs ardani such as, checking existence of the token without validating the token and without calling db
Kawakawa
Token that is on the client - browser store??
yea, on cookies
you STILL need to check auth again later on server components and routes
@alfonsüs ardani > I want to check if the user is authenticated, for that I use the request cookies to see if the accessToken exist, if not I check if the refreshToken exist, if it does, I want to refresh it, using the endpoint that Django provides.
updating cookies can only be done in 2 places:
- server action and
- api/route handlers (route.ts file)
but not page.tsx or server components
however, getting cookies can be done in all places
that being said, you must ensure that your logic fits this constraint and after that can always implement them to where it is needed.
so the most obvious choice for me is to make a reusable function in `/src/lib/auth.ts` or `/src/app/auth.ts` (wherever) that you can use in your page.tsx or server actions or api/ folders or route.ts.
just like Anay said, proxy.ts isn't supposed to be used as primary way to validate auth. Its best used as naive/simple checking since its run at every incoming call.
Kawakawa
Yeah proxy.ts is run on every server request : but what do you mean by this serveractions and server components - more cleary - intuitively. sorry for bother you.
Nowadays, since we use AI too much, we are getting far to the base.
Nowadays, since we use AI too much, we are getting far to the base.
so my assuption is
to check auth, you need to access cookies. since thats where auth token are usually stored (and is best practice to store auth tokens in cookies)
so you make function like this so that you can reuse it in many different places:
but then OP also said they need to refresh it if its invalid
refreshing a token implies setting a new cookie
im just giving precaution that if you make reusable function, that it might throw error in server components since you can't set cookie in server components
export async function checkAuth() {
const auth = await cookies() // get cookie from current request context
await refreshIfInvalidToken(auth) // MIGHT set cookie here
return isValid(auth) // your logic to validate cookie
}but then OP also said they need to refresh it if its invalid
refreshing a token implies setting a new cookie
im just giving precaution that if you make reusable function, that it might throw error in server components since you can't set cookie in server components
just make sure you fit your logic so that it can be used in many places
im just saying that you might need to retrofit your auth logic a bit to acommodate nextjs's restrictions
you might need to create separate functions to acommodate each environment like:
// In the server environment
export async function checkAuthInProxy()
export async function checkAuthInServerComponents()
export async function checkAuthAPIHandler()
export async function checkAuthInServerAction()
// or just "unify" it into one
export async function checkServerAuth()
// In the client environment
export async function useAuth()
// -> might call server action to check auth?
// -> might fetch /api/auth? checkAuth function - will be in proxy.ts?
as in usage?
Kawakawa
We can guess the usage - where will we write it?
up to you?
write what? write definition or usage?
Kawakawa
Where should we write this function?
yes, write what? definition of the function or the usage of the function?
Kawakawa
I consider the architecture.
so it will be helper and will use it in several places?
the point is that you only do naive checking in proxy.ts and not full auth check in proxy.ts. then do full checking at server components/route handler/server actions.
there is no strict practices on where you define your functions.
there is no strict practices on where you define your functions.
Kawakawa
We didn't consider about these thing too much since Cursor do everything. I learned a lot.
I am getting a little confused. native checking in proxy, and should we check auth again in app api route and server actions? I cannot know AI will handle these.
I am getting a little confused. native checking in proxy, and should we check auth again in app api route and server actions? I cannot know AI will handle these.
naive
not native
and should we check auth again in app api route and server actions?yes you should.
Kawakawa
naive check
@alfonsüs ardani > and should we check auth again in app api route and server actions?
yes you should.
Kawakawa
I see. you are great.
Since we usually use completed auth system like supabase normally. We don't care about it much - out of the box.
How do you think about these?
Since we usually use completed auth system like supabase normally. We don't care about it much - out of the box.
How do you think about these?
its great
supabase/auth0/clerk/auth.js takes care all of these but it "locks" you in with their back end
the OP wants to connect with their Django backend so its a bit tricky to use supabase/auth0/clerk
but it should be configurable with next-auth since next-auth is backend agnostic
honestly, if you understand how it works, theres no reason to not implement it yourself
and thats what im trying to do, to help clear misunderstanding if you're trying to implement your own auth architecture in Next.js
@alfonsüs ardani honestly, if you understand how it works, theres no reason to not implement it yourself
Kawakawa
Yeah, now i really feel it
Do you think we can the auth system like supabase auth from scratch?
yes, most definetly. many have done it.
Kawakawa
Normally, FastAPI or Flask is trend rather than Jango. In these case, can we use supabase as a auth and db system with FastAPI and Flask?
if anything, server implementation is easy. the client UX is the hard part xD
atleast for me
@Kawakawa Normally, FastAPI or Flask is trend rather than Jango. In these case, can we use supabase as a auth and db system with FastAPI and Flask?
im not well versed in different services and languages, you might need to ask others for that, or create a new thread since it is already beyond the context of this thread but i'd imagine it would not be worth the hassle
@alfonsüs ardani im not well versed in different services and languages, you might need to ask others for that, or __create a new thread__ since it is already beyond the context of this thread but i'd imagine it would not be worth the hassle
Kawakawa
Thx. One more thing. In that case, we will have to implement social login and two factor auth from scratch, can we handle these?
social login is easy since its just oauth. you can see [arctic's](https://arcticjs.dev/) documentation to implement it. 2FA froms cratch yes. but i heard Clerk/Auth0 handle these for you easily
arctic is just an oauth wrapper for popular provider, it is not a full package like next-auth
American ChinchillaOP
Hey! Thanks for the help regarding the auth.
I'm still trying to understand some auth things.
First, I created this new file called
I'm still trying to understand some auth things.
First, I created this new file called
session.ts to handle all the session related dataimport { cookies } from 'next/headers';
import { ACCESS_TOKEN_COOKIE, ACCESS_TOKEN_LIFETIME, REFRESH_TOKEN_COOKIE, REFRESH_TOKEN_LIFETIME } from '@/src/constants';
// Cookie options for security
const cookieOptions = {
httpOnly: true,
secure: process.env.NODE_ENV === 'production',
sameSite: 'lax' as const,
};
export async function setAuthTokens(accessToken: string, refreshToken: string) {
const cookieStore = await cookies();
// Set access token
cookieStore.set(ACCESS_TOKEN_COOKIE, accessToken, {
...cookieOptions,
maxAge: ACCESS_TOKEN_LIFETIME
});
// Set refresh token
cookieStore.set(REFRESH_TOKEN_COOKIE, refreshToken, {
...cookieOptions,
maxAge: REFRESH_TOKEN_LIFETIME
});
}
export async function getAccessToken(): Promise<string | undefined> {
const cookieStore = await cookies();
return cookieStore.get(ACCESS_TOKEN_COOKIE)?.value;
}
export async function getRefreshToken(): Promise<string | undefined> {
const cookieStore = await cookies();
return cookieStore.get(REFRESH_TOKEN_COOKIE)?.value;
}
export async function clearAuthTokens() {
const cookieStore = await cookies();
cookieStore.delete(ACCESS_TOKEN_COOKIE);
cookieStore.delete(REFRESH_TOKEN_COOKIE);
}These are my actions for the auth
'use server';
import { redirect } from 'next/navigation';
import { setAuthTokens, clearAuthTokens, getRefreshToken } from './session';
import type { LoginCredentials, AuthResponse } from '../types/auth';
const API_URL = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:8000';
export async function loginAction(credentials: LoginCredentials) {
try {
const response = await fetch(`${API_URL}/token/`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(credentials),
});
if (!response.ok) {
const error = await response.json();
return {
success: false,
error: error.message || 'Invalid credentials',
};
}
const data: AuthResponse = await response.json();
// Store tokens in httpOnly cookies
await setAuthTokens(data.access, data.refresh);
return {
success: true,
};
} catch (error) {
console.error('Login error:', error);
return {
success: false,
error: 'An error occurred during login',
};
}
}
export async function refreshTokenAction(): Promise<string | null> {
try {
const refreshToken = await getRefreshToken();
if (!refreshToken) {
return null;
}
const response = await fetch(`${API_URL}/api/token/refresh/`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ refresh: refreshToken }),
});
if (!response.ok) {
await clearAuthTokens();
return null;
}
const data = await response.json();
// Update tokens
await setAuthTokens(data.access, refreshToken);
return data.accessToken;
} catch (error) {
console.error('Token refresh error:', error);
await clearAuthTokens();
return null;
}
}
export async function logoutAction() {
await clearAuthTokens();
redirect('/login');
}And I'm going to use Axios, so I can use interceptors and check if the backend returns 401 and handling the token refresh, but in the middleware (proxy.ts) how I'm I supposed to check if the token is expired and how should I refresh it and set it again to a cookie?
@American Chinchilla And I'm going to use Axios, so I can use interceptors and check if the backend returns 401 and handling the token refresh, but in the middleware (proxy.ts) how I'm I supposed to check if the token is expired and how should I refresh it and set it again to a cookie?
yeah thats the thing though, i mean if we talking about best practices here, id suggest not doing any fetch() at all in the middleware/proxy
theres a reason next changed the name from middleware to proxy
as stated above, you can do naive checking of the token by simply checking the expiry date of the cookie or jwt (if used) and reject the token up front
as per setting the cookie, you can definetly do this on a client-side call on first mount of the page using useEffect(() => { ... } , []) or just refresh the token when user does an action
however, due to design constraint of nextjs, you just can't refresh the token in server page load like page.tsx or layout.tsx since you can't set cookies in server components
All in all the arch should look like this:
on proxy.ts:
- check if token exist,
- if not then reject or do nothing (since can't refresh)
- if ok then continue process
on page/layout.tsx:
- check if token exist,
- validate token by doing fetch call,
- if bad token, try refresh token,
- if refresh token valid, continue but can't set refresh token.
- if bad refresh token, reject
on api or route.ts or server action:
- check if token exist,
- validate token by doing fetch call,
- if bad token, try refresh token,
- if refresh token valid, continue AND set refresh token.
- if bad refresh token, reject and/or delete invalid tokens
on proxy.ts:
- check if token exist,
- if not then reject or do nothing (since can't refresh)
- if ok then continue process
on page/layout.tsx:
- check if token exist,
- validate token by doing fetch call,
- if bad token, try refresh token,
- if refresh token valid, continue but can't set refresh token.
- if bad refresh token, reject
on api or route.ts or server action:
- check if token exist,
- validate token by doing fetch call,
- if bad token, try refresh token,
- if refresh token valid, continue AND set refresh token.
- if bad refresh token, reject and/or delete invalid tokens
@alfonsüs ardani proxy.ts is just a simple script file that runs at every network request. its intended use is only for simple short running operations like modifying headers and naive request filterings. so best to not use fetching or db calls in this file.
American Crocodile
Is that because it happens on the edge, meaning the db call would be even further away from the server and db but closer to the user