Introduction
Session management is responsible for storing and maintaining user-specific data across multiple requests or interactions with a web application. In web apps, a "session" refers to the period during which a user is actively interacting with the app, starting when a user logs in and ending when they log out or after a certain period of inactivity.
Types of Session Management
Cookies: Small pieces of data stored on the user's browser.
Local Storage/Session Storage: Web storage methods to store data in the user's browser.
JWT (JSON Web Tokens): A stateless way to handle sessions with encoded tokens.
Server-side session stores: Persistent session storage on the server that tracks user sessions.
Session Management with JavaScript and React
Cookies
Cookies are stored in the user's browser and can be set to expire after a specific time. They are commonly used for session management because they can be set with server responses and sent automatically with every request to the server.
// Setting a cookie
document.cookie = "sessionId=9808@#45; expires=Fri, 31 Dec 2024 23:59:59 GMT; path=/login";
// Reading a cookie
const cookies = (cookieValue) => {
return cookieValue.split('; ').reduce((acc, cookie) => {
const [key, value] = cookie.split('=');
acc[key] = value;
return acc;
}, {});
}
const cookieObj = cookies(cookie)
console.log(cookieObj.sessionId) // 9808@#45
console.log(cookieObj.expires) // Fri, 31 Dec 2024 23:59:59 GMT
console.log(cookieObj.path) // /login
- Resources - JS Cookie(third party package) - https://www.npmjs.com/package/js-cookie
Local and Session Storage
localStorage persists data across browser sessions.
sessionStorage only persists data for the duration of the page session (until the tab or window is closed).
// React Example
import React from "react";
import { useState } from "react";
import { useEffect } from "react";
const Hero = () => {
// Accessing theme from localStorage for initial state
const [theme, setTheme] = useState(
localStorage.getItem("theme") === "dark" ? "dark" : "light"
);
useEffect(() => {
// Updating local storage if theme state is changed
localStorage.setItem("theme", theme);
}, [theme]);
// Toggling theme between dark and light
const toggleTheme = () => {
setTheme(theme === "dark" ? "light" : "dark");
};
return (
<section className={`${theme === "dark" ? "bg-gray-900" : ""} text-white`}>
<div className="mx-auto px-4 py-32 lg:flex lg:h-screen lg:items-center lg:justify-between max-w-[1200px]">
<h1>Theme Change Example</h1>
<button
className="fixed top-4 right-4 rounded bg-blue-600 p-2 text-white hover:bg-transparent hover:text-white focus:outline-none focus:ring"
onClick={toggleTheme}
>
{theme === "dark" ? " " : " "}
</button>
</div>
</section>
);
};
export default Hero;
JWT Authentication
JWT authentication is a token-based stateless authentication mechanism.
// Manually setting tokens for login
const login = async (username, password) => {
const response = await fetch('https://example.com/api/login', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ username, password }),
});
const data = await response.json();
localStorage.setItem('token', data.token);
};
// Checking tokens with Authorization headers
const getProtectedData = async () => {
const token = localStorage.getItem('token');
const response = await fetch('https://example.com/api/protected', {
method: 'GET',
headers: {
'Authorization': `Bearer ${token}`,
},
});
const protectedData = await response.json();
return protectedData
};
On login, the server generates a JWT and sends it to the client.
The client stores the JWT in localStorage or sessionStorage.
On each request to a protected resource, the client sends the JWT in the headers for authentication.
Resource - https://www.npmjs.com/package/react-jwt
Session Management in Next.js
Session management in Next.js can be more robust due to the combination of client and server contexts. Common ways to manage sessions in Next.js include using cookies, JWTs, and third-party authentication libraries. We have already saw Cookies and JWT approach and will implement third party authentication using next-auth, which is a popular authentication library for Next.js that simplifies session management.
Session storage: Sessions can be stored in JWTs (for stateless authentication) or server-side databases.
Automatic session handling: Next-auth manages session expiration, renewal, and session data automatically.
Installation
npm install next-auth
Adding Github auth provider
import NextAuth from "next-auth"
import GithubProvider from "next-auth/providers/github"
export const authOptions = {
// Configure one or more authentication providers
providers: [
GithubProvider({
clientId: process.env.GITHUB_ID,
clientSecret: process.env.GITHUB_SECRET,
}),
],
}
const handler = NextAuth(authOptions)
export { handler as GET, handler as POST }
Creating a Session provider for the entire app
"use client";
import { SessionProvider } from "next-auth/react"
import React from "react";
const SessionWrapper = ({children}:{children:React.ReactNode}) => {
return <SessionProvider>{children}</SessionProvider>
}
export default SessionWrapper
Wrapping the Layout with SessionWrapper
import "./globals.css";
import Navbar from "@/components/Navbar";
import Footer from "@/components/Footer";
import SessionWrapper from "@/components/SessionWrapper";
export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
return (
<html lang="en">
<body>
<SessionWrapper>
<Navbar />
<main>{children}</main>
<Footer />
</SessionWrapper>
</body>
</html>
);
}
Accessing the Session data and status
import { useSession } from "next-auth/react"
export default function Component() {
const { data: session, status } = useSession()
if (status === "authenticated") {
return <p>Signed in as {session.user.email}</p>
}
return <a href="/api/auth/signin">Sign in</a>
}
data: This can be three values: Session / undefined / null.
When the session hasn't been fetched yet, data will be undefined
In case it failed to retrieve the session, data will be null
In case of success, data will be Session.
status: enum mapping to three possible session states: "loading" | "authenticated" | "unauthenticated"
State Hydration
Hydration is the process where the server-rendered HTML is "reused" by React on the client, and React attaches its JavaScript logic (e.g., event listeners, state management) to the existing HTML.
In Next.js, state hydration is essential because when a page is server-rendered, it’s pre-rendered with HTML and some initial state, but once the browser takes over, the client-side React app must take that server-generated HTML and "hydrate" it by attaching event listeners, reinitializing state, and ensuring that the client-side app behaves correctly with the pre-rendered HTML.
Hydration could also occur if some process is dependent on an asynchronous state.
Here is an example of it
"use client";
import ProfileCard from "./ProfileCard";
import BlogsFetch from "./BlogsFetch";
import { useMarkdownStore } from "@/store/useStore";
const ProfileSection = () => {
const user = useMarkdownStore((state) => state.user);
return (
<div className="flex flex-col lg:flex-row justify-center lg:justify-start gap-5 lg:gap-10 px-5">
<ProfileCard user={user} />
<BlogsFetch userId={user.uid as string} />
</div>
);
};
export default ProfileSection;
The user variable refers to a user state from zustand, which is a state management library. This state is async and is used by the BlogsFetch component which use this user.uid to perform data fetching but initially when the component mounts, user returns null and the data fetching returns an emptry array as the user state is not hydrated immediately.
To fix this issue, we need to wait for the user state to get hydrated with some value and then perform the data fetching
const queryInfinite = useInfiniteQuery({
queryKey: userId ? ["blogs", userId] : ["blogs"],
queryFn: () =>
userId
? getUserBlogsFromDb(userId, lastDoc)
: getBlogsFromDb(filters, lastDoc),
initialPageParam: undefined,
getPreviousPageParam: (firstPage) => firstPage.firstVisibleDoc,
getNextPageParam: (lastPage) => lastPage.lastVisibleDoc,
enabled: userId ? true : false,
});
- We are using react query to handle the data fetching and mutations, it has 1 property called enabled which fetch the data once the userId state is ready and hydrated. Alternatively, we could use useEffect hook to refetch once the userId is ready to perform the data fetching operation.
Resources
https://next-auth.js.org/getting-started/introduction
https://tanstack.com/query/latest
https://github.com/pmndrs/zustand
That's it for this post
You can contact me on -
Instagram - https://www.instagram.com/supremacism__shubh/
LinkedIn - https://www.linkedin.com/in/shubham-tiwari-b7544b193/
Email - shubhmtiwri00@gmail.com
You can help me with some donation at the link below Thank you👇👇
https://www.buymeacoffee.com/waaduheck
Also check these posts as well
Top comments (0)