Skip to main content

Next.js Credentials Provider

In this guide, you'll learn how to implement traditional username/password authentication in your Next.js applications using the Credentials Provider from NextAuth.js. This authentication strategy is ideal for applications that need to authenticate users against an existing user database or API.

Introduction to Credentials Provider

The Credentials Provider allows you to handle authentication directly within your Next.js application by validating username/password combinations (or any credentials) against your own authentication logic. Unlike OAuth providers that redirect users to external services, the Credentials Provider enables you to create a custom authentication flow while still leveraging NextAuth.js's session management, JWT handling, and security features.

caution

While convenient, Credentials Provider is generally less secure than passwordless or OAuth authentication methods. It requires you to handle password storage, hashing, and security best practices.

Setting Up NextAuth.js with Credentials Provider

Step 1: Installation

First, install NextAuth.js in your Next.js project:

bash
npm install next-auth
# or
yarn add next-auth
# or
pnpm add next-auth

Step 2: Create API Route

Create a new API route for authentication. In your Next.js project, add the following file:

pages/api/auth/[...nextauth].js (for Pages Router) or app/api/auth/[...nextauth]/route.js (for App Router):

javascript
import NextAuth from 'next-auth';
import CredentialsProvider from 'next-auth/providers/credentials';

export default NextAuth({
providers: [
CredentialsProvider({
// The name to display on the sign in form (e.g. "Sign in with...")
name: "Credentials",
// The credentials is used to generate a suitable form on the sign in page.
credentials: {
username: { label: "Username", type: "text", placeholder: "jsmith" },
password: { label: "Password", type: "password" }
},
async authorize(credentials, req) {
// Add your own logic here to validate credentials
// Return null if user data could not be retrieved
// Return user data to store in JWT token

// Example validation:
const res = await fetch("https://your-api.com/api/login", {
method: 'POST',
body: JSON.stringify(credentials),
headers: { "Content-Type": "application/json" }
});
const user = await res.json();

if (res.ok && user) {
return user;
}

// If you return null then an error will be displayed advising the user to check their details.
return null;
}
})
],
// Additional NextAuth.js configuration options
session: {
strategy: "jwt",
},
callbacks: {
async jwt({ token, user }) {
// Add custom data to the JWT token
if (user) {
token.id = user.id;
token.role = user.role;
}
return token;
},
async session({ session, token }) {
// Add custom data to the session
session.user.id = token.id;
session.user.role = token.role;
return session;
}
},
pages: {
signIn: '/auth/signin', // Custom sign-in page
},
});

Creating a Custom Sign-in Form

Let's create a custom sign-in form to use with our Credentials Provider:

pages/auth/signin.js (Pages Router) or app/auth/signin/page.js (App Router):

jsx
import { useState } from 'react';
import { signIn } from 'next-auth/react';
import { useRouter } from 'next/router';

export default function SignIn() {
const [username, setUsername] = useState('');
const [password, setPassword] = useState('');
const [error, setError] = useState('');
const router = useRouter();

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

const result = await signIn('credentials', {
redirect: false,
username,
password,
});

if (result.error) {
setError(result.error);
} else {
// Redirect to the desired page after successful login
router.push('/dashboard');
}
};

return (
<div className="login-container">
<h1>Sign In</h1>
{error && <div className="error">{error}</div>}
<form onSubmit={handleSubmit}>
<div className="form-group">
<label htmlFor="username">Username</label>
<input
type="text"
id="username"
value={username}
onChange={(e) => setUsername(e.target.value)}
required
/>
</div>
<div className="form-group">
<label htmlFor="password">Password</label>
<input
type="password"
id="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
required
/>
</div>
<button type="submit">Sign In</button>
</form>
</div>
);
}

Integrating NextAuth.js with Your Application

Step 1: Configure NextAuth.js Provider

Wrap your application with the SessionProvider component to provide authentication context:

For Pages Router - pages/_app.js:

jsx
import { SessionProvider } from 'next-auth/react';

function MyApp({ Component, pageProps: { session, ...pageProps } }) {
return (
<SessionProvider session={session}>
<Component {...pageProps} />
</SessionProvider>
);
}

export default MyApp;

For App Router - app/layout.js:

jsx
import { getServerSession } from 'next-auth';
import SessionProvider from './components/SessionProvider';
import { authOptions } from './api/auth/[...nextauth]/route';

export default async function RootLayout({ children }) {
const session = await getServerSession(authOptions);

return (
<html lang="en">
<body>
<SessionProvider session={session}>
{children}
</SessionProvider>
</body>
</html>
);
}

Step 2: Create a SessionProvider component (for App Router only)

app/components/SessionProvider.js:

jsx
'use client';

import { SessionProvider as NextAuthSessionProvider } from 'next-auth/react';

export default function SessionProvider({ children, session }) {
return (
<NextAuthSessionProvider session={session}>
{children}
</NextAuthSessionProvider>
);
}

Practical Examples of Using Authentication

Example 1: Protected Routes

Create a component to protect routes that require authentication:

jsx
import { useSession } from 'next-auth/react';
import { useRouter } from 'next/router';
import { useEffect } from 'react';

export default function ProtectedRoute({ children }) {
const { data: session, status } = useSession();
const router = useRouter();

useEffect(() => {
if (status === 'loading') return; // Do nothing while loading
if (!session) router.push('/auth/signin');
}, [session, status, router]);

if (status === 'loading') {
return <div>Loading...</div>;
}

return session ? <>{children}</> : null;
}

Usage:

jsx
import ProtectedRoute from '../components/ProtectedRoute';

export default function Dashboard() {
return (
<ProtectedRoute>
<div>
<h1>Dashboard</h1>
<p>This page is protected and only visible to authenticated users.</p>
</div>
</ProtectedRoute>
);
}

Example 2: Implementing User Authentication with a Database

Let's build a more realistic example using Prisma ORM and bcrypt for password hashing:

javascript
// pages/api/auth/[...nextauth].js
import NextAuth from 'next-auth';
import CredentialsProvider from 'next-auth/providers/credentials';
import { PrismaClient } from '@prisma/client';
import bcrypt from 'bcryptjs';

const prisma = new PrismaClient();

export default NextAuth({
providers: [
CredentialsProvider({
name: "Credentials",
credentials: {
email: { label: "Email", type: "email" },
password: { label: "Password", type: "password" }
},
async authorize(credentials) {
// Check if email and password are provided
if (!credentials?.email || !credentials?.password) {
throw new Error('Please enter an email and password');
}

// Find user by email
const user = await prisma.user.findUnique({
where: {
email: credentials.email,
},
});

// If no user found or password does not match
if (!user || !(await bcrypt.compare(credentials.password, user.password))) {
throw new Error('Invalid email or password');
}

// Remove sensitive data before returning the user object
const { password, ...userWithoutPassword } = user;
return userWithoutPassword;
}
})
],
callbacks: {
async jwt({ token, user }) {
if (user) {
token.id = user.id;
token.role = user.role;
}
return token;
},
async session({ session, token }) {
if (session?.user) {
session.user.id = token.id;
session.user.role = token.role;
}
return session;
}
},
pages: {
signIn: '/auth/signin',
},
session: {
strategy: "jwt",
maxAge: 30 * 24 * 60 * 60, // 30 days
},
secret: process.env.NEXTAUTH_SECRET,
});

User Registration

A complete authentication system needs a registration endpoint. Here's a simple example:

javascript
// pages/api/register.js
import { PrismaClient } from '@prisma/client';
import bcrypt from 'bcryptjs';

const prisma = new PrismaClient();

export default async function handler(req, res) {
if (req.method !== 'POST') {
return res.status(405).json({ message: 'Method not allowed' });
}

try {
const { name, email, password } = req.body;

// Validation
if (!name || !email || !password) {
return res.status(400).json({ message: 'Missing required fields' });
}

// Check if user already exists
const existingUser = await prisma.user.findUnique({
where: { email },
});

if (existingUser) {
return res.status(409).json({ message: 'User already exists' });
}

// Hash password
const hashedPassword = await bcrypt.hash(password, 10);

// Create user
const user = await prisma.user.create({
data: {
name,
email,
password: hashedPassword,
},
});

// Remove password from response
const { password: _, ...userWithoutPassword } = user;

return res.status(201).json(userWithoutPassword);
} catch (error) {
console.error('Registration error:', error);
return res.status(500).json({ message: 'Internal server error' });
}
}

Security Considerations

When using the Credentials Provider, remember these security best practices:

  1. Always hash passwords before storing them using libraries like bcrypt
  2. Implement rate limiting to prevent brute-force attacks
  3. Use HTTPS for all authentication requests
  4. Implement multi-factor authentication for sensitive applications
  5. Set secure and HTTP-only cookies
  6. Validate inputs to prevent injection attacks
  7. Implement proper error handling without leaking sensitive information

Summary

The Credentials Provider in NextAuth.js gives you a flexible way to implement username/password authentication in your Next.js applications. By following the steps in this guide, you can:

  • Set up NextAuth.js with Credentials Provider
  • Create custom sign-in forms
  • Protect routes that require authentication
  • Implement secure user registration and login flows
  • Integrate with databases for user storage

While the Credentials Provider is a powerful tool, consider other authentication methods like OAuth or passwordless authentication for improved security in production applications.

Additional Resources

Exercises

  1. Create a complete authentication system with registration, login, and password reset functionality
  2. Enhance the login form with client-side validation and error handling
  3. Implement role-based authorization to restrict access based on user roles
  4. Add multi-factor authentication to your login flow
  5. Create a user profile page where authenticated users can update their information

Happy coding!



If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)