Next.js Discord

Discord Forum

Next Auth with external authorization service.

Unanswered
Korat posted this in #help-forum
Open in Discord
KoratOP
Since i'm already using an external service to create the jwt for me and the refresh token logic, I'm sending the access_token to the client using zustand for better usability (I dont want to send a request to /api/session for taking the access_token everytime I make a fetch in the client).

This is my client.tsx (fetch wrapper)
import { useAuthenticationStore } from '@/store/authentication.store';

export const client = async (endpoint: string, init?: RequestInit) => {
  const access_token = useAuthenticationStore.getState().access_token;

  const fetchRequest = await fetch(endpoint, {
    ...init,
    headers: {
      ...init?.headers,
      Authorization: `Bearer ${access_token}`,
    },
    cache: 'no-cache',
  });

  return fetchRequest.json();
};


My AuthProvider which wraps the app in RootLayout

'use client';

import { useAuthenticationStore } from '@/store/authentication.store';
import { PropsWithChildren } from 'react';

export default function AuthProvider({
  access_token,
  children,
}: PropsWithChildren<{ access_token: string }>) {
  if (access_token) useAuthenticationStore.setState({ access_token });

  return children;
}


Thing is, how do I make this access_token which is in client to be in sync with the one saved in the server on token rotation (token refresh)

Here is my auth.ts file attached.

28 Replies

KoratOP
Yeah, here it is

/**
 *
 * @param refreshToken
 * @returns
 */
export const refreshTokenAction = async (
  refreshToken: string
): Promise<LoginResponseDto> => {
  const response = await fetch(
    'https://server/api/refresh-token',
    {
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ refreshToken }),
      method: 'POST',
    }
  );

  return await response.json();
};
I've been working on this for too much now, I would appreciate the most your help
Also this is marked as server fyi
@Korat Yeah, here it is /** * * @param refreshToken * @returns */ export const refreshTokenAction = async ( refreshToken: string ): Promise<LoginResponseDto> => { const response = await fetch( 'https://server/api/refresh-token', { headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ refreshToken }), method: 'POST', } ); return await response.json(); };
I would set access_token to cookies then do this in refreshTokenAction
/**
 *
 * @param refreshToken
 * @returns
 */
export const refreshTokenAction = async (): Promise<LoginResponseDto> => {
  const refreshToken = cookies().get('refreshToken')?.value || ''
  const response = await fetch(
    'https://server/api/refresh-token',
    {
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ refreshToken }),
      method: 'POST',
    }
  );

  return await response.json();
};
KoratOP
I need a way to rerender AuthProvider so it gets the new access token from the session, maybe use an useEffect and check for session changes, but that didn't work
'use client';

import { useEffect } from 'react';
import { useSession } from 'next-auth/react';
import { useAuthenticationStore } from '@/store/authentication.store';

export default function AuthProvider() {
  const { data: session, status } = useSession();

  useEffect(() => {
    if (status === 'authenticated' && session) {
      useAuthenticationStore.setState({
        access_token: session.user?.access_token,
      });
    }
  }, [session, status]);

  return null;
}
This is how my rootlayout looks like

export default async function RootLayout({
  children,
}: Readonly<{
  children: React.ReactNode;
}>) {
  const session = await auth();

  return (
    <html lang='en'>
      <body className={inter.className}>
        <SessionProvider session={session}>
          <AuthProvider />
          <QueryClientProvider>
            <header className='space-x-2'>
              <Link href='/users'>Users</Link>
              <Link href='/notifications'>Notification</Link>
            </header>

            {children}
          </QueryClientProvider>
        </SessionProvider>
      </body>
    </html>
  );
}
KoratOP
Interesting thought, lemme try it,
But this only works if the rootlayout rerenders right
@Korat Interesting thought, lemme try it, But this only works if the rootlayout rerenders right
yeah, or move AuthProvider to app/template.tsx
KoratOP
I dunno why but there is no expires inside session
am i overriding it the wrong way
from session callback
 async session(params: any) {
      console.log('SESSION CALLBACK', params.token);

      if (!('token' in params)) return params.session;

      return {
        id: params.token?.id,
        email: params.token?.email,
        access_token: params.token?.access_token,
      };
    },
@Korat am i overriding it the wrong way
yes
callbacks: {
  async session({ session, token, user }) {
    // Send properties to the client, like an access_token and user id from a provider.
    session.accessToken = token.accessToken
    session.user.id = token.id
    
    return session
  }
}
KoratOP
Daym, i think the key worked (but I would love to know why, since root layout doesn't rerender or does it)
I've returned it as object and not replaced the values
@Ray I think this is working only on tab change and not when navigating throught pages
KoratOP
When I defocus and focus the app, the AuthProvider rerenders and takes the new access_token from session
look like react-query?
use app/template.ts for <AuthProvider />
template will re-render on page change
KoratOP
import AuthProvider from '@/providers/auth.provider';

export default function Template({ children }: { children: React.ReactNode }) {
  return (
    <>
      <AuthProvider />
      {children}
    </>
  );
}
I guess nothing changed, i still see the different access_token in client,

When changing chrome tabs, it does recieve the new access_token and if reloading the browser too, but not while in the app (navigating to different pages)