1. Auth.js のセットアップ (NextAuth v5)
npm install next-auth@beta
// auth.ts
import NextAuth from 'next-auth';
import Google from 'next-auth/providers/google';
import GitHub from 'next-auth/providers/github';
import Credentials from 'next-auth/providers/credentials';
import { PrismaAdapter } from '@auth/prisma-adapter';
import { db } from '@/lib/db';
import bcrypt from 'bcryptjs';
export const { handlers, signIn, signOut, auth } = NextAuth({
adapter: PrismaAdapter(db),
providers: [
Google({
clientId: process.env.GOOGLE_CLIENT_ID!,
clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
}),
GitHub({
clientId: process.env.GITHUB_ID!,
clientSecret: process.env.GITHUB_SECRET!,
}),
Credentials({
credentials: {
email: { label: 'Email', type: 'email' },
password: { label: 'Password', type: 'password' },
},
async authorize(credentials) {
const user = await db.user.findUnique({
where: { email: credentials.email as string },
});
if (!user?.hashedPassword) return null;
const valid = await bcrypt.compare(
credentials.password as string,
user.hashedPassword
);
return valid ? user : null;
},
}),
],
session: { strategy: 'jwt' },
callbacks: {
async jwt({ token, user }) {
if (user) {
token.role = user.role;
}
return token;
},
async session({ session, token }) {
session.user.id = token.sub!;
session.user.role = token.role as string;
return session;
},
},
});
2. ルートハンドラー
// app/api/auth/[...nextauth]/route.ts
import { handlers } from '@/auth';
export const { GET, POST } = handlers;
3. ミドルウェアの保護
// middleware.ts
import { auth } from '@/auth';
export default auth((req) => {
const isLoggedIn = !!req.auth;
const isAuthPage = req.nextUrl.pathname.startsWith('/auth');
const isProtected = req.nextUrl.pathname.startsWith('/dashboard');
if (isProtected && !isLoggedIn) {
return Response.redirect(new URL('/auth/login', req.nextUrl));
}
if (isAuthPage && isLoggedIn) {
return Response.redirect(new URL('/dashboard', req.nextUrl));
}
});
export const config = {
matcher: ['/dashboard/:path*', '/auth/:path*'],
};
4. サーバーコンポーネントでの使用
import { auth } from '@/auth';
import { redirect } from 'next/navigation';
export default async function DashboardPage() {
const session = await auth();
if (!session) redirect('/auth/login');
return (
<div>
<h1>Xin chào, {session.user?.name}</h1>
<p>Role: {session.user?.role}</p>
<img src={session.user?.image || ''} alt="Avatar" />
</div>
);
}
5. クライアントコンポーネントでの使用
'use client';
import { useSession, signIn, signOut } from 'next-auth/react';
export function AuthButton() {
const { data: session, status } = useSession();
if (status === 'loading') return <div>Loading...</div>;
if (session) {
return (
<div>
<span>{session.user?.name}</span>
<button onClick={() => signOut()}>Đăng xuất</button>
</div>
);
}
return (
<div>
<button onClick={() => signIn('google')}>Google</button>
<button onClick={() => signIn('github')}>GitHub</button>
</div>
);
}
6. 役割ベースのアクセス制御
// lib/auth-utils.ts
import { auth } from '@/auth';
export async function requireRole(role: string) {
const session = await auth();
if (!session || session.user?.role !== role) {
throw new Error('Unauthorized');
}
return session;
}
// app/admin/page.tsx
import { requireRole } from '@/lib/auth-utils';
export default async function AdminPage() {
const session = await requireRole('admin');
return <div>Admin Dashboard — {session.user?.name}</div>;
}
7. サーバーアクションを含むログインフォーム
'use server';
import { signIn } from '@/auth';
import { AuthError } from 'next-auth';
export async function loginAction(prevState: any, formData: FormData) {
try {
await signIn('credentials', {
email: formData.get('email'),
password: formData.get('password'),
redirectTo: '/dashboard',
});
} catch (error) {
if (error instanceof AuthError) {
return { error: 'Email hoặc mật khẩu không đúng' };
}
throw error;
}
}
次の記事: ミドルウェア、API ルート、ルート ハンドラー — 高度なリクエスト/レスポンス処理。