Introduction
Authentication is a critical part of web applications, and external authentication services like Google, GitHub, and Facebook make it easier to implement secure login mechanisms. In this guide, we will go step by step to integrate external authentication in a Next.js application using TypeScript and NextAuth.js.
Before moving to the tutorial, let’s dive into what NextAuth is.
What is NextAuth
NextAuth.js is a complete open source authentication solution for Next.js applications. It is designed from the ground up to support Next.js and Serverless.
Before we start, ensure you have the following installed:
- Node.js
- Next.js (latest version)
- TypeScript
Project Setup
First, create a Next.js project if you haven’t already:
npx create-next-app@latest my-auth-app
We will use TypeScript in this project, so choose TypeScript from the options.
For better production and understanding purpose, we will not be using /src folder in our structure.
Install NextAuth
After setting up your project, install next next-auth JS through npm
npm install next-auth
Before creating components and writing code, let’s understand the structure of nextAuth.
Setup env variable
Add the nextauth_secret to the environment variable.
NEXTAUTH_SECRET=nextauth
NextAuth Structure
- next-auth.d.ts is a file in the root directory that defines all the data types for using nextAuth.
- nextAuthOptions - This contains all the options (for instance, consider it as an array of options) that is needed for performing the authentication part. Can also refer to the Documentation.
- […nextauth] - It is a special directory that contains route.ts to maintain and inject auth options.
- middleware - Acts as a bridge between the frontend and authentication service.
next-auth.d.ts
In the root directory, create a file named next-auth.d.ts
import { DefaultSession } from "next-auth";
declare module "next-auth"{
interface Session{
user:{
id: string;
} & DefaultSession["user"];
}
}
We will use session as our default datatype to capture all the information.
Now we will define options for our nextAuth
nextAuthOptions
In the root directory, create a folder util. Inside that, create a file auth.ts (name of file can be anything).
Inside util/auth.ts paste this code.
Authentication Providers in NextAuth.js are services that can be used to sign in a user. There are many ways a user can be signed in, we are using Credentials as the provider.
Next, we will be adding callback option, in the above file, after adding provider,s add another option named callbacks. File should look like this-
import { NextAuthOptions } from "next-auth";
import CredentialsProvider from "next-auth/providers/credentials";
import bcrypt from "bcryptjs";
export const authOptions: NextAuthOptions = {
providers: [
CredentialsProvider( {
name: "Credentials",
credentials: {
email: {label:"Email",type:"text"},
password: {label: "Password", type: "password"}
},
async authorize(credentials){
if(!credentials?.email || !credentials?.password){
throw new Error("Missing Email or Password");
}
try {
return {
email: credentials.email
}
} catch (error) {
throw error
}
}
}),
],
callbacks: {
async jwt({token,user}){
if(user){
token.id = user.id;
}
return token
},
async session({session,token}){
if(session.user){
session.user.id = token.id as string
}
return session
}
},
}
Jwt is used to generate the token of the user and store it in the session.
We have used two methods in callbacks-
- jwt to tokenize the user credential
- session
Next option is session, modify the code after callbacks-
import { NextAuthOptions } from "next-auth";
import CredentialsProvider from "next-auth/providers/credentials";
import bcrypt from "bcryptjs";
export const authOptions: NextAuthOptions = {
providers: [
CredentialsProvider( {
name: "Credentials",
credentials: {
email: {label:"Email",type:"text"},
password: {label: "Password", type: "password"}
},
async authorize(credentials){
if(!credentials?.email || !credentials?.password){
throw new Error("Missing Email or Password");
}
try {
return {
email: credentials.email
}
} catch (error) {
throw error
}
}
}),
],
callbacks: {
async jwt({token,user}){
if(user){
token.id = user.id;
}
return token
},
async session({session,token}){
if(session.user){
session.user.id = token.id as string
}
return session
}
},
session: {
strategy: "jwt",
maxAge: 30 * 24 * 60 * 60
},
}
In session we use Strategy to define where to store and use our sessions.
Two types of strategies are there-
- Database
- jwt The most common and easy way is to use jwt strategy. maxAge is defined for the time-limit of this session.
Final step is to add the pages, secret option.
import { NextAuthOptions } from "next-auth";
import CredentialsProvider from "next-auth/providers/credentials";
import bcrypt from "bcryptjs";
export const authOptions: NextAuthOptions = {
providers: [
CredentialsProvider( {
name: "Credentials",
credentials: {
email: {label:"Email",type:"text"},
password: {label: "Password", type: "password"}
},
async authorize(credentials){
if(!credentials?.email || !credentials?.password){
throw new Error("Missing Email or Password");
}
try {
return {
email: credentials.email
}
} catch (error) {
throw error
}
}
}),
],
callbacks: {
async jwt({token,user}){
if(user){
token.id = user.id;
}
return token
},
async session({session,token}){
if(session.user){
session.user.id = token.id as string
}
return session
}
},
session: {
strategy: "jwt",
maxAge: 30 * 24 * 60 * 60
},
pages:{
signIn: "/login",
error: "/login"
},
secret: process.env.NEXTAUTH_SECRET
}
Setting […nextauth]
In the app folder, create api/auth folder. Inside auth create another directory […nextauth].
Remember to name it the same way. Create a file route.ts to handle the routes for the nextAuth operations .
import { authOptions } from "@/util/auth";
import NextAuth from "next-auth";
const handler = NextAuth(authOptions);
export {handler as GET, handler as POST};
This way our nextAuth will handle and bundle the Get and Post requests.
Final step is to setup the middleware.
Middleware
In the root directory, create a file middleware.ts and paste the following code
import withAuth from "next-auth/middleware";
import { NextResponse } from "next/server";
export default withAuth(
function middleware(){
return NextResponse.next()
},
{
callbacks: {
authorized: ({token,req}) =>{
const {pathname} = req.nextUrl;
//allow auth related routes
if(
pathname.startsWith("/api/auth") ||
pathname === "/login" ||
pathname === "/register"
){
return true
}
//public
if(pathname ==="/" || pathname.startsWith("/api/video")){
return true
}
return !!token
}
}
}
)
export const config = {
matcher: ["/((?!_next/static|_next/image|favicon.ico|public/).*)"]
}
The best method that works efficiently with nextjs is withAuth().
This function takes parameters to define where to use and block our paths for nextAuth.
Callback is used to ensure the paths mentioned are not allowed to be accessed openly by any user without authentication.
Conclusion
This guide provides a step-by-step tutorial for integrating external authentication in a Next.js application using TypeScript and NextAuth.js. You'll learn how to set up a Next.js project without the /src folder, install NextAuth.js, and configure authentication providers and callbacks. The guide also covers setting up middleware and routing for secure authentication, ensuring only authorized users can access specific paths.
Thanks a lot developers 😄, I hope this article was helpful to you.
Please support me by subscribing to my newsletter for such tutorials and tip on Development.
Top comments (0)