7.Authentication in NextJS with server actions

Ayush RajputAyush Rajput
3 min read
  1. Installations neccessary dependecies

// create next app
npx create-next-app@latest next-auth

//install shadcn
npx shadcn@latest init

//other
npm i bcryptjs jsonwebtoken 
npx shadcn@latest add input // optional for styling
npx shadcn@latest add label
npm i mongoose // for database connection
  1. SignUp

//src/actions/auth.js
'use server'

import dbConnect from "@/database"
import User from "@/models";
import bcryptjs from "bcryptjs"

export  async function SignUp(formData){

    await dbConnect();
    try{
        const {name , email , password} = formData;

        const existingUser = await User.findOne({email:email});
        if(existingUser){
            return {
                success:false,
                message:"User already exist ! Please Login"
            }
        }
        // hash the password
        const salt =await bcryptjs.genSalt(10);
        const hasedPassword= await bcryptjs.hash(password,salt);

        const newUser = await User.create({  //model:extracted data
            name:name,
            email:email,
            password:hasedPassword 
        })

        return {
            success:true,
            message:"Account created successfully",
            data: JSON.parse(JSON.stringify(newUser))
        }
    }
    catch(err){
        console.log(err)
        return {
            success:false,
            message:"Internal server error"
        }
    }
}

Now Call this action from frontend form action just like previous article

  1. Login

     export async function Login(formData){
         await dbConnect();
         try{
             const{email , password}= formData;
             const existingUser = await User.findOne({email:email});
             if(!existingUser){
                 return {
                     success:false,
                     message:"User not exist, Please Signup"
                 }
             }
    
             //check password
             const checkPassword = await bcryptjs.compare(password, existingUser.password);
             if(!checkPassword){
                 return {
                     success:false,
                     message:"Invalid Credetials"
                 }
             }
    
             // create token if password is correct
             const payload = {
                 id:existingUser._id,
                 userName:existingUser.name,
                 email:existingUser.email
             }
             const token = jwt.sign(payload , 'JWT_SECRET_KEY' , {expiresIn:'1d'});
    
             //store this token
             const getCookies = cookies()
             getCookies.set('token',token)
    
             return {
                 success:true,
                 message:"Login successful"
             }  
    
         }
         catch(err){
             console.log(err)
             return {
                 success:false,
                 message:"Internal Server Error"
             }
         }
     }
    

    4. If we wany user data- name and email so we look for the token as our user data present in token payload , decode the token from cookies(or pass it to the login response and store it in localStorage but here we store in cookies)

// action -> now ftech the user info from any page in any server component too (we cant use localstorage in server component)
export async function fetchAuthUserData(){
    await dbConnect()
    try{
        const getCookies = cookies();
        const token = getCookies.get("token")?.value || "";
        if(token==""){
            return {
                success:false,
                message:"Token missing"
            }
        }

        const decodeToken = jwt.verify(token , "JWT_SECRET_KEY") // it give us payload that is stored in payload of token
        const getUserInfo = await User.findOne({_id:decodeToken.id});

        if(getUserInfo){
            return {
                success:true,
                data:JSON.parse(JSON.stringify(getUserInfo)) // contain user name and email and id 
            }
        }
        else{
            return{
                success:false,
                message:"Token is invalid"
            }
        }
    }
    catch(err){
        return {
            success:false,
            message:"Internal Server error , Please try again later"
        }
    }
}

// frontend
import { fetchAuthUserData } from "@/actions/auth";
import Image from "next/image";
export default async function Home() {
  const currentUser = await fetchAuthUserData() // using current user we decdie what to show on home page login and signup buttons
   if(!currentUser?.success) redirect('/login') // we cant access home page if token is not availablle
  return (
    <div className="w-11/12 mx-auto mt-5 font-bold text-3xl">
      Authentication in NextJS
      <p>{currentUser?.data?.name}</p>
      <p>{currentUser?.data?.email}</p>
      <LogOut/>
    </div>
  );
}
  1. If token is present then we cant allowed to access the openRoutes - sign-up , login page ; To make open Route we use concept of middleware in nextJS

     //src/middleware.js
     import { cookies } from "next/headers";
     import { NextResponse } from "next/server";
    
     export function middleware(request){
         const path = request.nextUrl.pathname;
         const checkOpenRoute = path ==="/sign-up" || path==="/login";
         const getCookies = cookies();
         const token = getCookies.get("token")?.value || "";
    
         if(checkOpenRoute && token !== ""){ // if user trying to access open/pulbic route when he is authenticated
             return NextResponse.redirect(new URL('/', request.nextUrl));
    
         }
    
         if(!checkOpenRoute && token ===""){ // if user is not authneicated but he trying to access private route 
             return NextResponse.redirect(new URL('/login',request.nextUrl))
         }
     }
    
     export const config={
         matcher:['/sign-up' , '/login']
     }
    
    1. Implement LogOut Functionality

       // logout in action
       export async function LogOutAction(){
           const getCookies = cookies()
           getCookies.set("token","") // make token as empty
       }
      
       // frontend
       'use client'
       import { Button } from "../ui/button";
       import { LogOutAction } from "@/actions/auth";
       export default function LogOut(){
           const handleLogOut = async()=>{
               await LogOutAction()
           }
           return (
               <Button onClick={handleLogOut}>LogOut</Button>
           )
       }
      
0
Subscribe to my newsletter

Read articles from Ayush Rajput directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Ayush Rajput
Ayush Rajput