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.
Signing in with Google, GitHub, or Facebook accounts brings several important benefits for both users and us - application developers:
So let's proceed to the practical implementation of login with these popular providers in a Next.js application.
Before we start coding, we need to create appropriate OAuth applications at each provider to obtain the necessary keys.
http://localhost:3000/api/auth/callback/google (for the development environment)https://your-domain.com/api/auth/callback/google (for production)http://localhost:3000http://localhost:3000/api/auth/callback/githublocalhost (for the development environment)your-domain.com (for production)http://localhost:3000/api/auth/callback/facebookNow 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).
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_secretNow 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 };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}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}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}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}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}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};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});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}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}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}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:
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.