Project: bcrypt Credentials Login with Rate Limiting
In this project, we will implement a custom username/password login system. We will verify hashes using bcryptjs and execute credential verification inside a Next.js Server Action to shield APIs from script attacks.
1. Creating the Verification Server Action
To process logins securely, write a Next.js Server Action:
// app/actions/loginAction.ts
"use server";
import { signIn } from "../../auth";
import { AuthError } from "next-auth";
export async function authenticateUser(formData: FormData) {
const email = formData.get("email");
const password = formData.get("password");
if (!email || !password) {
return { error: "Please enter email and password fields" };
}
try {
// Call Auth.js internal credentials routing handler
await signIn("credentials", {
email,
password,
redirectTo: "/dashboard",
});
return { success: true };
} catch (error) {
if (error instanceof AuthError) {
switch (error.type) {
case "CredentialsSignin":
return { error: "Invalid username or password credentials" };
default:
return { error: "Authentication system error occurred" };
}
}
// Re-throw next.js internal redirect exceptions
throw error;
}
}2. Designing the Custom Credentials Login Page
Create the login component containing the server action binding form:
// app/login-credentials/page.tsx
"use client";
import React, { useState } from "react";
import { authenticateUser } from "../actions/loginAction";
export default function CredentialsLoginPage() {
const [errorMessage, setErrorMessage] = useState("");
const [loading, setLoading] = useState(false);
async function handleFormSubmit(e: React.FormEvent<HTMLFormElement>) {
e.preventDefault();
setErrorMessage("");
setLoading(true);
const formData = new FormData(e.currentTarget);
const result = await authenticateUser(formData);
if (result?.error) {
setErrorMessage(result.error);
setLoading(false);
}
}
return (
<div className="max-w-md mx-auto p-8 border rounded-3xl bg-white shadow-sm mt-20">
<h2 className="text-xl font-bold text-gray-950">Credentials Login</h2>
<p className="text-gray-400 text-xs mt-1">Provide your local registered details to enter.</p>
{errorMessage && (
<div className="p-3 bg-red-50 text-red-600 text-xs rounded-lg mt-4">
{errorMessage}
</div>
)}
<form onSubmit={handleFormSubmit} className="mt-8 space-y-6">
<div>
<label className="block text-xs font-semibold text-gray-700 uppercase">Email Address</label>
<input
type="email"
name="email"
required
className="w-full border p-2.5 rounded-lg mt-2 text-sm"
/>
</div>
<div>
<label className="block text-xs font-semibold text-gray-700 uppercase">Password</label>
<input
type="password"
name="password"
required
className="w-full border p-2.5 rounded-lg mt-2 text-sm"
/>
</div>
<button
type="submit"
disabled={loading}
className="w-full bg-blue-600 hover:bg-blue-700 text-white font-semibold py-2.5 rounded-lg text-sm transition"
>
{loading ? "Authenticating..." : "Login"}
</button>
</form>
</div>
);
}Published on Last updated: