DEV Community

Cover image for Implementing Server Actions and Stateless Authentication in Next.js
Leapcell
Leapcell

Posted on

2 1 1 1 1

Implementing Server Actions and Stateless Authentication in Next.js

Image description

Leapcell: The Best of Serverless Web Hosting

Detailed Explanation of Server Actions and Stateless Sessions in Next.js

Introduction

With the release of the widely popular App Router, Next.js has introduced an important new feature: Server Actions. Server Actions are designed to assist with server-side data operations. By reducing the dependence on client-side JavaScript, they gradually enhance form functionality. With this feature, developers can create powerful web applications using JavaScript and React without relying on traditional REST APIs.

This article will delve into the advantages of Server Actions and demonstrate their practical application by implementing stateless sessions based on cookies. At the same time, this article will serve as a step-by-step guide, providing a detailed introduction to the entire process of building a demonstration project using the App Router.

Getting Rid of REST APIs

In the traditional development model, when creating a Next.js web application that queries a database on the backend, it is usually necessary to create REST APIs to verify the identity status and query the database. The React application on the frontend is responsible for calling these APIs. However, when the React application is the only client and there is no need to open the APIs to the public, using REST APIs can be redundant because these APIs will only be called by the application itself.

With Server Actions, React components can directly run server-side code. Developers do not need to manually create API endpoints, and Next.js will automatically create corresponding endpoints for Server Actions in the background. When a Server Action is called, Next.js will send a POST request containing operation metadata to the current page to execute the corresponding operation.

The Need for Stateless Sessions

Next.js, as the preferred framework for creating stateless applications, often runs in a serverless environment, which means that in-memory storage of session data is not possible. Traditional session management usually relies on external storage services, such as Redis or a database.

However, when the amount of session data is small, an alternative solution is to design stateless sessions through secure encryption methods and cookies stored on the client side. This approach does not require external storage, decentralizes session data, and has significant advantages in reducing server load and improving the overall performance of the application. Therefore, our goal is to build a lightweight and efficient stateless session system that fully utilizes the client-side storage capabilities and ensures data security through rigorous encryption measures.

Basic Session Implementation

First, you need to start a new project:

npx create-next-app@latest
Enter fullscreen mode Exit fullscreen mode

For more installation details, you can refer to the official guide.

Building the Session Library

To make it easier to understand Server Actions, we will first create a simplified session system. This version will use JSON to store session data in cookies.

Create the session/index.ts file and add the following code:

"use server";

import { cookies } from 'next/headers';

export type Session = {
  username: string;
};

export const getSession = async (): Promise<Session | null> => {
  const cookieStore = cookies();
  const session = cookieStore.get('session');

  if (session?.value) {
    return JSON.parse(session.value) as Session;
  }

  return null;
};

export const setSession = async (session: Session) => {
  const cookieStore = cookies();
  cookieStore.set('session', JSON.stringify(session));
};

export const removeSession = async () => {
  const cookieStore = cookies();
  cookieStore.delete('session');
};
Enter fullscreen mode Exit fullscreen mode

The first line of the code "use server" marks the functions in this file as Server Actions. Since direct access to request and response is not possible, next/headers is used here to read and write cookies, and this functionality is only available in Server Actions.

Implementing Two New Server Actions

After having the session library, the next step is to implement the login and logout functions to demonstrate the usability of the session system.

Add the following code to the user/index.ts file:

"use server";

import { removeSession, setSession } from '@/session';

export const signIn = async (username: string) => {
  await setSession({ username });
};

export const signOut = async () => {
  await removeSession();
};
Enter fullscreen mode Exit fullscreen mode

Here, a simplified login process is adopted, and logging in only requires entering the username.

Building the Page

In the App Router, pages are usually asynchronous components, and Server Actions cannot be directly called from such components. You need to create components using "use client":

components/sign-in.tsx

"use client";

import { signIn } from '@/user';
import { useState } from'react';

const SignIn = () => {
  const [username, setUsername] = useState('');

  return (
    <div>
      <input
        type="text"
        value={username}
        placeholder="username"
        onChange={(event) => {
          setUsername(event.target.value);
        }}
      />
      <button
        disabled={!username}
        onClick={() => {
          signIn(username);
        }}
      >
        Sign In
      </button>
    </div>
  );
};

export default SignIn;
Enter fullscreen mode Exit fullscreen mode

components/sign-out.tsx

"use client";

import { signOut } from '@/user';

const SignOut = () => {
  return (
    <button
      onClick={() => {
        signOut();
      }}
    >
      Sign Out
    </button>
  );
};

export default SignOut;
Enter fullscreen mode Exit fullscreen mode

Finally, build the app/page.tsx:

import { getSession } from '@/session';
import styles from './page.module.css';
import SignIn from '../components/sign-in';
import SignOut from '@/components/sign-out';

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

  return (
    <main className={styles.main}>
      {session? (
        <div>
          <div>You are logged in as {session.username}</div>
          <div>
            <SignOut />
          </div>
        </div>
      ) : (
        <SignIn />
      )}
    </main>
  );
}
Enter fullscreen mode Exit fullscreen mode

Implementing Encryption

After implementing the relevant functions of Server Actions, the last step is to implement the encryption function, which can be completed through the crypto module.

session/encrypt.ts

import { createCipheriv, createDecipheriv } from 'crypto';

// Replace with your own key and iv
// You can generate them using crypto.randomBytes(32) and crypto.randomBytes(16)
const key = Buffer.from('17204a84b538359abe8ba74807efa12a068c20a7c7f224b35198acf832cea57b', 'hex');
const iv = Buffer.from('da1cdcd9fe4199c835bd5f1d56446aff', 'hex');
const algorithm = 'aes-256-cbc';

export const encrypt = (text: string) => {
  const cipher = createCipheriv(algorithm, key, iv);
  const encrypted = cipher.update(text, 'utf8', 'base64');
  return `${encrypted}${cipher.final('base64')}`;
};

export const decrypt = (encrypted: string) => {
  const decipher = createDecipheriv(algorithm, key, iv);
  const decrypted = decipher.update(encrypted, 'base64', 'utf8');
  return `${decrypted}${decipher.final('utf8')}`;
};
Enter fullscreen mode Exit fullscreen mode

Subsequently, modify the session library to implement the encryption function:

"use server";

import { cookies } from 'next/headers';
import { decrypt, encrypt } from './encrypt';

export type Session = {
  username: string;
};

export const getSession = async (): Promise<Session | null> => {
  const cookieStore = cookies();
  const session = cookieStore.get('session');

  if (session?.value) {
    try {
      const decrypted = decrypt(session.value);
      return JSON.parse(decrypted) as Session;
    } catch {
      // Ignore invalid sessions
    }
  }

  return null;
};

export const setSession = async (session: Session) => {
  const cookieStore = cookies();
  const encrypted = encrypt(JSON.stringify(session));
  cookieStore.set('session', encrypted);
};

export const removeSession = async () => {
  const cookieStore = cookies();
  cookieStore.delete('session');
};
Enter fullscreen mode Exit fullscreen mode

Leapcell: The Best of Serverless Web Hosting

Finally, I recommend a platform that is most suitable for deploying web services: Leapcell

Image description

🚀 Build with Your Favorite Language

Develop effortlessly in JavaScript, Python, Go, or Rust.

🌍 Deploy Unlimited Projects for Free

Only pay for what you use—no requests, no charges.

⚡ Pay-as-You-Go, No Hidden Costs

No idle fees, just seamless scalability.

Image description

📖 Explore Our Documentation

🔹 Follow us on Twitter: @LeapcellHQ

Heroku

Amplify your impact where it matters most — building exceptional apps.

Leave the infrastructure headaches to us, while you focus on pushing boundaries, realizing your vision, and making a lasting impression on your users.

Get Started

Top comments (1)

Collapse
 
michael_liang_0208 profile image
Michael Liang

Nice post.
This post is helpful for web developers.

Join the Runner H "AI Agent Prompting" Challenge: $10,000 in Prizes for 20 Winners!

Runner H is the AI agent you can delegate all your boring and repetitive tasks to - an autonomous agent that can use any tools you give it and complete full tasks from a single prompt.

Check out the challenge

DEV is bringing live events to the community. Dismiss if you're not interested. ❤️