Skip to content

Instantly share code, notes, and snippets.

@omer-os
Last active April 9, 2024 14:48
Show Gist options
  • Save omer-os/36b493ce6e7db6fe3b2b50d6b5ac96b6 to your computer and use it in GitHub Desktop.
Save omer-os/36b493ce6e7db6fe3b2b50d6b5ac96b6 to your computer and use it in GitHub Desktop.
rule based authentication with nextjs, typescript, next-auth, prisma and postgressql db hosted in railway

Role-Based Authentication in Next.js with Prisma and Next-Auth

A step-by-step guide to setting up role-based authentication in a Next.js project using Prisma and Next-Auth.

Step 1: Setting Up Your Next.js Application

pnpm create next-app ./

Step 2: Setting Up the UI Components with Shadcn

  1. Install the latest version of Shadcn UI:

    npx shadcn-ui@latest init
  2. Add all the components of Shadcn:

    npx shadcn-ui@latest add

Step 3: Configuring the Database with Prisma

  1. Go to Railway Dashboard, click "Create Database" and create a new PostgreSQL database.

  2. Configure Prisma with the following commands:

    npx prisma init
  3. Add .env to .gitignore and configure the DATABASE_URL in the .env file as follows:

    DATABASE_URL="your_database_url_here"
  4. Create a Prisma client as prisma/prismaClient.ts with the following content:

    import { PrismaClient } from "@prisma/client";
    
    const prismaClientSingleton = () => {
      return new PrismaClient();
    };
    
    type PrismaClientSingleton = ReturnType<typeof prismaClientSingleton>;
    
    const globalForPrisma = globalThis as unknown as {
      prisma: PrismaClientSingleton | undefined;
    };
    
    const MyPrismaClient = globalForPrisma.prisma ?? prismaClientSingleton();
    
    export default MyPrismaClient;
    
    if (process.env.NODE_ENV !== "production")
      globalForPrisma.prisma = MyPrismaClient;
  5. Generate the Prisma client:

    npx prisma generate

Step 4: Setting Up Next-Auth

  1. Install the necessary packages:

    pnpm add next-auth @auth/prisma-adapter
  2. In _app.tsx, set up the SessionProvider:

    import { SessionProvider } from "next-auth/react";
    import "@/styles/globals.css";
    import type { AppProps } from "next/app";
    
    export default function App({
      Component,
      pageProps: { session, ...pageProps },
    }: AppProps) {
      return (
        <SessionProvider session={session}>
          <Component {...pageProps} />
        </SessionProvider>
      );
    }
  3. Install Prisma client:

    pnpm add @prisma/client

Step 5: Configuring Next-Auth API Route

Create and configure the API route pages/api/auth/[...nextauth].ts with the following content:

// imports
import MyPrismaClient from "@/prisma/prismaClient";
import NextAuth, { AuthOptions } from "next-auth";
import GoogleProvider from "next-auth/providers/google";
import { PrismaAdapter } from "@auth/prisma-adapter";

export const authOptions: AuthOptions = {
  // adding prisma adapter
  adapter: PrismaAdapter(MyPrismaClient),

  providers: [
    // we have the google auth privider for now
    GoogleProvider({
      clientId: process.env.GOOGLE_CLIENT_ID as string,
      clientSecret: process.env.GOOGLE_CLIENT_SECRET as string,
    }),
  ],

  callbacks: {
    // jwt is the token that we get from the provider this will run only once when the user logs in
    async jwt({ token, user }) {
      // here we are getting the user from database
      const dbUser = await MyPrismaClient.user.findFirst({
        where: {
          email: token.email,
        },
      });

      if (!dbUser) {
        token.id = user!.id;
        return token;
      }

      // jwt token returning the user data with the role
      return {
        id: dbUser.id,
        name: dbUser.name,
        role: dbUser.role,
        email: dbUser.email,
        picture: dbUser.image,
      };
    },

    // session is the session object that we get from the jwt callback, we can get session data client side using useSession hook
    async session({ session, token }: any) {
      if (token) {
        session.user.id = token.id;
        session.user.name = token.name;
        session.user.email = token.email;
        session.user.picture = token.picture;
        session.user.role = token.role;
      }

      // we returned all the user data
      return session;
    },
  },

  // this is the secret that we use to encrypt the jwt token
  secret: process.env.NEXTAUTH_SECRET,

  session: {
    strategy: "jwt",
  },
};

export default NextAuth(authOptions);

Step 6: Setting Up Prisma Schema

Define your Prisma schema with the following models:

model Account {
  id                 String  @id @default(cuid())
  userId             String
  type               String
  provider           String
  providerAccountId  String
  refresh_token      String?  @db.Text
  access_token       String?  @db.Text
  expires_at         Int?
  token_type         String?
  scope              String?
  id_token           String?  @db.Text
  session_state      String?

  user User @relation(fields: [userId], references: [id], onDelete: Cascade)

  @@unique([provider, providerAccountId])
}

model Session {
  id           String   @id @default(cuid())
  sessionToken String   @unique
  userId       String
  expires      DateTime
  user         User     @relation(fields: [userId], references: [id], onDelete: Cascade)
}

model User {
  id            String    @id @default(cuid())
  name          String?
  email         String?   @unique
  emailVerified DateTime?
  image         String?
  accounts      Account[]
  sessions      Session[]

  role         Role      @default(USER)
}

model VerificationToken {
  identifier String
  token      String   @unique
  expires    DateTime

  @@unique([identifier, token])
}

enum Role {
  ADMIN
  USER
}

Step 7: Configuring Environment Variables

last thing lets generate the enviorment variables needed for the next-auth and google auth provider:

we will get these now:

  • GOOGLE_CLIENT_ID =
  • GOOGLE_CLIENT_SECRET =
  • NEXTAUTH_SECRET =

first setup OAuth 2.0 in GCP:

1- go to google cloud platform 2- From the projects list in top left navbar, select a project or create a new one and select it 3- open right side bar and go to APIs & services then Enabled APIs & services 4- go to oauth consent screen and fill all the required stuff, just leave the optional ones we dont need them 5- then open credentials page and click create credentials button and select oauth client id

6- in Authorized JavaScript origins add this URL: http://localhost:3000 7- in Authorized redirect URIs add this: http://localhost:3000/api/auth/callback/google

8- click create, it will give u Client ID and Client secret copy paste them to your env file like this:

GOOGLE_CLIENT_ID      = "client id here"
GOOGLE_CLIENT_SECRET  = "client seceret here"
NEXTAUTH_SECRET       = "put-random-thing-here"

create middleware.ts file in root of your project:

import { withAuth } from "next-auth/middleware";

// this middleware will be used to protect routes that require authentication
export default withAuth({
  callbacks: {
    // this callback will run when the user logs in, if this callback returns false the user will be redirected to login page else user will be allowed to access the page
    authorized: ({ token }: any) => {
      // checking if the user has role of admin
      if (token && token.role === "ADMIN") {
        return true;
      } else {
        return false;
      }
    },
  },

  secret: process.env.NEXTAUTH_SECRET,
});

// this config is used to select which routed you want to protect, or use this middleware, in our case we want to protect all the routes thats under /admin route
export const config = { matcher: ["/admin/:path*"] };
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment