Next.js Discord

Discord Forum

Implementing sessions and refresh tokens correctly

Arboreal ant posted this in #help-forum
Open in Discord
Arboreal antOP
Hi All,

I've set up next-auth using keycloak as an oidc provider (it pretty much works the same as any other oidc provider, so don't worry about the keycloak bit too much) and am having difficulty getting automatic token refreshing working nicely. It's very janky atm lol.

I've set up a jwt callback for next-auth which refreshes the token if it's expired, and I've set up the withAuth middleware protecting certain routes in the app.

# middleware.ts
authorized: async ({ token }) => {
  return !!token;

What I'd really really like to have happen, is anytime someone navigates to a protected page and their token isn't valid, we seamlessly try to refresh the token and continue with loading the page if it's successful.

What I've had to do is wrap all the protected routes in a wrapper which uses useSession with a useEffect to signOut the user if there's an error refreshing the token, and if the token isn't valid calls the update hook to attempt to retrigger the refresh.

This all sort of works, but now I'm trying to hit some external api endpoints with the accessToken and it isn't valid. It looks like you can't update cookies from middleware, so I'm completely stumped about how to properly refresh the users token in server components.

Has anyone managed to get next-auth working nicely in both the RSC and client components context? And has anyone managed to get automatic token refreshing working nicely?

I'm open to handling auth in a different way than using next-auth too. It's been a pretty frustrating onboarding experience with it and I'm not a fan of all the hacks I've had to put in to get it semi working.

If I were making this just a react SPA then I'd store a httpOnly cookie on the client, and my backend middleware would refresh the cookie if it's expired. The frontend would only need to listen for 401's and then redirect to the login page if it's expired. Is there a simple way to set that up in next?

6 Replies

Arboreal antOP
A bit more clarity on my current approach:
1. if a user tries to access a protected route without a next-auth session, we redirect them to keycloak
2. the user signs in and hits the next-auth callback
3. we create a session, and store tokens
4. a wrapper calls useSession to get the current session state and/or asks next-auth to update the session if needed
5. we pass the accessToken to the backend api
6. if the backend throws a 401, we panic and ask this question on discord

I'm thinking I might just rip out next-auth as it's a bit of a pain for our usecase. My current half baked plan is:

1. have the separate api handle all of the session and authN logic via a passport strategy.
2. store the session in a httpOnlyCookie
3. have next middleware which just checks if the cookie exists to protect routes.
4. have the api automatically refresh expired sessions if they come in
5. if a token can't be refreshed, throw a 401 from the api
6. if the next middleware sees a 401, redirect to the login page.

What do you all think? This approach seems much simpler to reason about to me. Next is only responsible for the frontend, and the backend handles all the usual backend stuff.
Arboreal antOP
I had a look at iron-session but it seemed to be linked to email,password auth, plus it needed to be set up on the backend as well.

The next docs have quite a simple middleware example which looks like exactly what I'm after.

We're already using passport on the backend so I was thinking I'd implement a passport strategy with sessions on the backend, and then just have the nextjs frontend make a call to the /auth/login route or something to authenticate. Then have the backend login to keycloak via oidc and save the tokens in the session. It's a touch more extensible that way as well if we want to expand the types of authN (i.e. add google oauth or something).
Asian black bear
this sounds a little like what i'm running into here?
tldr is that when the user visits a page with an expired token, middleware refreshes that token and sets the cookie. but in reality the cookie doesn't get "applied" until the user refreshes the page, which results in the first visit erroring with a 401
Arboreal antOP
Hey sorry I missed your message.
I ended up implementing completely backend driven auth. I have a passport strategy with express-session which sets a session token for client. Then when making any fetch requests from the client side it's already got the cookie, and from the server side I had to call headers() and pass in headers: new Headers(headers()) to pass the client's headers through next and to the backend. The backend handles refreshing the session cookies, and if a 401 gets thrown we know that the cookie has expired so it redirects to the sign in page.

The middleware on next only reads the cookie and checks it exists for any protected routes. Anything more than that is handled by the backend.