Next.js Discord

Discord Forum

How can I manage sessions with Next.js? How can I persist user information?

Unanswered
Netherland Dwarf posted this in #help-forum
Open in Discord
Netherland DwarfOP
I am using the rest api that I created with Laravel.

1- I send a request to the Laravel /sign-in endpoint and it returns me the jwt token and user information.
2- I get the JWT token and save it to the cookie in next.js
3- I send a request to the /profile endpoint in Laravel. When making the request, I send the token on the cookie as a bearer token and it returns the information of the logged in user.
4- How can I permanently store this returning user's information in next.js?

I store user information on the state using context, etc., but I need to constantly send requests to the /profile endpoint. What is the most logical and standard way to do this?

17 Replies

Milkfish
U can either temporarily cache the data, or, use an auth library like next auth.
If you use next auth, u can fetch the data once during or after signin, store it in the session object, and use it anywhere on ur app.
This would also remove the need for context.

Also, just a personal take, I don't manually store jwts in cookie. I also put it in next auths session object and let it worry about storing it in cookie.
Doesn't have to be next auth tho. It's just what I prefer. I heard better auth is cool too.
Netherland DwarfOP
'use client';

import { createContext, ReactNode, useContext } from 'react';

export type User = {
    id: number;
    name: string;
    surname: string;
    username?: string;
    email: string;
    avatar?: string | null;
    banner?: string | null;
    bio?: string | null;
    location?: string | null;
    created_at: string;
    updated_at: string;
};

type Session = {
    status: boolean;
    data: User | null;
};

type AuthContextProps = {
    user: User | null;
    isAuthenticated: boolean;
};

const AuthContext = createContext<AuthContextProps | undefined>(undefined);

const AuthProvider = ({
    children,
    currentSession,
}: {
    children: ReactNode;
    currentSession: Session;
}) => {
    const user = currentSession.data;
    const isAuthenticated = currentSession.status;
    return (
        <AuthContext.Provider value={{ user, isAuthenticated }}>
            {children}
        </AuthContext.Provider>
    );
};

const useAuth = () => {
    const context = useContext(AuthContext);

    if (!context) {
        throw new Error(
            'useAuth hookunu AuthProvider içerisinde kullanmadınız!'
        );
    }

    return context;
};

export { AuthProvider, useAuth };


const session = await getSession();

    return (
        <html lang="en" className={inter.className}>
            <body className="antialiased">
                <ThemeProvider>
                    <QueryProvider>
                        <AuthProvider currentSession={session}>
                            {children}
                            <Toaster />
                        </AuthProvider>
                    </QueryProvider>
                </ThemeProvider>
            </body>
        </html>
    );
'use server';

import { cookies } from 'next/headers';

export async function getSession() {
    const cookieStore = await cookies();
    const API_URL = process.env.NEXT_PUBLIC_API_URL;
    const TOKEN = cookieStore.get('token')?.value;

    const response = await fetch(`${API_URL}/profile`, {
        method: 'GET',
        headers: {
            Accept: 'application/json',
            Authorization: `Bearer ${TOKEN}`,
        },
    });

    return await response.json();
}
{
"status": true,
"data": {
"id": 1,
"name": "dd",
"surname": "dd",
"username": "ddd",
"bio": "dd",
"avatar": null,
"banner": null,
"location": null,
"email": "dd@gmail.com",
"created_at": "2025-09-07T18:11:43.000000Z",
"updated_at": "2025-09-07T18:11:43.000000Z"
}
}


If there is a session, a response like this is received
{
"status": false,
"message": "Giriş yapmanız gerekiyor.",
"data": null
}
This is how I control the session right now
Milkfish
Aight. Let's start with the getSession function.

1. You're not checking whether the cookie exists before making a request with it. You're just getting the value (which could be undefined) and putting it in your request. Also, you're not checking if the response object is valid. Make sure everything returns the data you're expecting so you can properly return an error message or debug when something goes wrong.


2. It looks like your layout file is a server component.
If it is, then there's no need to put "use server" at the top of the getSession file since it's going to run on the server anyway. You can leave it there if you want to call it on the client, but it's not recommended to use server actions for GET requests.



Your auth provider and hook are okay-ish. You're basically making a mini NextAuth. I have nothing against rolling your own auth and handling authorization yourself, but this looks like something that would break if things get more advanced or when you want to implement some complex authorization.

The isAuthenticated variable is also redundant. Right now, you're returning the user object and the status. But if the user object is valid, the user is authenticated; if it's not, then the user is not. I recommend adding an error object:

type Session =
| { status: true; error: { message: string } }
| { status: true; data: User };

So now you can use this to check if the status is true before getting the data and passing it down.

Right now, if currentSession is undefined, things break because your code always assumes it's a session object. Try adding a fallback.
My advice, just use an auth library vro. U could stick with this if the project is basic tho.

Also, how are you securing the cookie? Are you just using cookie.set()?
Netherland DwarfOP
import { cookies } from 'next/headers';

export async function getSession() {
    const cookieStore = await cookies();
    const accessToken = cookieStore.get('access_token')?.value;

    if (!accessToken) return null;

    const API_URL = process.env.NEXT_PUBLIC_API_URL;
    const res = await fetch(`${API_URL}/profile`, {
        method: 'GET',
        headers: {
            accept: 'application/json',
            authorization: `Bearer ${accessToken}`,
        },
    });

    const data = await res.json();

    if (!data?.status) return null;

    return data.data;
}

export async function createSession(accessToken: string) {
    const expiresAt = new Date(Date.now() + 7 * 24 * 60 * 60 * 1000);
    const cookieStore = await cookies();

    cookieStore.set('access_token', accessToken, {
        path: '/',
        httpOnly: true,
        expires: expiresAt,
    });
}


'use client';

import { User } from '@/types/user';
import React, { createContext, useContext, useState } from 'react';

type SessionContextProps = {
    session: User | null;
};

const SessionContext = createContext<SessionContextProps | null>(null);

const useSession = () => {
    const context = useContext(SessionContext);
    if (!context) {
        throw new Error('useSession must be used within a <SessionProvider>');
    }

    return context;
};

const SessionProvider = ({
    children,
    currentSession,
}: {
    children: React.ReactNode;
    currentSession: User | null;
}) => {
    const [session, setSession] = useState<User | null>(currentSession);

    return (
        <SessionContext.Provider value={{ session }}>
            {children}
        </SessionContext.Provider>
    );
};

export { useSession, SessionProvider };
const session = await getSession();
    return (
        <html lang="en" className={inter.className}>
            <body className="antialiased">
                <ThemeProvider>
                    <QueryProvider>
                        <SessionProvider currentSession={session}>
                            <Toaster />
                            {children}
                        </SessionProvider>
                    </QueryProvider>
                </ThemeProvider>
            </body>
        </html>
    );
@Milkfish Aight. Let's start with the getSession function. 1. You're not checking whether the cookie exists before making a request with it. You're just getting the value (which could be undefined) and putting it in your request. Also, you're not checking if the response object is valid. Make sure everything returns the data you're expecting so you can properly return an error message or debug when something goes wrong. 2. It looks like your layout file is a server component. If it is, then there's no need to put "use server" at the top of the getSession file since it's going to run on the server anyway. You can leave it there if you want to call it on the client, but it's not recommended to use server actions for GET requests. Your auth provider and hook are okay-ish. You're basically making a mini NextAuth. I have nothing against rolling your own auth and handling authorization yourself, but this looks like something that would break if things get more advanced or when you want to implement some complex authorization. The isAuthenticated variable is also redundant. Right now, you're returning the user object and the status. But if the user object is valid, the user is authenticated; if it's not, then the user is not. I recommend adding an error object: type Session = | { status: true; error: { message: string } } | { status: true; data: User }; So now you can use this to check if the status is true before getting the data and passing it down. Right now, if currentSession is undefined, things break because your code always assumes it's a session object. Try adding a fallback. My advice, just use an auth library vro. U could stick with this if the project is basic tho. Also, how are you securing the cookie? Are you just using cookie.set()?
Netherland DwarfOP
Hello, I wrote a code like this based on what you said.
session simply returns null or a user object.
For now, the application has not exploded and I can manage the session process this way.
import { getSession } from '@/lib/session';

export default async function Home() {
    const session = await getSession();

    if (!session) {
        return <div>not logged in</div>;
    }

    return <div>{session.name}</div>;
}
'use client';

import { useSession } from '@/context/session-context';

export default function Home() {
    const { session } = useSession();

    if (!session) {
        return <div>not logged in</div>;
    }

    return <div>{session.name}</div>;
}
in both client and server components.