Implementing Authentication and Authorization using Context API in Next.js: A Comprehensive Guide with Real Code Examples [PART 1]

Implementing Authentication and Authorization using Context API in Next.js: A Comprehensive Guide with Real Code Examples [PART 1]

Table of contents

No heading

No headings in the article.

In today's digital landscape, web applications require robust security measures to protect sensitive user data and ensure proper access control. Authentication and authorization are fundamental components of any secure web application. Authentication is the process of verifying a user's identity, while authorization determines what actions a user is allowed to perform within the application.

In this article, we will explore how to implement authentication and authorization in a Next.js application using the Context API, a powerful state management tool provided by React. The Context API simplifies the process of passing data through the component tree without the need for prop-drilling, making it an excellent choice for handling global authentication and authorization states.

Prerequisites

Before diving into the implementation, ensure you have the following prerequisites:

  1. Basic knowledge of React and Next.js.

  2. Familiarity with concepts like authentication and authorization.

  3. Node.js and npm are installed on your system.

  4. A code editor of your choice (e.g., Visual Studio Code, Sublime Text).

Setting Up the Next.js Project

To get started, let's create a new Next.js project:

Step 1: Install Next.js

Open your terminal and execute the following command:

npx create-next-app next-auth-example

Step 2: Navigate to the project folder

cd next-auth-example

Step 3: Install required dependencies

Next, we'll install some essential packages for our authentication implementation:

npm install jsonwebtoken bcryptjs cookie-parser

Now that we have our Next.js project set up and dependencies installed let's move on to implementing authentication and authorization.

Implementing Authentication with Context API

Step 1: Creating the AuthContext

First, we need to create the AuthContext to manage the authentication state throughout the application. Create a new file called authContext.js inside the context folder in the root of your project.

// context/authContext.js
import { createContext, useState, useContext } from 'react';

const AuthContext = createContext();

export const AuthProvider = ({ children }) => {
  const [user, setUser] = useState(null);

  return (
    <AuthContext.Provider value={{ user, setUser }}>
      {children}
    </AuthContext.Provider>
  );
};

export const useAuth = () => useContext(AuthContext);

Step 2: Wrap your application with AuthProvider

Open the _app.js file inside the pages folder and wrap your entire application with the AuthProvider.

// pages/_app.js
import { AuthProvider } from '../context/authContext';

function MyApp({ Component, pageProps }) {
  return (
    <AuthProvider>
      <Component {...pageProps} />
    </AuthProvider>
  );
}

export default MyApp;

Step 3: Implementing the Authentication Functions

Now, let's create functions to handle user authentication. Create a new file called auth.js in the utils folder.

// utils/auth.js
import jwt from 'jsonwebtoken';
import bcrypt from 'bcryptjs';

const secret = 'your_secret_key_here'; // Replace this with a secure random string

// Function to generate JWT
const generateToken = (data) => {
  return jwt.sign(data, secret, { expiresIn: '1d' });
};

// Function to hash the password
const hashPassword = async (password) => {
  const salt = await bcrypt.genSalt(10);
  return await bcrypt.hash(password, salt);
};

// Function to compare hashed password with plain text password
const comparePassword = async (password, hashedPassword) => {
  return await bcrypt.compare(password, hashedPassword);
};

export { generateToken, hashPassword, comparePassword };

Step 4: Creating the Login Component

Create a new file called Login.js inside the components folder.

// components/Login.js
import { useState } from 'react';
import { useRouter } from 'next/router';
import { useAuth } from '../context/authContext';
import { generateToken } from '../utils/auth';

const Login = () => {
  const [email, setEmail] = useState('');
  const [password, setPassword] = useState('');
  const { setUser } = useAuth();
  const router = useRouter();

  const handleSubmit = async (e) => {
    e.preventDefault();

    // Replace this with your actual login logic (e.g., fetching user data from a database)
    const user = {
      id: 1,
      email: 'user@example.com',
      password: '$2a$10$1Uj/XoWwTc.3e7vXuX/EbOQbV3UcdaTgVc8N/ijEg7xyob5FXT4fi', // hashed password
    };

    // Compare the entered password with the stored hashed password
    if (user.email === email && (await comparePassword(password, user.password))) {
      // If the passwords match, generate a token and set the user in the context
      const token = generateToken({ id: user.id, email: user.email });
      setUser({ ...user, token });
      router.push('/');
    } else {
      alert('Invalid credentials. Please try again.');
    }
  };

  return (
    <form onSubmit={handleSubmit}>
      <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>
  );
};

export default Login;

Step 5: Implementing the Logout Component

Create a new file called Logout.js inside the components folder.

// components/Logout.js
import { useRouter } from 'next/router';
import { useAuth } from '../context/authContext';

const Logout = () => {
  const { setUser } = useAuth();
  const router = useRouter();

  const handleLogout = () => {
    // Clear the user from the context to log them out
    setUser(null);
    router.push('/login');
  };

  return (
    <button onClick={handleLogout}>
      Logout
    </button>
  );
};

export default Logout;

Step 6: Implementing Protected Routes

To enforce authorization and restrict access to certain pages, we need to create a Higher Order Component (HOC) that will check if the user is authenticated before rendering the component. Create a new file called withAuth.js inside the utils folder.

// utils/withAuth.js
import { useEffect } from 'react';
import { useRouter } from 'next/router';
import { useAuth } from '../context/authContext';

const withAuth = (WrappedComponent) => {
  const Wrapper = (props) => {
    const { user } = useAuth();
    const router = useRouter();

    useEffect(() => {
      // If the user is not authenticated, redirect them to the login page
      if (!user) {
        router.push('/login');
      }
    }, [user, router]);

    return <WrappedComponent {...props} />;
  };

  return Wrapper;
};

export default withAuth;

Step 7: Create Protected Pages

Now, you can create pages that require authentication. For example, create a new file called dashboard.js inside the pages folder.

// pages/dashboard.js
import withAuth from '../utils/withAuth';

const Dashboard = () => {
  return (
    <div>
      <h1>Welcome to the Dashboard</h1>
    </div>
  );
};

export default withAuth(Dashboard);

Congratulations! You have successfully implemented authentication and authorization in a Next.js application using the Context API. By following this comprehensive guide and using real code examples, you've learned how to set up AuthContext, handle user login and logout, and restrict access to certain pages based on user authentication.

Remember, security is an ongoing process, and it's crucial to keep your authentication and authorization mechanisms up-to-date with the latest best practices. Always validate user input, use secure encryption methods, and consider incorporating additional security measures like two-factor authentication (2FA) for enhanced protection.

Look out for part 2 in my next article.

With the knowledge gained from this article, you can confidently build secure and robust web applications with Next.js. Happy coding!