Skip to main content

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:

bash
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:

javascript
// 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​

  1. Go to the Google Cloud Console
  2. Create a new project or select an existing one
  3. Navigate to "APIs & Services" > "Credentials"
  4. Click "Create Credentials" and select "OAuth client ID"
  5. Configure the OAuth consent screen
  6. For application type, select "Web application"
  7. Add authorized JavaScript origins (e.g., http://localhost:3000)
  8. Add authorized redirect URIs (e.g., http://localhost:3000/api/auth/callback/google)
  9. Note your Client ID and Client Secret

GitHub OAuth Setup​

  1. Go to your GitHub Settings
  2. Click on "OAuth Apps" and then "New OAuth App"
  3. Fill in your application details
  4. For the homepage URL, use your application's URL (e.g., http://localhost:3000)
  5. For Authorization callback URL, use http://localhost:3000/api/auth/callback/github
  6. 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:

jsx
// 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:

jsx
// 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:

jsx
// 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​

jsx
// 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:

javascript
// 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:

bash
npm install @prisma/client @next-auth/prisma-adapter
# or
yarn add @prisma/client @next-auth/prisma-adapter

Example integration with Prisma:

javascript
// 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.

jsx
// 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:

javascript
// 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:

jsx
// 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:

javascript
// 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:

javascript
// 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: "test@example.com",
image: "https://i.pravatar.cc/150?u=test@example.com"
};
}
return null;
}
}),

Summary​

In this guide, we've covered:

  1. Setting up NextAuth.js for OAuth integration
  2. Configuring OAuth providers (Google and GitHub)
  3. Creating a sign-in UI component
  4. Implementing protected routes
  5. Advanced configuration options
  6. Building a real-world example application
  7. 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​

Exercises​

  1. Implement Facebook OAuth integration alongside Google and GitHub.
  2. Create a user profile page that displays information retrieved from the OAuth provider.
  3. Add a custom login page with your own styling instead of using the default NextAuth.js UI.
  4. Implement role-based access control using the information from OAuth providers.
  5. 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 feedback@compilenrun.com. I’d greatly appreciate your feedback! :)