2. Supabaseの認証機能を使わない認証機能(成功)

NextJS

パッケージのインストール

npm install next-auth
npm install next-auth bcrypt   <--- パスワードをハッシュ化するため

pages/api/auth/[…nextauth].js

import NextAuth from 'next-auth';  <--- NextAuth.jsの主要なライブラリで認証方法が色々あります
import CredentialsProvider from 'next-auth/providers/credentials'; <--- メールアドレスとパスワードによる認証の場合はこのライブラリをインポートします
import bcrypt from 'bcrypt';  <--- パスワードをハッシュ化するためのライブラリ

// 仮のユーザーデータ(本番はDBを使います)
// bcrypt.hashSync()でハッシュ化して安全に保存します
const users = [
    { id: "1", email: "sample@gmail.com", password: bcrypt.hashSync("password123", 10) },
];


export default NextAuth({
    providers: [  <--- 認証プロバイダの設定をします
        CredentialsProvider({
            name: "Email and Password",  <--- プロバイダの名前
            credentials: {               <--- 認証する情報
                email: { label: "Email", type: "email" },
                password: { label: "Password", type: "password" },
            },
            async authorize(credentials) {   <--- 認証処理を行う関数
                const user = users.find((u) => u.email === credentials?.email);
                if (user && bcrypt.compareSync(credentials.password, user.password)) {
                    return { id: user.id, email: user.email }  <--- 2つのパスワードをcompareして
                }                                               --- 同じ(Sync)ならユーザー情報をreturn
                throw new Error("Invalid email or password");
            },
        }),
    ],
    pages: {  <--- 認証ページを設定します
        signIn: "/auth/signin",
    },
    secret: process.env.NEXTAUTH_SECRET   <--- セッションを暗号化するための秘密鍵(.env.localで定義)
});

※[…nextauth]の部分は動的に変わります

※jsファイルでなくtsファイルとした場合、import bcrypt from ‘bcrypt’に対して「Could not find a declaration file for module ‘bcrypt’」というエラーが出ます

.env.local

NEXTAUTH_SECRET=mysecretkey   <--- この文字列は適当でも動きます
NEXTAUTH_URL=http://localhost:3000

pages/auth/signin.tsx

import { signIn } from "next-auth/react";
import { useState } from "react";

export default function SingIn() {
    const [email, setEmail] = useState("");
    const [password, setPassword] = useState("");
    const [error, setError] = useState("");

    const handleSubmit = async (e: React.FormEvent) => {
        e.preventDefault();
        const result = await signIn("credentials", { email, password, redirect: false });
        if (result?.error) {
            setError("ログインに失敗しました");
        } else {
            window.location.href = "/";  <--- ログイン成功時にホームへリダイレクト
        }
    };

    return(
        <div>
            <h2>LOGIN</h2>
            <p>{error}</p>
            <form onSubmit={handleSubmit}>
                <input type="email" placeholder="Email" value={email} onChange={(e) => setEmail(e.target.value)} required />
                <input type="password" placeholder="Password" value={password} onChange={(e) => setPassword(e.target.value)} required />
                <button type="submit">LOGIN</button>
            </form>
        </div>
    );
}

ナビゲーションバーにログイン状態を表示する

import { signIn, signOut, useSession } from "next-auth/react";

export default function Navbar() {
    const { data: session } = useSession(); <--- ※1
    return (
        <nav>
            {session ? (
                <>
                    <p>ログイン中: {session.user?.email}</p>
                    <button onClick={() => signOut()}>ログアウト</button>
                </>
            ) : (
                <button onClick={() => signIn()}>ログイン</button>  <--- ※2
            )}
        </nav>
    );
}

※1 … この行をexport defaultの外で書くと「TypeError: Cannot read properties of null (reading ‘useContext’)」というエラーが出ます

※2 … buttonタグではなく<Link href=”/auth/signIn”>SIGN IN</Link>にした場合、クリックすると「Error: This action with HTTP GET is not supported by NextAuth.js」というエラーが出ます。NextAuth.jsのサインイン処理はセキュリティ上の理由からonClickイベントハンドラで行う必要があります

page/_app.tsx

import { SessionProvider } from "next-auth/react";
import type { AppProps } from "next/app";

export default function App({ Component, pageProps }: AppProps) {
    return (
        <SessionProvider sessino={pageProps.session}>   <--- useSessionを使うためには
            <Component {...pageProps} />                 --- このタグでラップする必要があります
        </SessionProvider>
    );
}

pages/dashboard.tsx(認証しないと中身が見れないページの例)

import { useSession } from "next-auth/react";
import { useRouter } from "next/router";
import { useEffect } from "react";

export default function Dashboard() {
  const { data: session, status } = useSession();
  const router = useRouter();

  useEffect(() => {
    if (status === "unauthenticated") {
      router.push("/auth/signin");
    }
  }, [status, router]);

  if (status === "loading") return <p>Loading...</p>;
  if (!session) return null;

  return <h1>ダッシュボード(ログイン済み)</h1>;
}
BACK