Handling Authentication in SSR with JWT and Cookies

Loading

Authentication is a crucial aspect of any web application, especially when dealing with Server-Side Rendering (SSR). JSON Web Tokens (JWT) are commonly used for handling authentication in modern web applications due to their simplicity and statelessness. In an SSR context, managing authentication becomes more nuanced because the server must authenticate the user and inject the relevant data or token into the page before it’s sent to the client.

This guide will walk you through how to handle authentication using JWT and cookies in a Server-Side Rendered (SSR) React application.


1. What is JWT?

JSON Web Token (JWT) is an open standard used to securely transmit information between parties as a JSON object. It is compact, URL-safe, and can be used for authentication and information exchange.

A typical JWT consists of three parts:

  • Header: Contains metadata about the token, including the signing algorithm.
  • Payload: Contains the claims or the information you want to exchange (like user ID, roles, etc.).
  • Signature: Used to verify that the sender of the JWT is who it says it is and that the message wasn’t altered along the way.

2. Why Use JWT for Authentication in SSR?

JWT is a popular choice for authentication because:

  • Stateless: JWT doesn’t require the server to store session information, making it scalable.
  • Cross-domain: Can be used across different domains or microservices.
  • Secure: When properly configured, JWT can be very secure for transmitting authentication information.

3. Handling Authentication in SSR with JWT and Cookies

In a typical SSR application (e.g., with Next.js or custom React SSR), the flow for handling JWT-based authentication is as follows:

  • The user logs in, and the server generates a JWT.
  • The JWT is sent back to the client, typically stored in a cookie.
  • On subsequent requests, the server can read the JWT from the cookie to authenticate the user and serve personalized content during SSR.

Flow:

  1. Login:
    • The client sends credentials (username and password) to the server.
    • The server validates the credentials and generates a JWT.
    • The JWT is sent back to the client as a cookie (usually an HTTP-only cookie for security).
  2. Authentication on SSR:
    • When the user accesses a page, the server reads the JWT from the cookie.
    • The server validates the JWT and ensures the user is authenticated.
    • Based on authentication, the server renders the page with personalized data.
  3. Token Expiration and Refreshing:
    • JWT tokens have an expiration time (usually set to a few hours).
    • After expiration, the client can send a request to the server to refresh the token (using a refresh token).

4. Setting Up JWT Authentication with Cookies in SSR (Using Next.js as an Example)

Let’s implement JWT authentication in a Next.js application with SSR.

Step 1: Install Dependencies

First, install necessary dependencies:

npm install next react react-dom jsonwebtoken cookie

Step 2: Server-Side JWT Authentication Logic

We will need to create two main components:

  • API route to authenticate and issue JWT.
  • Server-side logic to handle JWT from cookies in SSR pages.
1. API Route for Authentication (/pages/api/login.js)

This API route is responsible for authenticating the user and issuing the JWT.

// pages/api/login.js
import jwt from 'jsonwebtoken';
import cookie from 'cookie';

export default async function handler(req, res) {
  if (req.method === 'POST') {
    const { username, password } = req.body;

    // Validate credentials (this is just an example; normally you'd query a DB)
    if (username === 'user' && password === 'password') {
      // Generate JWT token
      const token = jwt.sign({ username }, process.env.JWT_SECRET, { expiresIn: '1h' });

      // Set the token in an HTTP-only cookie
      res.setHeader('Set-Cookie', cookie.serialize('auth_token', token, {
        httpOnly: true, // Prevent client-side access to the cookie
        secure: process.env.NODE_ENV === 'production', // Use secure cookies in production
        maxAge: 60 * 60, // 1 hour
        path: '/',
      }));

      return res.status(200).json({ message: 'Authentication successful' });
    }

    return res.status(401).json({ message: 'Invalid credentials' });
  }

  return res.status(405).json({ message: 'Method Not Allowed' });
}

In this API route:

  • We validate the user’s credentials (you would normally query a database for real applications).
  • If the credentials are correct, we generate a JWT with jsonwebtoken and set it as an HTTP-only cookie using cookie.
2. Server-Side Authentication Check in getServerSideProps (/pages/dashboard.js)

When rendering the SSR page, we’ll read the cookie from the request to authenticate the user.

// pages/dashboard.js
import jwt from 'jsonwebtoken';
import cookie from 'cookie';

export async function getServerSideProps(context) {
  // Retrieve the JWT from the cookies
  const cookies = context.req.headers.cookie ? cookie.parse(context.req.headers.cookie) : {};
  const token = cookies.auth_token || '';

  // Verify the JWT and retrieve user data if valid
  try {
    const decoded = jwt.verify(token, process.env.JWT_SECRET);
    return { props: { user: decoded.username } };  // Pass user info to the page
  } catch (err) {
    return { redirect: { destination: '/login', permanent: false } };  // Redirect to login if invalid token
  }
}

export default function Dashboard({ user }) {
  return (
    <div>
      <h1>Welcome, {user}</h1>
      <p>This is your personalized dashboard</p>
    </div>
  );
}

In this getServerSideProps function:

  • We parse the cookies from the request headers.
  • We check for the presence of a valid JWT (auth_token) in the cookies.
  • If the JWT is valid, we extract the user data (like the username) and pass it to the page.
  • If the token is invalid or expired, we redirect the user to the login page.

Step 3: Handling Token Expiration and Refreshing

When the token expires, you may want to implement a refresh token mechanism:

  1. Store a refresh token securely in a cookie.
  2. When the JWT expires, the client can send a request to an API route (e.g., /api/refresh-token) to obtain a new JWT using the refresh token.

This approach typically requires:

  • Storing the refresh token in a secure, HttpOnly cookie.
  • Implementing an API route that verifies the refresh token and issues a new JWT if the refresh token is valid.

5. Security Considerations

  • HttpOnly Cookies: Always set the JWT in HttpOnly cookies to prevent client-side JavaScript from accessing the token. This prevents cross-site scripting (XSS) attacks.
  • Secure Cookies: In production, ensure that cookies are marked as secure to transmit them only over HTTPS.
  • Token Expiration: JWT tokens should have a reasonable expiration time. For longer sessions, consider using refresh tokens.
  • Refresh Tokens: Store refresh tokens in HttpOnly cookies and issue them with each login. Refresh tokens allow you to issue new JWT tokens without requiring the user to log in again.

Leave a Reply

Your email address will not be published. Required fields are marked *