惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Scott Helme
Scott Helme
N
Netflix TechBlog - Medium
AI
AI
Security Latest
Security Latest
GbyAI
GbyAI
P
Proofpoint News Feed
Y
Y Combinator Blog
A
Arctic Wolf
G
Google Developers Blog
U
Unit 42
爱范儿
爱范儿
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
V
Vulnerabilities – Threatpost
Know Your Adversary
Know Your Adversary
Cisco Talos Blog
Cisco Talos Blog
T
Tor Project blog
C
CXSECURITY Database RSS Feed - CXSecurity.com
T
Threatpost
L
Lohrmann on Cybersecurity
C
CERT Recently Published Vulnerability Notes
C
Check Point Blog
B
Blog RSS Feed
The GitHub Blog
The GitHub Blog
Microsoft Azure Blog
Microsoft Azure Blog
博客园 - 【当耐特】
博客园 - Franky
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
C
Cisco Blogs
云风的 BLOG
云风的 BLOG
NISL@THU
NISL@THU
D
Darknet – Hacking Tools, Hacker News & Cyber Security
Microsoft Security Blog
Microsoft Security Blog
T
The Blog of Author Tim Ferriss
阮一峰的网络日志
阮一峰的网络日志
Latest news
Latest news
L
LINUX DO - 最新话题
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
美团技术团队
WordPress大学
WordPress大学
L
LangChain Blog
Stack Overflow Blog
Stack Overflow Blog
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
酷 壳 – CoolShell
酷 壳 – CoolShell
大猫的无限游戏
大猫的无限游戏
The Hacker News
The Hacker News
Simon Willison's Weblog
Simon Willison's Weblog
V
V2EX
Project Zero
Project Zero
博客园_首页

Clerk Blog

Going to production with Clerk Deploy Clerk Init: The fastest way to start a new project Introducing Clerk CLI Middleware-based route protection bypass Postmortem: Clerk System Outage (March 10, 2026) Clerk for the AI era Add API Key support to your SaaS in minutes Postmortem: Clerk System Outage (February 19, 2026) Using Clerk in a React Native app Postmortem: DNS Provider Outage (February 10, 2026) How do I implement passkeys in Next.js? Clerk ranked #4 fastest-growing software vendor on Ramp’s December 2025 list How do I handle JWT verification in Next.js? Committing to Agent Identity: Clerk raises $50m Series C from Menlo and Anthropic’s Anthology Fund What is the best way to handle authentication in Next.js App Router? Postmortem: Database Incident (September 14–18, 2025) How do I add authentication to a Next.js app? Introducing Free Trials in Clerk Billing Postmortem: August 28, 2025 - elevated API latency and errors Introducing Mosaic: Bring Your Brand to Every Authentication Flow Multi-tenant authentication: What you need to know (and how Clerk helps) What are the risks and challenges of multi-tenancy? Resilience in Practice: Regional Failover at Clerk Build a Cross-Platform B2B App with Clerk, Expo, and Supabase Highlights from the MiduDev/Clerk Hackathon Add multi-tenancy to an app built with Clerk, Lovable, and Supabase How to build an AI coding rules app with Clerk, Lovable, and Supabase How to Build Multi-Tenant Authentication with Clerk Choosing the right SaaS architecture: Multi-Tenant vs. Single-Tenant Postmortem: June 26, 2025 service outage How to Design a Multi-Tenant SaaS Architecture What is multi-tenancy and why it matters for B2B SaaS How OAuth Works Synchronize user data from Clerk to Supabase Add subscriptions to your SaaS with Clerk Billing Getting started with Clerk Billing Multi-tenant analytics with Tinybird and Clerk How Huntr Migrated 250K Users to Clerk: A Scalable Auth Solution for Startups How to take Clerk to Production How to take your Clerk application to production A practical guide to testing Clerk Next.js applications Implementing multi-tenancy into a Supabase app with Clerk How Clerk integrates with a Next.js application using Supabase How Clerk integrates with Supabase Build a blog with tRPC, Prisma, Next.js and Clerk How to enrich PostHog events with Clerk user data How to build a secure project management platform with Next.js, Clerk, and Neon Validate your SaaS idea while building an audience Postmortem: February 6, 2025 service outage Implement Role-Based Access Control in Next.js 15 Build a Next.js sign-up form with React Hook Form Build a Next.js login page template How to implement Google authentication in Next.js 15 What is middleware in Next.js? How to customize Next.js metadata How to set environment variables in Node.js Building a React Login Page Template How to implement per-user OAuth scopes with Clerk Using Clerk SSO to access Google Calendar and other service data Streamline enterprise customer onboarding with SAML and Clerk Clerk launches EASIE SSO and eliminates SSO fees How to secure Liveblocks Rooms with Clerk in Next.js Securing Node.js Express APIs with Clerk and React Combining the benefits of session tokens and JWTs Build a task manager with Next.js, Supabase, and Clerk Comparing Clerk Webhooks vs Backend API Automate Neon schema changes with Drizzle and GitHub Actions A guide to reading authenticated user data from Clerk Role based access control with Clerk Organizations Mitigating OAuth’s recently discovered Open Response Type vulnerability Per-user B2B monetization with Stripe and Clerk Organizations Build a team-based task manager with Next.js, Neon, and Clerk Building a Hybrid Sign-Up/Subscribe Form with Stripe Elements Welcoming Colin from Zod as our inaugural Open Source Fellow Build a modern authenticated chat application with Next.js, Ably, and Clerk Build a waitlist with Clerk user metadata How to use Clerk with PostHog Identify in Next.js How to secure API Gateway using JWT and Lambda Authorizers with Clerk What are passkeys and how do they work? Comparing Authentication in React.js vs. Next.js How to Add an Onboarding Flow for your Application with Clerk Create Your Own Custom User Menu with Radix - Part 2 Introducing Webhook Workflows with Inngest & Svix Clerk raises $30M Series B from CRV and Stripe Clerk in 2023: A Year in Review Build a Movie Emoji Quiz App with Remix, Fauna, and Clerk Ultimate Guide to Magic Link Authentication Create Your Own Custom User Menu with Radix Introducing has(), protect(), and <Protect> Updated Pricing: 10,000 MAUs Free, and a new “Pro Plan” Next.js Authentication with Clerk: Streamlined SSR Handling Clerk Webhooks: Data Sync with Convex Exploring Clerk Metadata with Stripe Webhooks The Ultimate Guide to Next.js Authentication Empower Your Support Team With User Impersonation Clerk Webhooks: Getting Started The Advanced Guide to Passwordless Authentication in Next.js How We Roll – Chapter 10: Roundup How We Roll – Chapter 9: Infrastructure Password-Based Authentication in Next.js
A Complete Guide to Session Management in Next.js
Nick Parsons · 2023-09-28 · via Clerk Blog

Session management is a concept that flies under the radar in most applications. It’s built into every authentication library you are using, and seamlessly allows users to stay logged in, use different tabs, and stay secure while they are using your app.

But because it is abstracted away by auth systems, it’s also opaque. How does session management work to keep track of your usage?

Here, we want to build session management in Next.js without using any authentication library to show you what is really happening under the hood.

What is a session?

This might seem like a trivial question, but it doesn’t have a trivial answer. One of the reasons that session management is often abstracted away from developers is that it’s a difficult concept to grok and implement.

A session is a series of interactions between a user and an application that occur within a given time frame.

A session is initiated when a user logs in or starts interacting with the application and is typically terminated when the user logs out or after a period of inactivity. It is a way to preserve certain data/state across multiple requests between the client and the server in an inherently stateless environment, which is the HTTP protocol.

These are the main components of a session:

  1. Session Identifier (Session ID): A unique string that distinguishes one session from another. It is sent by the server to the client after successful login and is usually stored in a cookie on the client side.
  2. Session Store: A storage mechanism, often on the server side, where session data, such as user details and preferences, are stored. This could be in-memory storage, a database, a file system, or a distributed cache, depending on the application's requirements.
  3. Session Data: Information stored in the session, often including user preferences, user identification and authentication data, and temporary application state. This data is used to personalize the user experience and to maintain the state of the application between different requests from the client.
  4. Session Timeout: A mechanism to terminate sessions after a predefined period of inactivity to minimize the risk of unauthorized access. Once a session is timed out, the user needs to re-authenticate to continue interacting with the application.
  5. Session Cookie: A type of HTTP cookie sent from the server to the client’s browser to store the session ID.

So a session lifecycle starts with creation, when a session is created from a user login or starts interacting with an application. This is when a unique session ID is generated and associated with the user. The session is maintained through the session ID that is transmitted with each subsequent request from the client and is used to retrieve and manage session data on the server. The session ends when the user logs out or after a period of inactivity (session timeout). Any data stored in the session is usually deleted or invalidated.

Why session management is important

Session management is pivotal for the seamless functioning and robust security of web applications. When it's not implemented effectively, applications become vulnerable to a host of security issues and often provide an experience that's frustrating for users. Thus, grasping and applying solid session management strategies are absolutely critical if one wants to build web applications that are secure, scalable, and user-friendly.

Importance for Security

Session management acts as the security guard of web applications. It works to correctly identify and authenticate users, ensuring they only access what they’re allowed to. It’s there to protect sensitive information belonging to the users by allowing only authenticated and authorized individuals to access it. It also safeguards session identifiers as they are transmitted and stored. It shields applications from security threats, such as session hijacking, session fixation, and Cross-Site Request Forgery (CSRF).

Enhancement of User Experience

From a user experience standpoint, sessions empower applications to remember user preferences and deliver personalized content, crafting an experience that is more engaging for the user. Efficient session management allows users to move between different devices and browser tabs when interacting with applications without needing to keep logging in. This ease of access enhances overall user convenience and experience. Sessions are like invisible assistants, remembering temporary data between user requests, meaning users can roam freely within an application without the fear of losing their progress or context.

Beyond security and user experience, sessions are a tool for collecting valuable data regarding user behavior and preferences. This kind of information is a goldmine for businesses, helping them make well-informed decisions and refine their strategies based on user interaction and needs. In essence, it’s like having a pulse on user behavior, enabling the refinement of business strategies and decision-making.

Setting up session management in Next.js

We’re going to produce a simple two page site that allows us access to a protected page if we are logged in. Fundamentally, this is an authentication setup, but we are going to set it up using JSON Web Tokens (JWT) that we’ll store on the client. This will give the user a live "session," so once they have logged in, they can continue to access the protected page, until the token and session expires.

We’re going to use Next.js 13 and the App Router. Let’s first create a new Next project:

To follow this tutorial you should use the defaults from the prompts. We’ll then open up the code in our IDE. We’re using VS Code, so we can:

We also want to install the libraries we’re going to use. None of these are authentication libraries. Instead they are libraries that let us directly access cookies and databases, and implement JWTs.

  • js-cookie is a lightweight JavaScript library that provides a straightforward API to handle browser cookies, allowing you to create, read, and delete cookies in a way that works with various JavaScript environments, like the browser and Node.js.
  • sqlite is a library that serves as a lightweight, file-based database engine, allowing developers to utilize SQL-based database functionality without the need for a full-fledged database management system.
  • sqlite3 is a Node.js library that provides bindings to SQLite3, enabling interaction with SQLite databases, allowing developers to perform operations like querying, updating, and deleting records in SQLite databases from within Node.js applications.
  • jsonwebtoken is a Node.js library that allows you to securely handle JSON Web Tokens (JWTs), which are compact, URL-safe means of representing claims to be transferred between two parties, commonly used for authentication and information exchange in web development.

Now, we’re ready to code.

The login page

First, let’s remove the boilerplate from the app/page.js file and replace it with this code:

Login Code

This is going to be our login page. The UX is simple–just a form with a username field, a password field, and a submit button.

Complete Guide Session Management Nextjs guide illustration

When the button is clicked, the handleLogin function is called.

The handleLogin function is an asynchronous event handler that deals with the logic of the login attempt. There are five main components to this function:

  1. const response = await fetch("/api/login", {...}). This sends a POST HTTP request to the /api/login endpoint with the username and password as the body of the request.
  2. if (!response.ok) throw new Error("Login failed"). This checks if the response received from the server is not ok (i.e., the HTTP status code is not in the range 200-299). If it's not ok, it throws an error with the message "Login failed".
  3. const { token } = await response.json(). If the response is ok, this parses the JSON body of the response and extracts the token property from it. This is the token that authenticates the user for subsequent requests.
  4. document.cookie = token=${token}; path=/. This sets a cookie in the user's browser with the name token and the value received from the login API, which is accessible to any path in the domain.
  5. router.push("/protected"). This navigates the user to the /protected route of the app.

So this is calling the login API, and if it receives a token back, sets that token in the browser and passes the user to the protected page. If the login fails and no token comes back, it just tells the user “Login failed.”

The login API

Let’s take a look at the login API next:

Let’s go through the POST function first. This is the part of the code that receives the POST call from the login page and authenticates our users.

  • const body = await req.json() reads the JSON body from the incoming request object (req). It is awaited because reading the body is an asynchronous operation.
  • const { username, password } = body. After the body is read, the username and password are destructured from it. These would be the username and password sent in the request, likely provided by the user through a form in the frontend.
  • const user = await authenticateUser(username, password) calls the authenticateUser function to authenticate users against the user database.
  • const token = jwt.sign({ userId: user.id }, process.env.JWT_SECRET, { expiresIn: "1m" }). If the user is successfully authenticated, a JWT is generated using the jwt.sign method. This token includes the user’s ID (user.id) as part of its payload. The token is signed using a secret key stored in process.env.JWT_SECRET, and it’s set to expire in 1 minute (expiresIn: "1m").
  • return NextResponse.json({ token }). Finally, if everything is successful, the function returns a response with the generated token in JSON format.

The core parts here are the authenticateUser call and the JWT signing. We’ll look closer at the authenticateUser call in a moment, but let’s discuss JWTs first as they are integral to session management.

JSON Web Tokens

JSON Web Tokens (JWTs) are a compact, URL-safe means of representing claims to be transferred between two parties. The claims in a JWT are encoded as a JSON object that is used as the payload of the token, which is then signed to secure the information.

JWTs are commonly used for authentication and information exchange in web development. When a user logs in, the server generates a JWT that encodes user information (like user ID) and sends this token to the client. The client then includes this token in the Authorization header in subsequent requests, allowing the server to identify and authorize the user.

JWTs consist of three parts separated by dots (.):

  1. Header: The header typically consists of two parts: the type of the token, which is JWT, and the signing algorithm being used, such as HMAC SHA256 or RSA.
  2. Payload: The payload contains the claims. Claims are statements about a user and additional metadata, such as expiry times, which are critical in session management.
  3. Signature: The signature is used to verify the message wasn't changed along the way and, in the case of tokens signed with a private key, it can also verify the sender of the JWT.

A JWT might look like this:

  • The first part is the Header, Base64Url encoded.
  • The second part is the Payload, also Base64Url encoded.
  • The third part is the Signature.

So here, the payload is our user ID, and we’re signing the token with our JWT_SECRET in our .env.local. This secret should be a long, random string. You can generate a random JWT secret like this:

Authenticating the user

The authenticateUser function is an asynchronous function intended to authenticate a user based on a provided username and password against a user record in an SQLite database.

Session management is really user management. So, you need to have a database set up of all your users so you can get, in this case, their IDs to add as the JWT payload. Here, we’ve set up a small SQLite database locally with a single user. To do this properly, you’ll need a full database for all your users, plus a way to add those users.

In this function, we initialize a connection to an SQLite database file named userdatabase.db using the sqlite open method. Once the database connection is established, we create an SQL query string, sql, to select a user from the users table where the username and password match the provided arguments.

We then execute this SQL query using await db.get(sql, username, password), which will return the first row that satisfies the conditions (i.e., where the username and password match the input), or return undefined if no such row exists.

We then return that user to the POST function, which uses the ID within the JWT payload, and returns that to the client.

Validating the token

After we’ve got the user and added the token to the user’s browser cookies, we are routed to the /protected page.

Not too much is happening on this page, apart from checking the token exists on the local client and then sending it as part of the authorization header as a bearer token to the protected API endpoint. That endpoint is where we do two things:

  1. Check that the token hasn’t expired
  2. Check that the token is valid

The first part is to extract the authorization header from the request, and then extract the token by splitting it from Bearer.

Then we use the verify method on the JWT with the secret we used to sign it originally. This will allow us to show it is an authenticated token. If the token is invalid, jwt.verify throws an error. From there, we want to check whether the token has expired by comparing the exp field in the decoded token with the current timestamp.

If the token is valid and not expired, the function returns a JSON response (with some data if you wanted). If the token is invalid or expired, or if any error occurs during verification, it returns a JSON response with a 400 status code and a message "Unauthorized".

If all is good, the user will be directed to the protected page:

Complete Guide Session Management Nextjs guide illustration

Because the token persists within the browser, they can also go to the protected page in another tab:

Complete Guide Session Management Nextjs guide illustration

But the token expires after only a minute. If they try and reload after that time, they are redirected to the login page again:

Complete Guide Session Management Nextjs guide illustration

And you have successfully managed a session!

The problems with this approach

This is the most basic code to get session management working. But even here we’ve had to manage our own user database, manage our own secrets, and manage all of the logic in between. It isn’t a robust system:

  • We’re missing the substantial error handling that is needed for this to work properly. For instance, we want to handle the case where the Authorization header is missing or malformed, to avoid issues like calling split on undefined, which would throw an error.
  • We’re missing any of the checks in the user management system for encrypting passwords.
  • We’re not managing our database calls well or persisting a connection.

If you are building your own session management system, you are also building your own user management system, and becoming a database administrator.

Session management in Next.js with Clerk

The easier way is to use a specifically designed authentication library. Here, we’re going to use Clerk, but any auth library is going to take this headache away from you.

First, we’ll set up the project in exactly the same way:

Again, use the defaults from the prompts. Then open the code

We don’t need to install all the libraries from before. All we need this time is the Clerk SDK:

We’ll start with adding the environment variables we need for Clerk–our NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY and CLERK_SECRET_KEY–into our .env.local file:

After that, we need to add the <ClerkProvider /> wrapper to the app. This is the critical component for session management. It is what we provide the active session and user information to all of Clerk’s components anywhere in the app. We add it in Layout.js, wrapping the entire body of our application:

We’ll then add some middleware. This is what decides which pages are protected and which aren’t. The default code below protects every page on the site:

We then need three pages. First, a sign up page, which will go at app/sign-up/[[...sign-up]]/page:

Then a sign in page at app/sign-in/[[...sign-in]]/page:

Finally, we’ll add a button to interact with these pages on our home page:

That’s a lot less code. We need to add paths to these in our .env.local as well:

And that’s it. Run npm run dev and you’ll get a way to sign in:

Complete Guide Session Management Nextjs guide illustration

Sign in, and you’ll be at a protected account page:

Complete Guide Session Management Nextjs guide illustration

And that is all that’s needed for session management with Clerk. You can set up other session options like token timeouts and custom payloads in your dashboard.

The pitfalls of session management

Session management is a critical component in web development, acting as the gatekeeper to user-specific, sensitive information and functionalities.

But implementing robust session management is not without its challenges. Security, performance, and usability issues are all concerns you’ll have to deal with when building session management in Next.js. Like many aspects of authentication, session management is one that is best left to dedicated libraries and solutions. Check out the Clerk session docs to find out more about setting this up easily with Clerk.