- Create Register page UI
- Install prisma
npm i -D prisma - install prisma client
npm i @prisma/client - Go to
database.connection.tsand add following code
import { PrismaClient } from "@prisma/client";
declare global {
var prisma: PrismaClient | undefined;
}
export const db = globalThis.prisma || new PrismaClient();
if (process.env.NODE_ENV !== "production") globalThis.prisma = db
-
Run command
npx prisma init -
We used
Neon DBas our database -
Got to
Neon DBto create a new database -
Paste connection string in
schema.prismaand.envfile -
Start creating schema in
schema.prismafile -
With help of
dbindatabase.connection.tsfile we can access our models -
Create
Usermodel inschema.prismafile -
Should also run following command to access
Usermodel indatabase.connection.tsfilenpx prisma generate -
To psuh your schema to database
npx prisma db push -
Move to
Auth JS site -
Select database adapter as
Prisma -
Install
@auth/prisma-adapter -
Comand to install
@auth/prisma-adapternpm i @auth/prisma-adapter -
Copy model
Userand paste inschema.prismafile -
Copy model
Accountand paste inschema.prismafile ( We are not using session model fromAuth JS site) -
Push again to database
npx prisma generateandnpx prisma db push -
Auth does not use
passwordfield inUsermodel. So we need to add it to user model as optional because google aut h providers do not require password inschema.prismafile. -
Add the code below to validate fields in
register.ts
"use server";
import { RegisterSchema } from "@/schema";
import * as z from "zod";
export const register = async (values: z.infer<typeof RegisterSchema>) => {
const validatedFields = RegisterSchema.safeParse(values); // safeParse returns a ZodResult object, and it is used to validate the input values
if (!validatedFields.success) {
return { error: "Invalid fields!" };
}
return { success: "Email sent!" };
};
- Install
bcryptto hash passwordnpm i bcryptandnpm i -D @types/bcrypt - Create register function
export const register = async (values: z.infer<typeof RegisterSchema>) => {
// * check and store user in database
const validatedFields = RegisterSchema.safeParse(values); // safeParse returns a ZodResult object, and it is used to validate the input values
if (!validatedFields.success) {
return { error: "Invalid fields!" };
}
const { email, password, name } = validatedFields.data;
const hashedPassword = await bcrypt.hash(password, 10); // 10 is the number of salt rounds
//finding the email in database
const exisitingUser = await db.user.findUnique({
where: {
email,
},
});
// if user already exists, return error
if (exisitingUser) {
return { error: "Email already exists!" };
}
// if not, create and save it in database
await db.user.create({
data: {
name,
email,
password: hashedPassword,
},
});
// TODO: send verification email
return { success: "Email sent!" };
};
- Create user actions in
user.action.tsfile, to get user by email and id
export const getUserByEmail = async (email: string) => {
try {
const user = await db.user.findUnique({ where: { email } });
return user;
} catch {
return null;
}
};
export const getUserById = async (id: string) => {
try {
const user = await db.user.findUnique({ where: { id } });
return user;
} catch {
return null;
}
};
- Use it in
register.tsfunction
//finding the email in database
const exisitingUser = await getUserByEmail(email)
- Now, for
loginwe have to install nextauth v5npm i next-auth@beta - Create
auth.tsfile in root directory for configuration - Add following code to
auth.tsfile- paste the code from website
- Create
app/api/auth/[...nextauth].tsfile and paste the code from the wesbite
- remove the edge because prisma does not support it
- Add
AUTH_SECRETin.envfile
- for the development mode we can use any string
- Go to see logs
http://localhost:3000/api/auth/providers
- Create middleware in
middleware.tsin root directory
middleware.tsfile is nextjs specific file- update matcher to
matcher: ["/((?!.+\\.[\\w]+$|_next).*)", "/", "/(api|trpc)(.*)"],from clerk
- Create
auth.config.tsfile in root directory
- paste the code from website
- Update
auth.tsfile
// * Comnfiguration for authentication
import NextAuth from "next-auth";
import authConfig from "@/auth.config";
import { db } from "./lib/database.connection";
import { PrismaAdapter } from "@auth/prisma-adapter";
export const {
handlers: { GET, POST },
auth,
} = NextAuth({
adapter: PrismaAdapter(db), // prisma adapter is supported on non edge
session: { strategy: "jwt" },
...authConfig,
});
- Update
api/auth/[...nextauth].tsfile
// * middleware works on the edge
import authConfig from "./auth.config";
import NextAuth from "next-auth";
const { auth } = NextAuth(authConfig);
export default auth((req) => {
// req.auth
});
// Optionally, don't invoke Middleware on some paths
export const config = {
matcher: ["/((?!.+\\.[\\w]+$|_next).*)", "/", "/(api|trpc)(.*)"],
};
- Create
route.tsfile in root directory
- this file will contain all types of routes
-
Edit middleware.ts file
- add routes condition in
middleware.tsfile
- add routes condition in
-
Do valdiations in
auth.config.tsfile -
Edit
auth.tsfile
export const {
handlers: { GET, POST },
auth,
signIn,
signOut,
} = NextAuth({
adapter: PrismaAdapter(db), // prisma adapter is supported on non edge
session: { strategy: "jwt" },
...authConfig,
});
- Implement functionality in
login.tsfile
"use server"; // necessary in every auth action
import * as z from "zod";
import { LoginSchema } from "@/schema";
import { signIn } from "@/auth";
import { DEFAULT_LOGIN_REDIRECT } from "@/route";
import { AuthError } from "next-auth";
export const Login = async (values: z.infer<typeof LoginSchema>) => {
const validatedFields = LoginSchema.safeParse(values); // valdiating the input values
if (!validatedFields.success) {
return { error: "Invalid fields! " };
}
const { email, password } = validatedFields.data;
try {
await signIn("credentials", {
email,
password,
redirectTo: DEFAULT_LOGIN_REDIRECT,
});
} catch (error) {
if (error instanceof AuthError) {
switch (error.type) {
case "CredentialsSignin":
return { error: "Invalid credentials!" };
default:
return { error: "Something went wrong!" };
}
}
throw error;
}
};
- Go to settings page and add logout button, and its functionality in
settings.tsxfile
import { auth, signOut } from '@/auth'
import React from 'react'
const Settings = async () => {
const user = await auth()
return (
<div>
{JSON.stringify(user)}
<form action={async () => {
'use server'
await signOut()
}}>
<button type='submit'>
Singout
</button>
</form>
</div>
)
}
export default Settings
-
add callbacks in
auth.ts- to check for tokensexport const { handlers: { GET, POST }, auth, signIn, signOut, } = NextAuth({ callbacks: { async jwt({ token }) { console.log({ token }); return token; }, }, adapter: PrismaAdapter(db), // prisma adapter is supported on non edge session: { strategy: "jwt" }, ...authConfig, }); -
update callback in
auth.tsfile
async session({ token, session }) {
if (token.sub && session.user) {
session.user.id = token.sub;
}
return session;
},
- Update schema in
schema.prismafile
role UserRole @default(USER)
- Close the server
- run command
npx prisma generateand thennpx prisma migrate resetand thennpx prisma db push - you can check the db status, users would be 0
- run command
- Update the callback in
auth.tsfile
async session({ token, session }) {
console.log({
sessionToken: token,
});
if (token.sub && session.user) {
session.user.id = token.sub;
}
return session;
},
async jwt({ token }) {
// fecthing the user
if (!token.sub) return token;
const exisitingUser = await getUserById(token.sub);
if (!exisitingUser) return token;
token.role = exisitingUser.role;
return token;
},
- Update session function
async session({ token, session }) {
if (token.sub && session.user) {
session.user.id = token.sub;
}
if(token.role && session.user){
session.user.role = token.role // you will be seeing here a typescript error
}
return session;
},
- Update in
auth.tsfile
if (token.role && session.user) {
session.user.role = token.role as UserRole;
}
- and create a
next-auth.d.tsfile in root directory - and paste the code
import { UserRole } from "@prisma/client";
import NextAuth from "next-auth";
export type ExtendedUser = DefaultSession["user"] & {
role: UserRole; // this is the type of role
};
declare module "next-auth" {
interface Session {
user: ExtendedUser;
}
}