We use cookies to enhance your experience on the site
CodeWorlds

Connecting providers (Google, GitHub, Facebook)

In previous lessons, we covered the basics of authentication in Next.js applications and various authorization strategies. Now we will focus on the practical implementation of authentication with popular external providers - Google, GitHub, and Facebook. This method is extremely popular because it offers users a fast and convenient login without the need to create a new account and remember another password.

Why add external authentication providers?

Signing in with Google, GitHub, or Facebook accounts brings several important benefits for both users and us - application developers:

  1. Better user experience - faster and more convenient login process, without the need to invent and remember new login credentials
  2. Higher registration rate - fewer abandoned registrations thanks to the simplified process
  3. Greater security - providers like Google or Facebook use advanced account protection mechanisms
  4. Additional information - ability to obtain additional data about the user (with their consent), such as profile picture or contact information
  5. Multi-factor authentication - many providers already offer built-in 2FA/MFA mechanisms

So let's proceed to the practical implementation of login with these popular providers in a Next.js application.

OAuth configuration for providers

Before we start coding, we need to create appropriate OAuth applications at each provider to obtain the necessary keys.

Google OAuth

  1. Go to Google Cloud Console
  2. Create a new project or select an existing one
  3. Go to "APIs & Services" > "Credentials"
  4. Click "Create Credentials" > "OAuth client ID"
  5. Select the application type (Web application)
  6. Add a name and allowed redirect URIs:
    • http://localhost:3000/api/auth/callback/google
      (for the development environment)
    • https://your-domain.com/api/auth/callback/google
      (for production)
  7. Save the Client ID and the Client Secret

GitHub OAuth

  1. Log in to GitHub
  2. Go to Settings > Developer settings > OAuth Apps
  3. Click "New OAuth App"
  4. Fill out the form:
    • Application name: the name of your application
    • Homepage URL: e.g.
      http://localhost:3000
    • Authorization callback URL:
      http://localhost:3000/api/auth/callback/github
  5. Register the application
  6. Generate a new Client Secret and save it together with the Client ID

Facebook OAuth

  1. Go to Facebook Developers
  2. Create a new application (select "For Everything Else" as the type)
  3. In the application panel, add "Facebook Login" and select "Web"
  4. In the settings, add the allowed domains:
    • localhost
      (for the development environment)
    • your-domain.com
      (for production)
  5. In the authentication settings, add the OAuth redirect:
    • http://localhost:3000/api/auth/callback/facebook
  6. Save the App ID and App Secret

Implementing authentication with NextAuth/Auth.js

Now that we have the OAuth keys, we can proceed to implement authentication in our Next.js application. We will use the NextAuth.js library (now also known as Auth.js).

1. Environment variables configuration

First, let’s add our OAuth keys to the

.env.local
file:

1# Basic NextAuth.js configuration
2NEXTAUTH_URL=http://localhost:3000
3NEXTAUTH_SECRET=your_secret_key_for_encryption
4
5# Google Provider
6GOOGLE_CLIENT_ID=your_google_client_id
7GOOGLE_CLIENT_SECRET=your_google_client_secret
8
9# GitHub Provider
10GITHUB_ID=your_github_client_id
11GITHUB_SECRET=your_github_client_secret
12
13# Facebook Provider
14FACEBOOK_CLIENT_ID=your_facebook_app_id
15FACEBOOK_CLIENT_SECRET=your_facebook_app_secret

2. Provider configuration in NextAuth

Now let's create or update the NextAuth.js configuration file:

1// app/api/auth/[...nextauth]/route.ts
2import NextAuth from "next-auth";
3import GoogleProvider from "next-auth/providers/google";
4import GitHubProvider from "next-auth/providers/github";
5import FacebookProvider from "next-auth/providers/facebook";
6import { PrismaAdapter } from "@auth/prisma-adapter";
7import { PrismaClient } from "@prisma/client";
8
9const prisma = new PrismaClient();
10
11export const authOptions = {
12  adapter: PrismaAdapter(prisma),
13  providers: [
14    GoogleProvider({
15      clientId: process.env.GOOGLE_CLIENT_ID!,
16      clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
17      // We can define what profile data we want to receive
18      profile(profile) {
19        return {
20          id: profile.sub,
21          name: profile.name,
22          email: profile.email,
23          image: profile.picture,
24          // We can add additional fields specific to our application
25          emailVerified: profile.email_verified,
26        };
27      },
28    }),
29    GitHubProvider({
30      clientId: process.env.GITHUB_ID!,
31      clientSecret: process.env.GITHUB_SECRET!,
32      profile(profile) {
33        return {
34          id: profile.id.toString(),
35          name: profile.name || profile.login,
36          email: profile.email,
37          image: profile.avatar_url,
38          // Additional field specific to GitHub
39          username: profile.login,
40        };
41      },
42    }),
43    FacebookProvider({
44      clientId: process.env.FACEBOOK_CLIENT_ID!,
45      clientSecret: process.env.FACEBOOK_CLIENT_SECRET!,
46      profile(profile) {
47        return {
48          id: profile.id,
49          name: profile.name,
50          email: profile.email,
51          image: profile.picture.data.url,
52        };
53      },
54    }),
55    // We can also add email/password authentication
56    // for users who prefer traditional login
57  ],
58  callbacks: {
59    async session({ session, user }) {
60      // Add user ID to the session to easily identify the user
61      if (session.user) {
62        session.user.id = user.id;
63      }
64      return session;
65    },
66    async jwt({ token, user, account, profile }) {
67      // If we have a user (e.g. during login), add their ID to the token
68      if (user) {
69        token.uid = user.id;
70      }
71      // We can also add information about the authentication provider
72      if (account) {
73        token.provider = account.provider;
74      }
75      return token;
76    },
77  },
78  pages: {
79    signIn: '/auth/login',  // Custom login page
80    signOut: '/auth/logout', // Custom logout page
81    error: '/auth/error',    // Authentication error page
82  },
83  session: {
84    strategy: 'database',  // We use the database to store sessions
85    maxAge: 30 * 24 * 60 * 60, // 30 days
86  },
87  debug: process.env.NODE_ENV === 'development',
88};
89
90const handler = NextAuth(authOptions);
91export { handler as GET, handler as POST };

3. Creating a user interface for login

Now let's create a friendly login interface that will offer all configured authentication methods:

1// app/auth/login/page.tsx
2'use client';
3
4import { signIn, useSession } from 'next-auth/react';
5import { useRouter, useSearchParams } from 'next/navigation';
6import { useEffect, useState } from 'react';
7
8export default function LoginPage() {
9  const router = useRouter();
10  const searchParams = useSearchParams();
11  const callbackUrl = searchParams.get('callbackUrl') || '/dashboard';
12  const { status } = useSession();
13  
14  const [email, setEmail] = useState('');
15  const [password, setPassword] = useState('');
16  const [isLoading, setIsLoading] = useState(false);
17  const [error, setError] = useState('');
18  
19  // If the user is already logged in, redirect them
20  useEffect(() => {
21    if (status === 'authenticated') {
22      router.push(callbackUrl);
23    }
24  }, [status, router, callbackUrl]);
25  
26  // Email/password login handling (if configured)
27  const handleEmailLogin = async (e: React.FormEvent) => {
28    e.preventDefault();
29    setIsLoading(true);
30    setError('');
31    
32    try {
33      const result = await signIn('credentials', {
34        redirect: false,
35        email,
36        password,
37      });
38      
39      if (result?.error) {
40        setError('Invalid email or password');
41      } else {
42        router.push(callbackUrl);
43      }
44    } catch (error) {
45      setError('An error occurred during login');
46    } finally {
47      setIsLoading(false);
48    }
49  };
50  
51  if (status === 'loading') {
52    return (
53      <div className="flex justify-center items-center min-h-screen">
54        <div className="animate-spin rounded-full h-12 w-12 border-t-2 border-b-2 border-blue-500" />
55      </div>
56    );
57  }
58  
59  return (
60    <div className="max-w-md mx-auto mt-10 p-6 bg-white rounded-lg shadow-lg">
61      <h1 className="text-2xl font-bold mb-6 text-center">Log in to the application</h1>
62      
63      {error && (
64        <div className="mb-4 p-3 bg-red-100 text-red-700 rounded">
65          {error}
66        </div>
67      )}
68      
69      <div className="space-y-4 mb-6">
70        <button
71          onClick={() => signIn('google', { callbackUrl })}
72          className="w-full flex items-center justify-center gap-3 p-3 border border-gray-300 rounded-md hover:bg-gray-50 transition-colors"
73          disabled={isLoading}
74        >
75          <svg viewBox="0 0 24 24" width="20" height="20">
76            {/* Logo Google */}
77            <path
78              d="M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92c-.26 1.37-1.04 2.53-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.09z"
79              fill="#4285F4"
80            />
81            <path
82              d="M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z"
83              fill="#34A853"
84            />
85            <path
86              d="M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22.81-.62z"
87              fill="#FBBC05"
88            />
89            <path
90              d="M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z"
91              fill="#EA4335"
92            />
93          </svg>
94          Continue with Google
95        </button>
96        
97        <button
98          onClick={() => signIn('github', { callbackUrl })}
99          className="w-full flex items-center justify-center gap-3 p-3 border border-gray-300 rounded-md hover:bg-gray-50 transition-colors"
100          disabled={isLoading}
101        >
102          <svg viewBox="0 0 24 24" width="20" height="20" fill="currentColor">
103            {/* Logo GitHub */}
104            <path d="M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12" />
105          </svg>
106          Continue with GitHub
107        </button>
108        
109        <button
110          onClick={() => signIn('facebook', { callbackUrl })}
111          className="w-full flex items-center justify-center gap-3 p-3 border border-gray-300 rounded-md hover:bg-gray-50 transition-colors"
112          disabled={isLoading}
113        >
114          <svg viewBox="0 0 24 24" width="20" height="20" fill="#1877F2">
115            {/* Logo Facebook */}
116            <path d="M24 12.073c0-6.627-5.373-12-12-12s-12 5.373-12 12c0 5.99 4.388 10.954 10.125 11.854v-8.385H7.078v-3.47h3.047V9.43c0-3.007 1.792-4.669 4.533-4.669 1.312 0 2.686.235 2.686.235v2.953H15.83c-1.491 0-1.956.925-1.956 1.874v2.25h3.328l-.532 3.47h-2.796v8.385C19.612 23.027 24 18.062 24 12.073z" />
117          </svg>
118          Continue with Facebook
119        </button>
120      </div>
121      
122      {/* Optional: email/password login form */}
123      <div className="relative my-6">
124        <div className="absolute inset-0 flex items-center">
125          <div className="w-full border-t border-gray-300"></div>
126        </div>
127        <div className="relative flex justify-center text-sm">
128          <span className="px-2 bg-white text-gray-500">
129            or use email
130          </span>
131        </div>
132      </div>
133      
134      <form onSubmit={handleEmailLogin} className="space-y-4">
135        <div>
136          <label htmlFor="email" className="block text-sm font-medium mb-1">
137            Email
138          </label>
139          <input
140            id="email"
141            type="email"
142            value={email}
143            onChange={(e) => setEmail(e.target.value)}
144            className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
145            required
146          />
147        </div>
148        
149        <div>
150          <label htmlFor="password" className="block text-sm font-medium mb-1">
151            Password
152          </label>
153          <input
154            id="password"
155            type="password"
156            value={password}
157            onChange={(e) => setPassword(e.target.value)}
158            className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
159            required
160          />
161        </div>
162        
163        <button
164          type="submit"
165          className="w-full bg-blue-600 text-white py-2 px-4 rounded-md hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 transition-colors"
166          disabled={isLoading}
167        >
168          {isLoading ? 'Logging in...' : 'Log in'}
169        </button>
170      </form>
171      
172      <div className="mt-6 text-center">
173        <p className="text-sm text-gray-600">
174          Don't have an account yet?{' '}
175          <a href="/auth/register" className="text-blue-600 hover:underline">
176            Sign up
177          </a>
178        </p>
179      </div>
180    </div>
181  );
182}

4. Preparing the Prisma database schema

If we are using the Prisma adapter, we must provide an appropriate database schema:

1// prisma/schema.prisma
2datasource db {
3  provider = "postgresql" // or another database provider
4  url      = env("DATABASE_URL")
5}
6
7generator client {
8  provider = "prisma-client-js"
9}
10
11model Account {
12  id                String  @id @default(cuid())
13  userId            String
14  type              String
15  provider          String
16  providerAccountId String
17  refresh_token     String? @db.Text
18  access_token      String? @db.Text
19  expires_at        Int?
20  token_type        String?
21  scope             String?
22  id_token          String? @db.Text
23  session_state     String?
24
25  user User @relation(fields: [userId], references: [id], onDelete: Cascade)
26
27  @@unique([provider, providerAccountId])
28}
29
30model Session {
31  id           String   @id @default(cuid())
32  sessionToken String   @unique
33  userId       String
34  expires      DateTime
35  user         User     @relation(fields: [userId], references: [id], onDelete: Cascade)
36}
37
38model User {
39  id            String    @id @default(cuid())
40  name          String?
41  email         String?   @unique
42  emailVerified DateTime?
43  image         String?
44  accounts      Account[]
45  sessions      Session[]
46  // Additional fields specific to your application
47  username      String?   @unique
48  role          String?   @default("user")
49  createdAt     DateTime  @default(now())
50  updatedAt     DateTime  @updatedAt
51}
52
53model VerificationToken {
54  identifier String
55  token      String   @unique
56  expires    DateTime
57
58  @@unique([identifier, token])
59}

5. Creating a SessionProvider for the entire application

For authentication to work properly across the entire application, let's add

SessionProvider
in the main layout:

1// app/providers.tsx
2'use client';
3
4import { SessionProvider } from "next-auth/react";
5import { ReactNode } from "react";
6
7export function Providers({ children }: { children: ReactNode }) {
8  return <SessionProvider>{children}</SessionProvider>;
9}
10
11// app/layout.tsx
12import { Providers } from "./providers";
13import "./globals.css";
14
15export const metadata = {
16  title: "My Application",
17  description: "Description of my application",
18};
19
20export default function RootLayout({
21  children,
22}: {
23  children: React.ReactNode;
24}) {
25  return (
26    <html lang="pl">
27      <body>
28        <Providers>{children}</Providers>
29      </body>
30    </html>
31  );
32}

6. Adding a navigation component with authentication state support

Now let's create a navigation component that will show different options depending on whether the user is logged in:

1// components/Navigation.tsx
2'use client';
3
4import { signOut, useSession } from 'next-auth/react';
5import Link from 'next/link';
6import Image from 'next/image';
7
8export default function Navigation() {
9  const { data: session, status } = useSession();
10  const isLoading = status === 'loading';
11  
12  return (
13    <nav className="bg-white shadow-sm">
14      <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
15        <div className="flex justify-between h-16">
16          <div className="flex items-center">
17            <Link href="/" className="flex-shrink-0 flex items-center">
18              <span className="text-xl font-bold">MojaAplikacja</span>
19            </Link>
20            <div className="hidden sm:ml-6 sm:flex sm:space-x-8">
21              <Link 
22                href="/" 
23                className="border-transparent text-gray-500 hover:border-gray-300 hover:text-gray-700 inline-flex items-center px-1 pt-1 border-b-2 text-sm font-medium"
24              >
25                Home page
26              </Link>
27              <Link 
28                href="/features" 
29                className="border-transparent text-gray-500 hover:border-gray-300 hover:text-gray-700 inline-flex items-center px-1 pt-1 border-b-2 text-sm font-medium"
30              >
31                Features
32              </Link>
33              <Link 
34                href="/pricing" 
35                className="border-transparent text-gray-500 hover:border-gray-300 hover:text-gray-700 inline-flex items-center px-1 pt-1 border-b-2 text-sm font-medium"
36              >
37                Pricing
38              </Link>
39            </div>
40          </div>
41          
42          <div className="flex items-center">
43            {isLoading ? (
44              <div className="h-8 w-8 animate-pulse rounded-full bg-gray-200" />
45            ) : session ? (
46              <div className="relative ml-3">
47                <div className="flex items-center space-x-4">
48                  <Link 
49                    href="/dashboard" 
50                    className="text-sm font-medium text-gray-700 hover:text-gray-800"
51                  >
52                    Dashboard
53                  </Link>
54                  
55                  <div className="flex items-center space-x-2">
56                    {session.user?.image ? (
57                      <Image
58                        className="h-8 w-8 rounded-full"
59                        src={session.user.image}
60                        alt={session.user.name || "Avatar"}
61                        width={32}
62                        height={32}
63                      />
64                    ) : (
65                      <div className="h-8 w-8 rounded-full bg-blue-600 flex items-center justify-center text-white text-sm">
66                        {session.user?.name?.charAt(0) || "U"}
67                      </div>
68                    )}
69                    
70                    <span className="text-sm font-medium text-gray-700">
71                      {session.user?.name || session.user?.email?.split('@')[0]}
72                    </span>
73                  </div>
74                  
75                  <button
76                    onClick={() => signOut({ callbackUrl: '/' })}
77                    className="text-sm font-medium text-gray-700 hover:text-gray-800"
78                  >
79                    Log out
80                  </button>
81                </div>
82              </div>
83            ) : (
84              <div className="space-x-4">
85                <Link 
86                  href="/auth/login" 
87                  className="text-gray-700 hover:text-gray-900 font-medium"
88                >
89                  Log in
90                </Link>
91                <Link 
92                  href="/auth/register" 
93                  className="bg-blue-600 text-white py-2 px-4 rounded-md hover:bg-blue-700 font-medium"
94                >
95                  Sign up
96                </Link>
97              </div>
98            )}
99          </div>
100        </div>
101      </div>
102    </nav>
103  );
104}

7. Creating a Dashboard page accessible only to logged-in users

Let's create a Dashboard page that will be accessible only to logged-in users:

1// app/dashboard/page.tsx
2import { getServerSession } from "next-auth/next";
3import { redirect } from "next/navigation";
4import { authOptions } from "../api/auth/[...nextauth]/route";
5
6export default async function DashboardPage() {
7  const session = await getServerSession(authOptions);
8  
9  if (!session) {
10    redirect("/auth/login?callbackUrl=/dashboard");
11  }
12  
13  return (
14    <div className="max-w-7xl mx-auto py-6 sm:px-6 lg:px-8">
15      <div className="px-4 py-6 sm:px-0">
16        <h1 className="text-2xl font-bold mb-4">Dashboard</h1>
17        
18        <div className="bg-white shadow overflow-hidden sm:rounded-lg">
19          <div className="px-4 py-5 sm:px-6">
20            <h3 className="text-lg leading-6 font-medium text-gray-900">
21              Profile information
22            </h3>
23            <p className="mt-1 max-w-2xl text-sm text-gray-500">
24              Your account details and settings.
25            </p>
26          </div>
27          <div className="border-t border-gray-200">
28            <dl>
29              <div className="bg-gray-50 px-4 py-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6">
30                <dt className="text-sm font-medium text-gray-500">Full name</dt>
31                <dd className="mt-1 text-sm text-gray-900 sm:mt-0 sm:col-span-2">
32                  {session.user?.name || "Not provided"}
33                </dd>
34              </div>
35              <div className="bg-white px-4 py-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6">
36                <dt className="text-sm font-medium text-gray-500">Address email</dt>
37                <dd className="mt-1 text-sm text-gray-900 sm:mt-0 sm:col-span-2">
38                  {session.user?.email}
39                </dd>
40              </div>
41              <div className="bg-gray-50 px-4 py-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6">
42                <dt className="text-sm font-medium text-gray-500">Login method</dt>
43                <dd className="mt-1 text-sm text-gray-900 sm:mt-0 sm:col-span-2">
44                  {/* This field requires extending the token with provider information */}
45                  External authentication provider
46                </dd>
47              </div>
48            </dl>
49          </div>
50        </div>
51      </div>
52    </div>
53  );
54}

8. Creating middleware for path protection

Let's secure specific paths in our application using Next.js middleware:

1// middleware.ts
2import { NextResponse } from "next/server";
3import type { NextRequest } from "next/server";
4import { getToken } from "next-auth/jwt";
5
6export async function middleware(request: NextRequest) {
7  const token = await getToken({ req: request });
8  
9  const isAuthPage = request.nextUrl.pathname.startsWith('/auth');
10  
11  if (isAuthPage) {
12    if (token) {
13      return NextResponse.redirect(new URL('/dashboard', request.url));
14    }
15    return NextResponse.next();
16  }
17  
18  if (!token) {
19    const url = new URL('/auth/login', request.url);
20    url.searchParams.set('callbackUrl', request.nextUrl.pathname);
21    return NextResponse.redirect(url);
22  }
23  
24  return NextResponse.next();
25}
26
27// Specify paths for which the middleware should apply
28export const config = {
29  matcher: [
30    '/dashboard/:path*', 
31    '/profile/:path*', 
32    '/settings/:path*',
33    '/auth/:path*'
34  ],
35};

Advanced configuration

Modifying user profile data

We can modify profile data from external providers before saving them to our database:

1GoogleProvider({
2  clientId: process.env.GOOGLE_CLIENT_ID!,
3  clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
4  profile(profile) {
5    // Transforming profile data
6    return {
7      id: profile.sub,
8      name: profile.name,
9      email: profile.email,
10      image: profile.picture,
11      // Additional fields specific to the application
12      role: profile.email.endsWith('@yourcompany.com') ? 'admin' : 'user',
13      emailVerified: profile.email_verified ? new Date() : null,
14    };
15  },
16});

Linking accounts from different providers

NextAuth.js automatically handles linking accounts from different providers if they use the same email address. If we want to customize this, we can modify the function

linkAccount
in the callbacks:

1callbacks: {
2  // ...other callbacks
3  async linkAccount({ user, account, profile }) {
4    // Here we can perform custom account linking logic
5    console.log(`Linking account: ${account.provider} for user ${user.id}`);
6    return true;
7  },
8}

Adding custom logic to the authentication process

We can add additional steps in the authentication process using the callback

signIn
:

1callbacks: {
2  // ...other callbacks
3  async signIn({ user, account, profile, email, credentials }) {
4    // Check if the user can log in
5    // For example: check if the email is in the company domain
6    const allowedDomains = ['yourcompany.com', 'partner.com'];
7    const emailDomain = user.email?.split('@')[1];
8    
9    if (emailDomain && !allowedDomains.includes(emailDomain)) {
10      return false; // Deny access
11    }
12    
13    // We can also update user data on each login
14    if (user.id) {
15      await prisma.user.update({
16        where: { id: user.id },
17        data: { lastLogin: new Date() }
18      });
19    }
20    
21    return true; // Allow access
22  },
23}

Error handling authentication

Let's create a custom authentication error page:

1// app/auth/error/page.tsx
2'use client';
3
4import { useSearchParams } from "next/navigation";
5import Link from "next/link";
6
7export default function AuthErrorPage() {
8  const searchParams = useSearchParams();
9  const error = searchParams.get('error');
10  
11  let errorMessage = "An error occurred during authentication attempt.";
12  let errorDescription = "Please try again or contact administration.";
13  
14  // Map error codes to user-friendly messages
15  switch (error) {
16    case "Configuration":
17      errorMessage = "A server configuration error occurred.";
18      errorDescription = "Contact the system administrator.";
19      break;
20    case "AccessDenied":
21      errorMessage = "Access denied.";
22      errorDescription = "You do not have sufficient permissions to sign in.";
23      break;
24    case "Verification":
25      errorMessage = "The verification link has expired or is invalid.";
26      errorDescription = "Try sending the verification link again.";
27      break;
28    case "OAuthSignin":
29    case "OAuthCallback":
30    case "OAuthCreateAccount":
31    case "EmailCreateAccount":
32    case "Callback":
33      errorMessage = "An error occurred during communication with the authentication provider.";
34      errorDescription = "Try again in a moment or choose a different login method.";
35      break;
36    case "OAuthAccountNotLinked":
37      errorMessage = "An account with this email address already exists.";
38      errorDescription = "Log in using the method you used previously.";
39      break;
40    case "EmailSignin":
41      errorMessage = "Unable to send email with login link.";
42      errorDescription = "Check that the provided email address is correct.";
43      break;
44    case "CredentialsSignin":
45      errorMessage = "Invalid login credentials.";
46      errorDescription = "Check that the provided email and password are correct.";
47      break;
48    case "SessionRequired":
49      errorMessage = "Login required.";
50      errorDescription = "Log in, to access this page.";
51      break;
52    default:
53      // Default message already set
54      break;
55  }
56  
57  return (
58    <div className="min-h-screen flex items-center justify-center bg-gray-50 py-12 px-4 sm:px-6 lg:px-8">
59      <div className="max-w-md w-full space-y-8">
60        <div>
61          <h2 className="mt-6 text-center text-3xl font-extrabold text-gray-900">
62            {errorMessage}
63          </h2>
64          <p className="mt-2 text-center text-sm text-gray-600">
65            {errorDescription}
66          </p>
67        </div>
68        <div className="flex flex-col items-center space-y-4">
69          <Link 
70            href="/auth/login"
71            className="group relative w-full flex justify-center py-2 px-4 border border-transparent text-sm font-medium rounded-md text-white bg-blue-600 hover:bg-blue-700 focus:outline-none"
72          >
73            Back to login page
74          </Link>
75          <Link 
76            href="/"
77            className="text-sm text-blue-600 hover:text-blue-500"
78          >
79            Back to home page
80          </Link>
81        </div>
82      </div>
83    </div>
84  );
85}

Summary

In this module, we discussed in detail how to implement authentication with popular external providers (Google, GitHub, Facebook) in a Next.js application. We learned:

  1. The application registration process at OAuth providers and obtaining keys
  2. Configuration of NextAuth.js / Auth.js with different providers
  3. Creating a user interface for login and registration
  4. Securing routes using middleware
  5. Customizing the authentication process to our needs
  6. Error handling authentication

Thanks to this implementation, our Next.js application gains a professional, multi-platform authentication system that improves the user experience while strengthening the security of our application.

In the next module, we will deal in detail with user sessions and token management, which will allow us to better understand how authentication mechanisms work after user login.

Go to CodeWorlds