send-backwardBackend

The Importance of Backend in App Development

The backend is the backbone of any application, and it plays a crucial role in determining the overall quality of your app. If the backend isn't well-structured, the app will struggle to perform, no matter how good the frontend looks. Building a solid backend can be complex and challenging at times, but by the end of this documentation, you'll realize it's not as difficult as it seems when you use the right technologies and approaches.

I’ll also provide tips on best practices and recommend secure authentication providers for login and signup functionality. Let’s dive deep into the process of building a reliable and efficient backend.

  1. AUTHENTICATION with NextAuth

The Importance of Authentication in Application Security

Authentication is a critical component of any application. If your authentication system is not securely implemented, your app is at a higher risk of being compromised.

In addition to authentication, securing your routes is equally important. For instance, you wouldn't want everyone to access your admin route, which contains sensitive information and functionalities. To protect these routes, we implement middleware that ensures only authorized users can access specific areas of the application.

Setup for nextAuth: app/api/auth/[...nextauth]/route.js:


import NextAuth from "next-auth";

import User from "@/models/User";
import { connectDB } from "@/db/connectDB";
import bcrypt from "bcryptjs";
import GithubProvider from "next-auth/providers/github"
import GoogleProvider from "next-auth/providers/google";
import CredentialsProvider from "next-auth/providers/credentials";
import { setCookie } from "cookies-next";
const handler = NextAuth({
  providers: [
    // OAuth authentication providers...
    GithubProvider({
      clientId: process.env.GITHUB_ID,
      clientSecret: process.env.GITHUB_SECRET,
    }),
    GoogleProvider({
      clientId: process.env.GOOGLE_ID,
      clientSecret: process.env.GOOGLE_SECRET,
      authorization: {
        params: {
          prompt: "consent",
          access_type: "offline",
          response_type: "code"
        }
      }
    }),
    CredentialsProvider({
      name: "Credentials",
      id: "credentials",
      credentials: {
        email: { label: "Email", type: "text", placeholder: "jsmith" },
        password: { label: "Password", type: "password" },
      },
      async authorize(credentials) {
        await connectDB();
        const userFound = await User.findOne({
          email: credentials.email,
        }).select("+password");

        if (!userFound) throw new Error("Invalid Email or Password");

        const passwordMatch = await bcrypt.compare(credentials.password, userFound.password
        );

        if (!passwordMatch) throw new Error("Invalid Email or Password");
        return userFound;
      },
    })

  ],
  pages: {
    signIn: "/login",
  },
  session: {
    strategy: "jwt",
  },
  callbacks: {/**
 * Authorizes a user based on provided credentials.
 *
 * @param {Object} credentials - The user's credentials.
 * @param {string} credentials.email - The user's email address.
 * @param {string} credentials.password - The user's password.
 * @returns {Object} The authenticated user object.
 * @throws {Error} If the email or password is invalid.
 */
async authorize(credentials) {
  // Establish a connection to the database
  await connectDB();

  // Find the user in the database by email and include the password field
  const userFound = await User.findOne({
    email: credentials.email,
  }).select("+password");

  // If no user is found, throw an error indicating invalid email or password
  if (!userFound) throw new Error("Invalid Email or Password");

  // Compare the provided password with the stored hashed password
  const passwordMatch = await bcrypt.compare(credentials.password, userFound.password);

  // If the passwords do not match, throw an error indicating invalid email or password
  if (!passwordMatch) throw new Error("Invalid Email or Password");

  // Return the authenticated user object
  return userFound;
},
    async signIn({ user, account, profile }) {
      // console.log("profile", profile)
      // console.log("user", user)
      // console.log("acount", account)
      await connectDB();
      let currentUser = await User.findOne({ email: user.email })
      if (!currentUser) {
        await User.create({ name: user.name, email: user.email, photo: user.image })
      }

      return true // Do different verification for other providers that don't have `email_verified`
    },
    async signOut() {
      return true
    },
    async jwt({ token, account, profile, user }) {
      // Persist the OAuth access_token and or the user id to the token right after signin
      if (user) {
        const u = user;
        const tokens = {
          ...token,
          id: u.id,
        }
        // console.log("tokens is",tokens)
      }
      return token
    },
    async session({ session, token, user }) {

      console.log('i am cookies')
      // setCookie('session-token', token, { req, res, maxAge: 60 * 60 * 24 });
      // token=session.token;
      // console.log("token is:-", token)
      const currentUser = await User.findOne({ email: session.user.email })
      return {
        ...session,
        currentUser,
        token

      }
    }
  },
  secret: process.env.JWT_SECRET
})

export { handler as GET, handler as POST }
  1. install next-auth package

  1. import all provider which you want to include in your app for login and signup

/Middleware.js

  • This middleware insure that no one can access account route without login if user try to access this route without login then user redirect to login page

  • Admin Route: Middleware insure that admin route can be access only yashitkr@gmail.com if user login with this id then he can access admin route other wise he redirect to login page

Last updated