Next.js Discord

Discord Forum

Handling expired/cut session_id

Unanswered
Tornjak posted this in #help-forum
Open in Discord
TornjakOP
Hi everyone,

I am currently working on a Next.js application in which I use NextAuth to log in and create a guest session. When entering the page, a unique session_id is generated, which I have to attach to the header of each request to the external API.

The first problem I encounter is the session. (Currently, the duration of these sessions is set to the same on the API and NEXT sides) - it is still far from ideal, but for now it is enough.

The second problem: access to resources
All resources require a valid session_id. When the session on the API side expires or is deleted, each request returns 401 Unauthorized and throws an exception.

Here the problem is a bit more complicated because I have X API endpoints and X server actions. These functions sometimes throw exceptions, which leads to the application crashing (errors are not handled properly).

What I would like to achieve if possible is:
Automatic session refresh, on the first 401. That is, catch the error, refresh what is needed and re-query with a new session_id.

As I mentioned, I have this X (quite a lot) and I don't want to write it in every endpoint. I tried to make some wrapper but here I had a problem updating cookies. Some code below to better illustrate the situation:
export default async function RootLayout({ children }: { children: React.ReactNode }) {
  const session = await auth()
  const SESSION_ID = session?.user?.data?.session_id
  const countries = await getCountriesData(SESSION_ID)
  
  return (
    <html suppressHydrationWarning lang={locale} className={poppins.variable}>
      <body suppressHydrationWarning>
        <NextAuthProvider session={session}>
          ...
        </NextAuthProvider>
      </body>
    </html>
  )
}

2 Replies

TornjakOP
server action:
'use server'

import { BASE_URL } from '@/config'

import { CountryOption, Country } from '@/types/FormsTypes'

export const getCountriesData = async (SESSION_ID: string): Promise<CountryOption[]> => {
  try {
    const response = await fetch(`${BASE_URL}/api/dictionary/country`, {
      method: 'GET',
      cache: 'no-store',
      headers: {
        accept: 'application/json',
        'X-SESSION-ID': SESSION_ID,
      },
    })
    if (!response.ok) {
      throw new Error('Błąd podczas pobierania danych o krajach')
    }
    const countries: Country[] = await response.json()
    return countries.map((country) => ({
      symbol: country.ikraje,
      name: country.okraje.PL,
      dostawa_wg_ikody: country.dostawa_wg_ikody,
    }))
  } catch (error) {
    console.error('Error fetching countries:', error)
    return []
  }
}

NEXT API ENDPOINT:
import { NextRequest, NextResponse } from 'next/server'
import { fetchCountries } from '@/actions/dictionary/dictionary-actions'
import { auth } from '@/helpers/auth'
export const dynamic = 'force-dynamic'
export async function GET(req: NextRequest): Promise<NextResponse> {
  try {
    const SESSION_ID = req.headers.get('X-SESSION-ID') || (await auth())?.user.data.session_id
    if (!SESSION_ID) {
      return new NextResponse(JSON.stringify({ error: 'Autoryzacja zakończona niepowodzeniem' }), { status: 401 })
    }

    const countries = await fetchCountries(SESSION_ID)
    return new NextResponse(JSON.stringify(countries), {
      status: 200,
      headers: { 'Content-Type': 'application/json' },
    })
  } catch (e) {
    console.error('No authorization', e)
    return new NextResponse('No authorization', { status: 500 })
  }
}
server action again:
export const fetchCountries = async (SESSION_ID: string): Promise<CountriesProps> => {
  try {
    const response = await fetch(`${API_URL}/dictionary/country`, {
      method: 'GET',
      cache: 'no-store',
      headers: {
        Accept: 'application/json',
        'X-SESSION-ID': SESSION_ID,
      },
    })

    if (!response.ok) {
      const errorMessage = await response.text()
      console.error('Failed to fetch countries data', errorMessage)
      throw new Error('Failed to fetch countries data')
    }

    const { data: countries } = await response.json()
    return countries as CountriesProps
  } catch (e) {
    console.error('Error fetching countries data:', e)
    throw e
  }
}

I have many questions, not necessarily many answers.

I would like to do something great at the lowest possible cost.

I have already tried to create something like this:

'use server'
export async function fetchWithAuthServer(url: string, options?: RequestInit) {
  const response = await fetch(url, options)
  if (response.status === 401) {
    throw new Error('Unauthorized')
  }

  return response
}

but here I have a problem, how can I update the cookie from next auth.