Invalid Date
Next.js App RouterでFacebookログインを実装する
本記事では、Next.jsのApp Routerを使用したアプリケーションに、next-auth (Auth.js) を利用してFacebookログイン機能を実装する手順を解説します。また、実装中に遭遇する可能性のある一般的な問題とその解決策についても触れます。
導入
ユーザー認証は多くのWebアプリケーションにとって不可欠な機能です。ソーシャルログインは、ユーザーが新しいアカウントを作成する手間を省き、既存のソーシャルメディアアカウントで簡単にサインインできるようにするため、ユーザーエクスペリエンスを向上させます。ここでは、Facebookログインを例に、その実装方法を詳細に見ていきます。
前提条件
- Next.js App Routerを使用した既存のプロジェクト
- Node.jsとnpm (またはYarn) がインストールされていること
- Facebook開発者アカウント
- Prismaを使用したデータベースがセットアップされていること
実装ステップ
1. next-auth と Prisma Adapter のインストール
まず、認証ライブラリである next-auth と、Prismaと連携するためのアダプターをインストールします。
npm install next-auth @next-auth/prisma-adapter
2. Facebookアプリの作成と設定
Facebookログインを実装するには、Facebook開発者サイトで新しいアプリを作成し、必要な情報を設定する必要があります。
- Facebook for Developersにアクセス: https://developers.facebook.com/ にアクセスし、Facebookアカウントでログインします。
- 新しいアプリを作成: 「マイアプリ」から「アプリを作成」を選択し、アプリの種類(例: 「コンシューマー」)を選択します。アプリの表示名と連絡先メールアドレスを入力してアプリを作成します。
- App ID と App Secret の確認: アプリのダッシュボードの「設定」→「ベーシック」で、「アプリID」と「アプリシークレット」を確認します。これらは後で環境変数として使用します。
- Facebookログインの設定:
左側のナビゲーションメニューから「製品」→「製品を追加」を選択し、「Facebookログイン」を設定します。
「Facebookログイン」→「設定」で、「有効なOAuthリダイレクトURI」に以下のURLを追加します。
- 開発環境:
http://localhost:3000/api/auth/callback/facebook - 本番環境:
https://your-domain.com/api/auth/callback/facebook(デプロイ後に確定したURL)
- 開発環境:
- アプリの公開: アプリを「ライブモード」に切り替えることで、テストユーザー以外もログインできるようになります。ただし、プライバシーポリシーのURLなど、追加の設定が必要になる場合があります。
3. 環境変数の設定
プロジェクトのルートディレクトリに .env.local ファイルを作成し、以下の環境変数を追加します。
# NextAuth.js
NEXTAUTH_URL=http://localhost:3000 # 本番環境ではデプロイ後のURLに変更
NEXTAUTH_SECRET=YOUR_NEXTAUTH_SECRET # openssl rand -base64 32 で生成した秘密鍵
# Facebook OAuth
FACEBOOK_ID=YOUR_FACEBOOK_APP_ID
FACEBOOK_SECRET=YOUR_FACEBOOK_APP_SECRET
NEXTAUTH_SECRET は、openssl rand -base64 32 コマンドで生成できます。
4. Prismaスキーマの更新
next-auth がユーザー、アカウント、セッション、検証トークンを管理するために必要なモデルを prisma/schema.prisma に追加します。
// prisma/schema.prisma
model Account {
id String @id @default(cuid())
userId String
type String
provider String
providerAccountId String
refresh_token String?
access_token String?
expires_at Int?
token_type String?
scope String?
id_token String?
session_state String?
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
@@unique([provider, providerAccountId])
}
model Session {
id String @id @default(cuid())
sessionToken String @unique
userId String
expires DateTime
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
}
model User {
// 既存のUserモデルのフィールド
id String @id @default(cuid())
email String @unique
name String
password String? // next-authでパスワードを使用しない場合、Optionalにする
role Role @default(USER)
favoriteJobs Job[] @relation("UserFavoriteJobs")
forumPosts ForumPost[]
jobs Job[] @relation("UserJobs")
comments Comment[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
// next-authで追加するフィールド
emailVerified DateTime?
accounts Account[]
sessions Session[]
}
model VerificationToken {
identifier String
token String @unique
expires DateTime
@@unique([identifier, token])
}
スキーマを更新したら、Prismaマイグレーションを実行してデータベースを更新します。
npx prisma migrate dev --name add_nextauth_models
もし既存のデータがある場合、name フィールドが必須で password がOptionalになることでエラーが発生する可能性があります。その場合は、npx prisma migrate reset でデータベースをリセットするか、マイグレーションファイルを調整してデフォルト値を設定してください。
5. next-auth の設定ファイル (src/app/api/auth/[...nextauth]/route.ts) の作成
App Routerでは、app/api/auth/[...nextauth]/route.ts に認証ロジックを記述します。
// src/app/api/auth/[...nextauth]/route.ts
import NextAuth from 'next-auth';
import FacebookProvider from 'next-auth/providers/facebook';
import { PrismaAdapter } from '@next-auth/prisma-adapter';
import prisma from '@/lib/prisma'; // Prismaクライアントのインポート
const handler = NextAuth({
adapter: PrismaAdapter(prisma),
providers: [
FacebookProvider({
clientId: process.env.FACEBOOK_ID as string,
clientSecret: process.env.FACEBOOK_SECRET as string,
}),
],
secret: process.env.NEXTAUTH_SECRET,
session: {
strategy: 'jwt', // JWTセッションを使用
},
callbacks: {
async session({ session, token, user }) {
// セッションにユーザーIDとロール、名前、メールアドレスを追加
if (token) {
session.user.id = token.sub;
}
if (session.user.id) {
const dbUser = await prisma.user.findUnique({
where: { id: session.user.id },
select: { role: true, name: true, email: true },
});
if (dbUser) {
session.user.role = dbUser.role;
session.user.name = dbUser.name;
session.user.email = dbUser.email;
}
}
return session;
},
async jwt({ token, user }) {
if (user) {
token.sub = user.id;
}
return token;
},
},
pages: {
signIn: '/login',
},
});
export { handler as GET, handler as POST };
6. 型定義の拡張
next-auth のデフォルトの Session 型には、id, role, name などのカスタムフィールドが含まれていません。これらを拡張するために、プロジェクトのルートに next-auth.d.ts ファイルを作成します。
// next-auth.d.ts
import { DefaultSession, DefaultUser } from "next-auth";
import { DefaultJWT } from "next-auth/jwt";
declare module "next-auth" {
interface Session {
user: {
id: string;
role: string; // Role enumの型に合わせる
name: string;
email: string;
} & DefaultSession["user"];
}
interface User extends DefaultUser {
id: string;
role: string; // Role enumの型に合わせる
name: string;
email: string;
}
}
declare module "next-auth/jwt" {
interface JWT extends DefaultJWT {
id: string;
role: string; // Role enumの型に合わせる
name: string;
email: string;
}
}
7. ログインページへのFacebookログインボタンの追加
ログインページ (src/app/[locale]/login/page.tsx) にFacebookログインボタンを追加し、next-auth/react の signIn 関数を使用して認証フローを開始します。
// src/app/[locale]/login/page.tsx
'use client';
import React, { useState, useEffect } from 'react';
import { useRouter } from 'next/navigation';
import { useTranslation } from 'react-i18next';
import Link from 'next/link';
import { signIn } from 'next-auth/react'; // signInをインポート
// ... (既存のRegisterLinkコンポーネントとLoginPageのコード)
const LoginPage: React.FC<{ params: Promise<{ locale: string }> }> = ({ params }) => {
// ... (既存のstateとuseEffect)
const handleSubmit = async (e: React.FormEvent) => {
// ... (既存のメール/パスワードログインロジック)
};
const handleFacebookLogin = () => {
signIn('facebook', { callbackUrl: `/${locale}/` }); // Facebookログイン
};
return (
<div className="max-w-md mx-auto mt-10 p-6 bg-white rounded-lg shadow-md">
<h1 className="text-2xl font-bold mb-6 text-center">{t('login_title')}</h1>
<form onSubmit={handleSubmit} className="space-y-4">
{/* ... (メール/パスワード入力フィールドとログインボタン) */}
</form>
<div className="mt-4 text-center">
<button
onClick={handleFacebookLogin}
disabled={!locale}
className="w-full flex justify-center items-center py-2 px-4 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 disabled:bg-gray-400"
>
<svg className="w-5 h-5 mr-2" fill="currentColor" viewBox="0 0 24 24" aria-hidden="true">
<path fillRule="evenodd" d="M22 12c0-5.523-4.477-10-10-10S2 6.477 2 12c0 4.991 3.657 9.128 8.438 9.878v-6.987h-2.54V12h2.54V9.797c0-2.506 1.492-3.89 3.777-3.89 1.094 0 2.238.195 2.238.195v2.46h-1.26c-1.243 0-1.63.771-1.63 1.562V12h2.773l-.443 2.89h-2.33V22C18.343 21.128 22 16.991 22 12z" clipRule="evenodd" />
</svg>
{t('login_with_facebook')}
</button>
</div>
{locale && <RegisterLink locale={locale} t={t} />}
</div>
);
};
export default LoginPage;
8. SessionProvider の設定
next-auth のセッションを使用するために、アプリケーション全体を SessionProvider でラップする必要があります。これは通常、ルートレイアウト (src/app/[locale]/layout.tsx) で行います。
// src/app/[locale]/layout.tsx
// ... (既存のインポート)
import { SessionProvider } from 'next-auth/react'; // 追加
export default async function RootLayout({
children,
params,
}: Readonly<{
children: React.ReactNode;
params: Promise<{ locale: string }>;
}>) {
// ... (既存のデータ取得ロジック)
return (
<html lang={locale} dir={dir(locale)} suppressHydrationWarning>
<body className={`${geistSans.variable} ${geistMono.variable} antialiased`} suppressHydrationWarning>
<I18nProvider locale={locale} namespaces={['common']}>
<SessionProvider> {/* 追加 */}
<Header languages={languages} session={session} />
<main className="container mx-auto px-6 py-8">{children}</main>
</SessionProvider> {/* 追加 */}
</I18nProvider>
</body>
</html>
);
}
トラブルシューティング
client_id is required エラー
- 原因:
FACEBOOK_IDまたはFACEBOOK_SECRET環境変数が正しく設定されていないか、next-authが読み込めていない。 - 解決策:
.env.localファイルにFACEBOOK_IDとFACEBOOK_SECRETが正しく記述されていることを確認します。- Facebook開発者サイトで取得した実際の値に置き換えられていることを確認します。
- 環境変数を変更した後は、必ず開発サーバーを再起動してください。
- Cloudflare Pagesなどのデプロイ環境では、環境変数が正しく設定されているかダッシュボードで確認します。
React Hook "useTranslation" cannot be called in an async function エラー
- 原因: サーバーコンポーネント(async関数)内でReact Hookである
useTranslationを直接呼び出しているため。 - 解決策:
- サーバーコンポーネントでは、
i18nextのcreateInstanceを直接使用して翻訳インスタンスを作成し、t関数を取得します。 src/app/i18n/index.tsのuseTranslation関数名をgetTranslationのような名前に変更し、Next.jsのリンターがReact Hookとして誤認識しないようにします。
- サーバーコンポーネントでは、
APIルートの型エラー (Type "{ params: { id: string; }; }" is not a valid type for the function's second argument.)
- 原因: Next.jsのApp RouterのAPIルートの型定義が、ビルド環境のTypeScriptバージョンやNext.jsの内部的な型チェックと厳密に一致していないため。
- 解決策:
- APIルートの
GETやPOST関数は、NextRequestオブジェクトと、ダイナミックルートのparamsオブジェクトを引数として受け取ります。 export async function GET(request: NextRequest, { params }: { params: { id: string } })の形式を使用します。
- APIルートの
まとめ
本記事では、Next.js App RouterでFacebookログインを実装するための詳細な手順と、実装中に遭遇する可能性のある一般的なエラーの解決策を解説しました。next-auth を活用することで、OAuth認証フローを簡素化し、安全な認証システムを構築できます。
Facebookログインを実装することで、ユーザーは既存のFacebookアカウントで簡単にサインインできるようになり、アプリケーションの利便性が向上します。