Part 129: Enhancing User Authentication in Your Web Application

[App] Authentication User Database

[App] Authentication User Databas

In our previous exploration, we established a "Sign Up" page for new users to register on our website. This involved creating a form that collects user data and integrates it with our backend database using Prisma. Today, we'll focus on refining the "Sign In" functionality to ensure it operates seamlessly with our existing database, enabling users to log in with their registered credentials.

Understanding the Current Setup

Before diving into the new implementation, let's review our current setup. We have a "Sign In" page with a form where users can enter their email and password. The form submission triggers the signInAction, which we need to update to authenticate users against our database rather than using placeholder logic.

The Problem with Current Sign In

The existing signInAction uses a temporary function, authenticate, which allows any user with "test" as the password to log in. This is inadequate for real-world applications, where credentials should be verified against stored data.

Updating the Sign In Logic

Creating the authenticateUser Function

First, we'll create a new function, authenticateUser, in the lib/users.js module. This function will query the database to find a user with the provided email and password.

// File path: lib/users.js

import { db } from './db';

export async function authenticateUser(email, password) {
  return await db.user.findUnique({
    where: { email, password },
  });
}

export async function createUser({ email, name, password }) {
  return await db.user.create({
    data: { email, name, password },
  });
}

The authenticateUser function performs a database query to find a unique user matching the given email and password. If found, it returns the user object; otherwise, it returns null, indicating invalid credentials.

Modifying the signInAction

Next, we'll update the signInAction to use the authenticateUser function, replacing the placeholder logic.

// File path: app/sign-in/actions.js

'use server';

import { redirect } from 'next/navigation';
import { setSessionCookie } from '@/lib/auth';
import { authenticateUser } from '@/lib/users';

export async function signInAction(formData) {
  console.log('[signInAction]', formData);
  const email = formData.get('email');
  const password = formData.get('password');
  const user = await authenticateUser(email, password);
  if (!user) {
    return { isError: true, message: 'Invalid credentials' };
  }
  await setSessionCookie(user);
  redirect('/');
}

This updated action uses authenticateUser to verify credentials and, upon success, sets a session cookie and redirects the user to the home page. If authentication fails, it returns an error message.

Enhancing User Experience

Handling Duplicate Registrations

In the signUpAction, we encountered an issue with duplicate email registrations. To address this, we need to handle the unique constraint error gracefully by notifying the user.

// File path: app/sign-up/actions.js

export async function signUpAction(formData) {
  const data = {
    email: formData.get('email'),
    name: formData.get('name'),
    password: formData.get('password'),
  };
  // TODO validate data / handle duplicate email
  try {
    const user = await createUser(data);
    console.log('[signUpAction] user:', user);
    await setSessionCookie(user);
    redirect('/');
  } catch (error) {
    if (error.code === 'P2002') { // Prisma unique constraint error
      return { isError: true, message: 'Email already exists' };
    }
    throw error;
  }
}

Displaying Session Data

For better debugging and to enhance development visibility, we can log user session data in the NavBar component.

// File path: components/NavBar.jsx

import { getUserFromSession } from '@/lib/auth';
import NavLink from './NavLink';
import SignOutButton from './SignOutButton';

export default async function NavBar() {
  const user = await getUserFromSession();
  console.log('[NavBar] user:', user);
  return (
    <nav>
      <ul className="flex gap-2">
        <li className="font-bold font-orbitron">
          <NavLink href="/">
            Indie Gamer
          </NavLink>
        </li>
        <li className="ml-auto">
          <NavLink href="/reviews">
            Reviews
          </NavLink>
        </li>
        <li>
          <NavLink href="/about" prefetch={false}>
            About
          </NavLink>
        </li>
        {user ? (
          <li>
            <SignOutButton />
          </li>
        ) : (
        <li>
          <NavLink href="/sign-in">
            Sign in
          </NavLink>
        </li>
        )}
      </ul>
    </nav>
  );
}

Conclusion

By updating the "Sign In" logic to authenticate users against our database, we've strengthened our application's security and usability. We've also improved error handling for duplicate registrations and enhanced session visibility. These changes contribute to a more robust and user-friendly authentication system. In future posts, we'll explore further enhancements, such as securing passwords and implementing email verification. Stay tuned for more!

Last updated