Skip to content

認証

認証の追加

この章は複雑なので、こちらのチュートリアルを見てもらいたい。

認証: ユーザーが本人であることの確認

認可: ユーザーがアプリを使用できるかどうかの許可

  1. ログインフォームを作成し、ログインに必要な情報をformに渡す。
    ここではemailpassword

  2. NextAuth.jsの設定 NextAuth.js

    # NextAuth.jsのインストール
    pnpm i next-auth@beta
    
    # アプリケーションの秘密鍵の生成
    openssl rand -base64 32
    

    .envファイルにAUTH_SECRETを追加

    AUTH_SECRET=your-secret-key
    

  3. プロジェクトルートに/auth.config.tsを作成。

    import type { NextAuthConfig } from 'next-auth';
    
    export const authConfig = {
    pages: {
        signIn: '/login',
    },
    } satisfies NextAuthConfig;
    

  4. ミドルウェアでルートを保護
    これにより、ユーザーはログインしていない限りダッシュボード ページにアクセスできなくなります。

    import type { NextAuthConfig } from 'next-auth';
    
    export const authConfig = {
    pages: {
        signIn: '/login',
    },
    callbacks: {
        authorized({ auth, request: { nextUrl } }) {
        const isLoggedIn = !!auth?.user;
        const isOnDashboard = nextUrl.pathname.startsWith('/dashboard');
        if (isOnDashboard) {
            if (isLoggedIn) return true;
            return false; // Redirect unauthenticated users to login page
        } else if (isLoggedIn) {
            return Response.redirect(new URL('/dashboard', nextUrl));
        }
        return true;
        },
    },
    providers: [], // Add providers with an empty array for now
    } satisfies NextAuthConfig;
    

  5. ミドルウェア作成

    import NextAuth from 'next-auth';
    import { authConfig } from './auth.config';
    
    export default NextAuth(authConfig).auth;
    
    export const config = {
    // https://nextjs.org/docs/app/building-your-application/routing/middleware#matcher
    matcher: ['/((?!api|_next/static|_next/image|.*\\.png$).*)'],
    };
    

  6. auth.tsを作成

    import NextAuth from 'next-auth';
    import Credentials from 'next-auth/providers/credentials';
    import { authConfig } from './auth.config';
    import { z } from 'zod';
    import { sql } from '@vercel/postgres';
    import type { User } from '@/app/lib/definitions';
    import bcrypt from 'bcrypt';
    
    async function getUser(email: string): Promise<User | undefined> {
    try {
    // DBからユーザーをemailで探す
        const user = await sql<User>`SELECT * FROM users WHERE email=${email}`;
        return user.rows[0];
    } catch (error) {
        console.error('Failed to fetch user:', error);
        throw new Error('Failed to fetch user.');
    }
    }
    
    export const { auth, signIn, signOut } = NextAuth({
    ...authConfig,
    providers: [
        Credentials({
        async authorize(credentials) {
      // Zodでフォームをバリデーション
            const parsedCredentials = z
            .object({ email: z.string().email(), password: z.string().min(6) })
            .safeParse(credentials);
    
      // バリデーションを通過したらgetUser
            if (parsedCredentials.success) {
        const { email, password } = parsedCredentials.data;
        const user = await getUser(email);
        const passwordsMatch = await bcrypt.compare(password, user.password);
    
        if (passwordsMatch) return user;
        }
    
          console.log('Invalid credentials');
          return null;
      }),
    ],
    });
    
  7. actions.tsにメソッド追加

'use server';

import { signIn } from '@/auth';
import { AuthError } from 'next-auth';

// ...

export async function authenticate(
  prevState: string | undefined,
  formData: FormData,
) {
  try {
    await signIn('credentials', formData);
  } catch (error) {
    if (error instanceof AuthError) {
      switch (error.type) {
        case 'CredentialsSignin':
          return 'Invalid credentials.';
        default:
          return 'Something went wrong.';
      }
    }
    throw error;
  }
}
  1. formに設定
import { authenticate } from '@/app/lib/actions';

export default function LoginForm() {
const [errorMessage, formAction, isPending] = useActionState(
  authenticate,
  undefined,
);

return (
  <form action={formAction} className="space-y-3">
  //...
)
  1. ログアウト機能

    import { signOut } from '@/auth';
    
    return (
      <form
            action={async () => {
              'use server';
              await signOut();
            }}
          >
      //...
    )