Next.js OAuth Integration
Introduction
OAuth (Open Authorization) is an open standard protocol that allows secure token-based authentication and authorization. By implementing OAuth in your Next.js application, you can let users sign in through trusted third-party providers like Google, GitHub, Facebook, or Twitter without creating a new account specifically for your application.
This guide will walk you through implementing OAuth authentication in a Next.js application using the NextAuth.js library, which simplifies the OAuth integration process significantly.
Why Use OAuth?
Before diving into implementation, let's understand why OAuth is beneficial:
- Improved User Experience: Users can log in with accounts they already have
- Enhanced Security: You don't need to store passwords
- Reduced Development Time: No need to build a complete authentication system
- Access to Provider APIs: Many OAuth implementations give you access to the provider's APIs
Prerequisites
Before we start, make sure you have:
- Node.js installed (version 14 or later)
- A basic Next.js application set up
- npm or yarn for package management
Setting Up NextAuth.js
NextAuth.js is a complete authentication solution for Next.js applications that supports OAuth providers.
Step 1: Install NextAuth.js
First, install NextAuth.js in your project:
npm install next-auth
# or
yarn add next-auth
Step 2: Create API Route for Authentication
Create a file called [...nextauth].js
in the pages/api/auth
directory:
// pages/api/auth/[...nextauth].js
import NextAuth from 'next-auth';
import GoogleProvider from 'next-auth/providers/google';
import GitHubProvider from 'next-auth/providers/github';
export default NextAuth({
providers: [
GoogleProvider({
clientId: process.env.GOOGLE_CLIENT_ID,
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
}),
GitHubProvider({
clientId: process.env.GITHUB_ID,
clientSecret: process.env.GITHUB_SECRET,
}),
],
secret: process.env.NEXTAUTH_SECRET,
session: {
strategy: 'jwt',
},
// Additional configuration options...
});
Step 3: Set Up Environment Variables
Create a .env.local
file in your project root and add your OAuth provider credentials:
GOOGLE_CLIENT_ID=your-google-client-id
GOOGLE_CLIENT_SECRET=your-google-client-secret
GITHUB_ID=your-github-client-id
GITHUB_SECRET=your-github-client-secret
NEXTAUTH_SECRET=your-nextauth-secret
NEXTAUTH_URL=http://localhost:3000
Make sure to replace the placeholder values with your actual OAuth credentials.
Creating OAuth Provider Applications
To use OAuth providers, you need to register your application with them. Here's how to register with two popular providers:
Google OAuth Setup
- Go to the Google Cloud Console
- Create a new project or select an existing one
- Navigate to "APIs & Services" > "Credentials"
- Click "Create Credentials" and select "OAuth client ID"
- Configure the OAuth consent screen
- For application type, select "Web application"
- Add authorized JavaScript origins (e.g.,
http://localhost:3000
) - Add authorized redirect URIs (e.g.,
http://localhost:3000/api/auth/callback/google
) - Note your Client ID and Client Secret
GitHub OAuth Setup
- Go to your GitHub Settings
- Click on "OAuth Apps" and then "New OAuth App"
- Fill in your application details
- For the homepage URL, use your application's URL (e.g.,
http://localhost:3000
) - For Authorization callback URL, use
http://localhost:3000/api/auth/callback/github
- Register the application and note your Client ID and Client Secret
Implementing the Authentication UI
Now let's create a simple authentication UI that allows users to sign in with our configured OAuth providers.
Step 1: Create a Sign-In Component
Create a component that will render the sign-in buttons:
// components/SignIn.js
import { signIn, signOut, useSession } from "next-auth/react";
export default function SignIn() {
const { data: session } = useSession();
if (session) {
return (
<div className="auth-container">
<p>Signed in as {session.user.email}</p>
<img src={session.user.image} alt={session.user.name} style={{ borderRadius: '50%', width: '50px' }} />
<button onClick={() => signOut()}>Sign out</button>
</div>
);
}
return (
<div className="auth-container">
<p>You are not signed in</p>
<button onClick={() => signIn('google')}>Sign in with Google</button>
<button onClick={() => signIn('github')}>Sign in with GitHub</button>
</div>
);
}
Step 2: Set Up the Session Provider
Update your _app.js
file to include the SessionProvider:
// pages/_app.js
import { SessionProvider } from 'next-auth/react';
import '../styles/globals.css';
function MyApp({ Component, pageProps: { session, ...pageProps } }) {
return (
<SessionProvider session={session}>
<Component {...pageProps} />
</SessionProvider>
);
}
export default MyApp;
Step 3: Use the Sign-In Component in a Page
Now you can use the SignIn component in any page:
// pages/index.js
import SignIn from '../components/SignIn';
export default function Home() {
return (
<div className="container">
<main>
<h1>Next.js OAuth Example</h1>
<SignIn />
</main>
</div>
);
}
Creating Protected Routes
One common requirement is to protect certain pages so that only authenticated users can access them.
Example of a Protected Page
// pages/profile.js
import { useSession, getSession } from 'next-auth/react';
import { useRouter } from 'next/router';
import { useEffect } from 'react';
export default function Profile() {
const { data: session, status } = useSession();
const router = useRouter();
useEffect(() => {
if (status === "unauthenticated") {
router.push('/');
}
}, [status, router]);
if (status === "loading") {
return <p>Loading...</p>;
}
if (!session) {
return null;
}
return (
<div>
<h1>Protected Profile Page</h1>
<p>Welcome {session.user.name}!</p>
<p>Email: {session.user.email}</p>
</div>
);
}
// Server-side protection
export async function getServerSideProps(context) {
const session = await getSession(context);
if (!session) {
return {
redirect: {
destination: '/',
permanent: false,
},
};
}
return {
props: { session }
};
}
Advanced Configuration
Customizing Callbacks
NextAuth.js allows you to customize the behavior of your authentication process using callbacks:
// pages/api/auth/[...nextauth].js
import NextAuth from 'next-auth';
import GoogleProvider from 'next-auth/providers/google';
export default NextAuth({
providers: [
GoogleProvider({
clientId: process.env.GOOGLE_CLIENT_ID,
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
}),
],
callbacks: {
async jwt({ token, account, user }) {
// Initial sign in
if (account && user) {
return {
...token,
accessToken: account.access_token,
userId: user.id,
};
}
return token;
},
async session({ session, token }) {
// Send properties to the client
session.user.id = token.userId;
session.accessToken = token.accessToken;
return session;
},
},
});
Adding a Database Adapter
NextAuth.js can integrate with various databases to persist user sessions and accounts:
npm install @prisma/client @next-auth/prisma-adapter
# or
yarn add @prisma/client @next-auth/prisma-adapter
Example integration with Prisma:
// pages/api/auth/[...nextauth].js
import NextAuth from 'next-auth';
import GoogleProvider from 'next-auth/providers/google';
import { PrismaAdapter } from '@next-auth/prisma-adapter';
import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient();
export default NextAuth({
adapter: PrismaAdapter(prisma),
providers: [
GoogleProvider({
clientId: process.env.GOOGLE_CLIENT_ID,
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
}),
],
// ... other options
});
Real-World Example: Social Media Dashboard
Let's implement a simple social media dashboard that displays user information and posts after authenticating with OAuth.
// pages/dashboard.js
import { useSession, getSession } from 'next-auth/react';
import { useState, useEffect } from 'react';
import Link from 'next/link';
export default function Dashboard() {
const { data: session } = useSession();
const [posts, setPosts] = useState([]);
const [loading, setLoading] = useState(true);
useEffect(() => {
async function fetchPosts() {
if (session) {
try {
// This would be a real API call in a production app
// const response = await fetch('/api/posts', {
// headers: {
// Authorization: `Bearer ${session.accessToken}`
// }
// });
// const data = await response.json();
// Mock data for demonstration
const mockData = [
{ id: 1, title: 'Getting Started with Next.js', likes: 15 },
{ id: 2, title: 'OAuth Integration Made Easy', likes: 27 },
{ id: 3, title: 'Advanced Authentication Patterns', likes: 8 }
];
setPosts(mockData);
} catch (error) {
console.error('Error fetching posts:', error);
} finally {
setLoading(false);
}
}
}
fetchPosts();
}, [session]);
if (!session) {
return (
<div className="container">
<h1>Access Denied</h1>
<p>You must be signed in to view this page.</p>
<Link href="/">
<a>Go to Home Page</a>
</Link>
</div>
);
}
return (
<div className="dashboard">
<h1>Welcome to Your Dashboard</h1>
<div className="profile">
<img
src={session.user.image}
alt={session.user.name}
className="avatar"
/>
<h2>{session.user.name}</h2>
<p>{session.user.email}</p>
</div>
<div className="posts">
<h3>Your Recent Posts</h3>
{loading ? (
<p>Loading posts...</p>
) : (
<ul>
{posts.map(post => (
<li key={post.id}>
<h4>{post.title}</h4>
<p>Likes: {post.likes}</p>
</li>
))}
</ul>
)}
</div>
<style jsx>{`
.dashboard {
max-width: 800px;
margin: 0 auto;
padding: 20px;
}
.profile {
text-align: center;
margin-bottom: 30px;
}
.avatar {
border-radius: 50%;
width: 100px;
height: 100px;
}
.posts {
border-top: 1px solid #eaeaea;
padding-top: 20px;
}
ul {
list-style: none;
padding: 0;
}
li {
background: #f9f9f9;
border-radius: 5px;
padding: 15px;
margin-bottom: 10px;
}
`}</style>
</div>
);
}
export async function getServerSideProps(context) {
const session = await getSession(context);
if (!session) {
return {
redirect: {
destination: '/',
permanent: false,
},
};
}
return {
props: { session }
};
}
Common Challenges and Solutions
Challenge 1: Managing OAuth Scope
When you need specific permissions from providers:
// pages/api/auth/[...nextauth].js
GitHubProvider({
clientId: process.env.GITHUB_ID,
clientSecret: process.env.GITHUB_SECRET,
scope: "read:user user:email repo" // Request specific permissions
}),
Challenge 2: Handling OAuth Errors
Create a custom error page:
// pages/api/auth/error.js
export default function ErrorPage({ error }) {
return (
<div>
<h1>Authentication Error</h1>
<div>{error}</div>
</div>
);
}
And configure it in NextAuth:
// pages/api/auth/[...nextauth].js
export default NextAuth({
// ... other config
pages: {
error: '/auth/error', // Error code passed in query string as ?error=
},
});
Challenge 3: Testing OAuth Integration
For testing OAuth in development:
// pages/api/auth/[...nextauth].js
import CredentialsProvider from "next-auth/providers/credentials";
// Add this for development testing alongside your OAuth providers
CredentialsProvider({
name: "Test Credentials",
credentials: {
username: { label: "Username", type: "text" },
password: { label: "Password", type: "password" }
},
async authorize(credentials) {
// Only for development - this is not secure for production!
if (credentials.username === "test" && credentials.password === "test") {
return {
id: "1",
name: "Test User",
email: "[email protected]",
image: "https://i.pravatar.cc/[email protected]"
};
}
return null;
}
}),
Summary
In this guide, we've covered:
- Setting up NextAuth.js for OAuth integration
- Configuring OAuth providers (Google and GitHub)
- Creating a sign-in UI component
- Implementing protected routes
- Advanced configuration options
- Building a real-world example application
- Solving common challenges
OAuth integration with Next.js provides a secure, user-friendly authentication solution that leverages trusted third-party providers. By following the steps in this guide, you can implement OAuth authentication in your Next.js applications and give your users a streamlined sign-in experience.
Additional Resources
- NextAuth.js Documentation
- Next.js Authentication Documentation
- OAuth 2.0 Simplified
- Google OAuth Documentation
- GitHub OAuth Documentation
Exercises
- Implement Facebook OAuth integration alongside Google and GitHub.
- Create a user profile page that displays information retrieved from the OAuth provider.
- Add a custom login page with your own styling instead of using the default NextAuth.js UI.
- Implement role-based access control using the information from OAuth providers.
- Set up a database adapter to persist user sessions and account information.
If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)