Logo

dev-resources.site

for different kinds of informations.

Securing Your Fullstack App: Authentication & Authorization with JWT in Next.js and Node πŸ”’ πŸš€

Published at
11/27/2024
Categories
nextjs
jwt
node
react
Author
rodolphedupuis
Categories
4 categories in total
nextjs
open
jwt
open
node
open
react
open
Author
14 person written this
rodolphedupuis
open
Securing Your Fullstack App: Authentication & Authorization with JWT in Next.js and Node πŸ”’ πŸš€

Securing your fullstack application is critical to protect user data and maintain trust. One of the most common ways to implement authentication and authorization in modern applications is through JSON Web Tokens (JWT).

In this guide, we’ll explore how to secure your Next.js app by setting up JWT-based authentication and implementing role-based authorization.

What Is JWT and Why Use It?

JWT is a compact, self-contained way to securely transmit information between parties as a JSON object. It is often used for:

  • Authentication: Verifying the identity of a user.
  • Authorization: Controlling access to resources based on user roles or permissions.

How JWT works

  1. Login: The client sends login credentials (e.g., email and password) to the backend.
  2. Token generation: If valid, the server generates a JWT containing user details and sends it back.
  3. Storage: The client stores the JWT (e.g., in httpOnly cookies or local storage).
  4. Subsequent Requests: The client includes the JWT in the request headers to access protected resources.
  5. Verification: The server verifies the token's validity before granting access.

Step 1: Set Up Your Next.js and Node.js Environment

Before diving into the code, ensure you have the following:

  • Node.js installed.
  • A new Next.js project ready to run: run the command npx create-next-app@latest your-project-name to create a Next.js app.
  • The following dependencies installed on your backend folder: npm install jsonwebtoken bcryptjs express cors body-parser cookie-parser

Your Next.js environment is now ready to create some magic πŸš€


Step 2: Build the Backend API

We’ll use Node.js with Express to handle authentication.

Create the Express server

const express = require('express');
const jwt = require('jsonwebtoken');
const bcrypt = require('bcryptjs');
const bodyParser = require('body-parser');
const cors = require('cors');
const cookieParser = require('cookie-parser');

const app = express();
const PORT = 5001;

// Middleware
app.use(cors({ origin: 'http://localhost:3000', credentials: true }));
app.use(bodyParser.json());
app.use(cookieParser());

// Secret key for JWT
const SECRET_KEY = 'your-secret-key';

// Mock user database
const users = [
  { id: 1, email: '[email protected]', password: bcrypt.hashSync('aw3$0m3AndHaRdPwD!', 10), role: 'user' },
  { id: 2, email: '[email protected]', password: bcrypt.hashSync('aw3$0m3AndHaRdPwDButAdm1n!', 10), role: 'admin' },
];
Enter fullscreen mode Exit fullscreen mode

Create Login route

app.post('/api/login', (req, res) => {
  const { email, password } = req.body;
  const user = users.find(u => u.email === email);

  if (!user || !bcrypt.compareSync(password, user.password)) {
    return res.status(401).json({ message: 'Invalid email or password' });
  }

  // Generate JWT
  const token = jwt.sign({ id: user.id, role: user.role }, SECRET_KEY, { expiresIn: '1h' });

  // Set as httpOnly cookie
  res.cookie('token', token, { httpOnly: true }).json({ message: 'Logged in successfully' });
});
Enter fullscreen mode Exit fullscreen mode

Create Protected route

app.get('/api/protected', (req, res) => {
  const token = req.cookies.token;

  if (!token) {
    return res.status(401).json({ message: 'Unauthorized' });
  }

  try {
    const decoded = jwt.verify(token, SECRET_KEY);
    res.json({ message: 'Welcome to the protected route!', user: decoded });
  } catch (err) {
    res.status(401).json({ message: 'Invalid token' });
  }
});
Enter fullscreen mode Exit fullscreen mode

Create Logout route

app.post('/api/logout', (req, res) => {
  res.clearCookie('token').json({ message: 'Logged out successfully' });
});
Enter fullscreen mode Exit fullscreen mode

Don't forget to add at the end of your file:

// Start the server
app.listen(PORT, () => {
    console.log(`Server is running on http://localhost:${PORT}`);
});
Enter fullscreen mode Exit fullscreen mode

This is a very basic implementation of an Express app that will allow us to send HTTP request on the implemented routes 🎯

Start the server to test using the commande: node server.js where server.js is the name of your file.


Step 3: Implement the Next.js frontend

First of all, don't forget to add the axios dependency by using the command: npm install axios in your Next.js app

Login page

Create a new file pages/login.js:

import { useState } from 'react';
import axios from 'axios';

export default function Login() {
  const [email, setEmail] = useState('');
  const [password, setPassword] = useState('');

  const handleLogin = async (e) => {
    e.preventDefault();
    try {
      const response = await axios.post('http://localhost:5000/api/login', { email, password }, { withCredentials: true });
      alert(response.data.message);
    } catch (err) {
      alert(err.response?.data?.message || 'Login failed');
    }
  };

  return (
    <form onSubmit={handleLogin}>
      <input type="email" placeholder="Email" value={email} onChange={(e) => setEmail(e.target.value)} />
      <input type="password" placeholder="Password" value={password} onChange={(e) => setPassword(e.target.value)} />
      <button type="submit">Login</button>
    </form>
  );
}
Enter fullscreen mode Exit fullscreen mode

This page renders a very simple login form where you can use the login information you added in the users array variable in your server implementation. This is obviously not a good thing to do and you should never do this, this is a simple implementation to demonstrate the power of JWT security πŸ’ͺ

Add a protected page

Create a new file pages/protected.js:

import axios from 'axios';
import { useEffect, useState } from 'react';

export default function Protected() {
  const [data, setData] = useState(null);

  useEffect(() => {
    const fetchData = async () => {
      try {
        const response = await axios.get('http://localhost:5000/api/protected', { withCredentials: true });
        setData(response.data);
      } catch (err) {
        alert('Access denied');
      }
    };
    fetchData();
  }, []);

async function handleLogout() {
        try {
            await axios.post('http://localhost:5001/api/logout', {}, { withCredentials: true });
            router.push('/login');
        } catch (error) {
            alert('Could not logout');
        }
    }

  if (!data) return <p>Loading...</p>;

  return <div>
        {JSON.stringify(data)}
        <button onClick={handleLogout}>Logout</button>
    </div>;
}
Enter fullscreen mode Exit fullscreen mode

This page renders the result of the server GET request on the route /api/protected βœ…

If you try to access this route without being logged in, an error alert will be displayed on the page and you will not be able to access its content ❌


Step 4: Add Role-Based Authorization

Enhance your backend to enforce roles:

function authorizeRoles(allowedRoles) {
  return (req, res, next) => {
    const token = req.cookies.token;
    if (!token) return res.status(401).json({ message: 'Unauthorized' });

    try {
      const decoded = jwt.verify(token, SECRET_KEY);
      if (!allowedRoles.includes(decoded.role)) {
        return res.status(403).json({ message: 'Forbidden' });
      }
      req.user = decoded;
      next();
    } catch (err) {
      res.status(401).json({ message: 'Invalid token' });
    }
  };
}

// Example: Admin-only route
app.get('/api/admin', authorizeRoles(['admin']), (req, res) => {
  res.json({ message: 'Welcome Admin!' });
});
Enter fullscreen mode Exit fullscreen mode

This route uses a validation middleware authorizeRoles that is the function we added right before. This function verifies there is a token and that the user trying to access the resource has the right authorization.


Conclusion

So here is how you can simply, and very fast (as always with my guides 🀩), secure your fullstack application with JWT technology in Next.js and Node.

This approach provides a reliable and scalable solution for managing user sessions while ensuring a seamless user experience.

Stay proactive about security by keeping dependencies updated, implementing HTTPS, and following best practices for managing tokens.

Happy coding!

jwt Article's
30 articles in total
Favicon
Testing with JWT in .NET APIs
Favicon
JWT Authentication With NodeJS
Favicon
[Part 1] Rails 8 Authentication but with JWT
Favicon
How to Generate a Secure JWT Secret Using Node.js
Favicon
Implementing JWT Authentication in .NET API
Favicon
Managing JWT Logout with Blacklists and Redis: A Beginner-Friendly Guide
Favicon
Understanding the Differences Between OAuth2 and OpenID Connect (OIDC)
Favicon
JWT vs Opaque Tokens: A Comprehensive Guide to Choosing Wisely
Favicon
ΰΈ§ΰΈ΄ΰΈ˜ΰΈ΅ΰΈ—ΰΈ³ Auth API ΰΈ”ΰΉ‰ΰΈ§ΰΈ’ Express, JWT, MySQL แΰΈ₯ΰΈ° Prisma
Favicon
JsonWebTokenError: jwt must be provided
Favicon
JSON Web Tokens (JWT): GuΓ­a Esencial y Buenas PrΓ‘cticas
Favicon
Djoser+SimpleJWT
Favicon
Mastering JWT Authentication: A Complete Guide with MERN Stack
Favicon
How to secure minimal api microservices with asp.net core identity
Favicon
PHP HyperF -> Firebase JWT
Favicon
How to Create a quick Authentication library for NestJS/MongoDB application
Favicon
Learning JWT security using KumuluzEE β€” The finances of a league of the environment
Favicon
Feijuca.Auth - Part 1: Configuring the tool
Favicon
Securing Your .NET APIs with JWT Authentication
Favicon
"Unauthorized: No token provided")
Favicon
Implementing JWT Authentication in Express API
Favicon
Integration of Salesforce, Node.js, and React: A Step-by-Step Guide
Favicon
Definition of Jwt and Use
Favicon
Implementing JWT Authentication in Go API
Favicon
flow design for access and refresh token- JWT
Favicon
Implementing JWT Authentication in Spring Boot API
Favicon
What is REST Api ? Implement and Secure ?
Favicon
Securing a REST API with JWT Authentication in C# Using AES-Encrypted Keys
Favicon
MS Graph API Certificate and Client Secret OAuth2.0 in Java Spring boot
Favicon
Securing Your Fullstack App: Authentication & Authorization with JWT in Next.js and Node πŸ”’ πŸš€

Featured ones: