Skip to content

3. NextAuth

Apa itu NextAuth

Alt text

Dokumentasi : https://next-auth.js.org/

Kenapa Menggunakan NextAuth?

Implementasi NextAuth

Instalasi NextAuth

terminal
npm install next-auth
package.json
{
  "name": "frontend-nextjs",
  "version": "0.1.0",
  "private": true,
  "scripts": {
    "dev": "next dev -p 3010",
    "build": "next build",
    "start": "next start",
    "lint": "next lint"
  },
  "dependencies": {
    "@heroicons/react": "^2.0.18",
    "@react-spring/web": "^9.7.3",
    "@tanstack/react-query": "^4.33.0",
    "@tanstack/react-query-devtools": "^4.33.0",
    "@types/node": "20.4.5",
    "@types/react": "18.2.17",
    "@types/react-dom": "18.2.7",
    "autoprefixer": "10.4.14",
    "axios": "^1.5.0",
    "clsx": "^2.0.0",
    "eslint": "8.46.0",
    "eslint-config-next": "13.4.12",
    "formik": "^2.4.3",
    "next": "13.4.12",
    "next-auth": "^4.23.1",
    "postcss": "8.4.27",
    "react": "18.2.0",
    "react-dom": "18.2.0",
    "react-spinners": "^0.13.8",
    "sweetalert2": "^11.7.27",
    "tailwindcss": "3.3.3",
    "typescript": "5.1.6",
    "yup": "^1.2.0"
  }
}

Membuat API Route

Alt text

pages/api/auth/[...nextauth].ts
import NextAuth, { NextAuthOptions } from "next-auth";
import CredentialsProvider from "next-auth/providers/credentials";


const authOptions: NextAuthOptions = {

  providers: [
    // ...add more providers here
    CredentialsProvider({
      type: "credentials",
      credentials: {},
      authorize(credentials: any, req) {
        return {
          ...credentials,
        };
      },
    }),
  ],




};

export default NextAuth(authOptions);

Membuat Komponen NextAuthProvider

Alt text

components/NextAuthProvider.tsx
"use client";
import { SessionProvider } from "next-auth/react";

import React, { ReactNode } from "react";
import { Session } from "next-auth";

interface NextAuthProps {
  children: ReactNode;
  session: Session | null | undefined;
}

const NextAuthProvider: React.FC<NextAuthProps> = ({ children, session }) => {
  return <SessionProvider session={session}>{children}</SessionProvider>;
};

export default NextAuthProvider;

Impelentasi NextAuthProvider pada RootLayout

app/layout.tsx
import "./globals.css";
import React, { ReactNode } from "react";
import type { Metadata } from "next";
import ReactQuery from "../components/ReactQuery";
import NextAuthProvider from "@/components/NextAuthProvider";
import { Session } from "next-auth";
interface NextAuthProps {
  children: ReactNode;
  session: Session | null | undefined;
}

export const metadata: Metadata = {
  title: "Create Next App",
  description: "Generated by create next app",
};

export default function RootLayout({ children, session }: NextAuthProps) {
  return (
    <html lang="en">
      <body className={"px-5 w-screen h-screen overflow-hidden"}>
        <NextAuthProvider session={session}>
          <ReactQuery>{children}</ReactQuery>
        </NextAuthProvider>
      </body>
    </html>
  );
}

Lihat Session pada Komponen Login

app/auth/login/page/.tsx
"use client";

import { useFormik, Form, FormikProvider, getIn } from "formik";

import * as yup from "yup";
import { LoginPayload } from "../interface";
import InputText from "@/components/InputText";
import Label from "@/components/Label";
import Button from "@/components/Button";
import useAuthModule from "../lib";
import Link from "next/link";
import { useSession } from "next-auth/react";


export const registerSchema = yup.object().shape({
  email: yup
    .string()
    .nullable()
    .default("")
    .email("Gunakan format email")
    .required("Wajib isi"),
  password: yup
    .string()
    .nullable()
    .default("")
    .required("Wajib isi")
    .min(8, "Minimal 8 karakater"),
});

const Login = () => {
  const { data: session, status } = useSession();
  console.log('session', session)
  console.log('status', status)
  const { useLogin } = useAuthModule();
  const { mutate, isLoading } = useLogin();
  const formik = useFormik<LoginPayload>({
    initialValues: registerSchema.getDefault(),
    validationSchema: registerSchema,
    enableReinitialize: true,
    onSubmit: (payload) => {
      mutate(payload);
    },
  });
  const { handleChange, handleSubmit, handleBlur, values, errors } = formik;

  return (
    <section>
      <div className="flex items-center justify-center w-full">
        <h1 className="text-3xl text-blue-400">Login</h1>
      </div>
      <FormikProvider value={formik}>
        <Form className="space-y-5" onSubmit={handleSubmit}>
          <section>
            <Label htmlFor="email" title="Email" />
            <InputText
              value={values.email}
              placeholder="exampel@email.com"
              id="email"
              name="email"
              onChange={handleChange}
              onBlur={handleBlur}
              isError={getIn(errors, "email")}
              messageError={getIn(errors, "email")}
            />
          </section>
          <section>
            <Label htmlFor="password" title="Password" />

            <InputText
              value={values.password}
              placeholder="**********"
              id="password"
              name="password"
              type="password"
              onChange={handleChange}
              onBlur={handleBlur}
              isError={getIn(errors, "password")}
              messageError={getIn(errors, "password")}
            />
          </section>
          <section>
            <Button
              height="lg"
              title="Login"
              colorSchema="blue"
              isLoading={isLoading}
              isDisabled={isLoading}
            />
            <Link href={"register"}>
              <Button title="Halaman Register" colorSchema="green" />
            </Link>
          </section>
        </Form>
      </FormikProvider>
    </section>
  );
};

export default Login;

Alt text

Perbaharui next.config.ts

next.config.ts
/** @type {import('next').NextConfig} */
const nextConfig = {
  reactStrictMode: true,
  experimental: {
    appDir: true,
  },
};

module.exports = nextConfig;

Alt text

Membuat Konfigurasi global

Alt text

.env
NEXTAUTH_URL=http://localhost:3010
NEXTAUTH_SECRET=dankanfklgnakgnakn

Menyimpan Access Token dan Refresh Token di NextAuth session

Alt text

app/auth/lib/index.ts
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import { useToast } from "@/hook";
import {
  LoginPayload,
  LoginResponse,
  RegisterPayload,
  RegisterResponse,
} from "../interface";
import { axiosClient } from "@/lib/axiosClient";
import { signIn } from "next-auth/react";
import { useRouter } from "next/navigation";

const useAuthModule = () => {
  const { toastError, toastSuccess, toastWarning } = useToast();
  const router = useRouter();

  ...

  const login = async (payload: LoginPayload): Promise<LoginResponse> => {
    return axiosClient.post("/auth/login", payload).then((res) => res.data);
  };

  const useLogin = () => {
    const { mutate, isLoading } = useMutation(
      (payload: LoginPayload) => login(payload),
      {
        onSuccess: async (response) => {
          toastSuccess(response.message);
          await signIn("credentials", {
            id: response.data.id,
            name: response.data.nama,
            email: response.data.email,
            accessToken: response.data.access_token,
            refreshToken: response.data.refresh_token,
            redirect: false,
          });

          router.push("/admin");
        },
        onError: (error: any) => {
          if (error.response.status == 422) {
            toastWarning(error.response.data.message);
          } else {
            toastError();
          }
        },
      }
    );
    return { mutate, isLoading };
  };

  return { useRegister, useLogin };
};

export default useAuthModule;
pages/api/auth/[...nextauth].ts
import NextAuth, { NextAuthOptions } from "next-auth";
import CredentialsProvider from "next-auth/providers/credentials";


const authOptions: NextAuthOptions = {
  secret: process.env.NEXTAUTH_SECRET,
  providers: [
    CredentialsProvider({
      type: "credentials",
      credentials: {},
      authorize(credentials: any, req) {
        return {
          ...credentials,
        };
      },
    }),
  ],

  callbacks: {
    async jwt({ token, user, account, trigger, session }) {
      if (trigger === "update") {
        return { ...token, ...session.user };
      }

      return {
        ...token,
        ...user,
      };
    },
    async session({ session, user, token }) {
      session.user.id = Number(token.id);
      session.user.name = token.name;
      session.user.email = token.email;
      session.user.accessToken = token.accessToken;
      session.user.refreshToken = token.refreshToken;

      return session;
    },
  },

  pages: {
    signIn: "/auth/login",
    signOut: "/auth/login",
    error: "/auth/error",
  },
};

export default NextAuth(authOptions);

Mendefinisikan custom Type session

Alt text

types/next-auth.d.ts
import { Session } from "next-auth";

declare module "next-auth" {
  interface Session {
    user: {
      id: number | undefined | null;
      email: string | undefined | null;
      name: string | undefined | null;
      accessToken: any;
      refreshToken: any;
      token : any


    };


  }
}

Merubah komponen halaman admin

app/admin/page.tsx
"use client";
import React from "react";
import { useSession, signOut } from "next-auth/react";
import Button from "@/components/Button";

const Page = () => {
  const { data: session, status } = useSession();
  return (
    <div>
      Admin
      {JSON.stringify(session)}
      {status}
      <Button
        title="Logout"
        colorSchema="red"
        onClick={() => {
          signOut();
        }}
      />
    </div>
  );
};

export default Page;

Jalankan pada Browser

Alt text

Implementasi Middleware

Apa itu Midddleware?

Kenapa Menggunakan Midddleware?

Alt text

Alt text

Membuat Middleware

Alt text

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

export default withAuth(
  // `withAuth` augments your `Request` with the user's token.
  function middleware(req) {
    console.log("token", req.nextauth.token);
  },
  {
    callbacks: {
      authorized: ({ token }) => {
        if (token) return true;
        return false;
      },
    },
    pages: {
      signIn: "/auth/login",
      error: '/api/auth/error',
    },
  }
);

export const config = { matcher: ["/admin", "/admin/:path*"] };

Alt text

Alt text