Next.js(App Router)にNextAuth.jsを導入しGoogle認証とX(Twitter)ログインを実現する
はじめに
Next.js(App Router)にNextAuth.jsを導入して、X(Twitter)と Google のログインを実装します。
今回、Firebase Auth
ではなくNextAuth.js
を利用しようとしたのは、いくつか理由がありますがZennの開発者であるcatnoseさんのポストが一因でFirebase Auth
の導入を諦めました🫠
※ 調べてみるとFirebase Auth単体でもログインは遅いらしいです。
NextAuth.jsの公式ドキュメントを読んでいると簡単に導入できるように見えますが、実際にはいくつかの注意点がありますので、備忘録として書いておきます。
※ 主にApp RouterとX(Twitter)ログインの仕様のせいでハマりました。
作るもの
作るものは至ってシンプルです。ログインページとトップページのみです。
ヘッダーには最初にログインボタンが表示されており、ログイン後には、ヘッダーにログインユーザーの情報とログアウトボタンを表示します。
作成する画面
ログイン前の TOP ページ
ログインページ
ログイン後の TOP ページ
手順
npx create-next-app@latest
コマンドで作成した Next.js(App Router)のプロジェクト(すべてデフォルト設定)にNextAuth.js
を導入します。
npm install next-auth
JWT のシークレットを生成
openssl
コマンドを使用して JWT のシークレットを生成します。
openssl rand -base64 32
.env.local の作成
touch .env.local
Twitter のクライアント ID とシークレットを取得
次のページを参考にして、Twitter のクライアント ID とシークレットを取得します。
Callback URI
にはhttp://127.0.0.1:3000/api/auth/callback/twitterを設定します。
Twitter API の Key や Secret の取得・確認手順※2023 年 10 月最新 │Programming ZERO
Google のクライアント ID とシークレットを取得
次のページを参考にして、Google のクライアント ID とシークレットを取得します。
承認済みのリダイレクト URI
にはhttp://127.0.0.1:3000/api/auth/callback/googleを設定します。
NextAuth.js で Next.js13 に Google 認証機能を実装
.env.local の設定
NEXTAUTH_SECRET
にはopenssl
コマンドで生成した JWT のシークレットを設定します。
NEXTAUTH_SECRET=YOUR_JWT_SECRET
NEXTAUTH_URL=http://127.0.0.1:3000
TWITTER_CLIENT_ID=YOUR_TWITTER_CLIENT_ID
TWITTER_CLIENT_SECRET=YOUR_TWITTER_CLIENT_SECRET
GOOGLE_CLIENT_ID=YOUR_GOOGLE_CLIENT_ID
GOOGLE_CLIENT_SECRET=YOUR_GOOGLE_CLIENT_SECRET
next.config.mjs の設定
/** @type {import('next').NextConfig} */
const nextConfig = {
images: {
// X(Twitter)とGoogleのプロフィール画像を表示するために追加
domains: ["pbs.twimg.com", "lh3.googleusercontent.com"],
},
};
export default nextConfig;
ヘッダーの作成
ヘッダーにはログインユーザーの情報を表示しますが、親コンポーネントからログインユーザーの情報を受け取るためにsession
を引数に取ります。
session
はnext-auth/react
のuseSession
でも取得できますが、ページの初期描画時にuseSession
を使用すると、useSession
の初期化が完了するまでユーザー情報が描画されない(ログインボタンがチラついてしまう)ため、あえてサーバーコンポーネントからsession
を引数に取ることにします。
"use client";
import Image from "next/image";
import Link from "next/link";
import { type Session } from "next-auth";
import { signOut } from "next-auth/react";
const Header = ({ session }: { session: Session | null }) => {
return (
<header className="flex items-center justify-between bg-white p-4 shadow-md">
<div className="flex items-center">
<Link href="/" className="text-4xl font-bold">
あなたのイケてるサービス
</Link>
</div>
<ul className="flex items-center space-x-4">
{session ? (
<>
<li>
<Image
src={session.user?.image ?? ""}
alt={session.user?.name ?? ""}
width={40}
height={40}
className="rounded-full"
/>
</li>
<li>
<button
onClick={() => signOut()}
className="rounded-lg bg-blue-500 px-4 py-[7px] text-white hover:bg-gray-600"
>
ログアウト
</button>
</li>
</>
) : (
<li>
<Link href="/login">
<button className="rounded-lg bg-blue-500 px-4 py-[7px] text-white hover:bg-gray-600">
ログイン
</button>
</Link>
</li>
)}
</ul>
</header>
);
};
export default Header;
レイアウトファイルの編集
NextAuth.js
のgetServerSession
を使用してサーバーサイドでsession
を取得し、ヘッダーに渡します。
これにより、ページの初期描画時にログインユーザーの情報が表示されます。
import type { Metadata } from "next";
import { Inter } from "next/font/google";
import "./globals.css";
import { getServerSession } from "next-auth/next";
import NextAuthProvider from "@/app/providers";
import Header from "@/app/_components/header";
import { nextAuthOptions } from "@/app/_utils/next-auth-options";
const inter = Inter({ subsets: ["latin"] });
export const metadata: Metadata = {
title: "Create Next App",
description: "Generated by create next app",
};
export default async function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
const session = await getServerSession(nextAuthOptions);
return (
<html lang="ja">
<body className={inter.className}>
<NextAuthProvider>
<Header session={session} />
{children}
</NextAuthProvider>
</body>
</html>
);
}
NextAuth.js のオプションを設定
X(Twitter)と Google のプロバイダーを設定します。
import TwitterProvider from "next-auth/providers/twitter";
import GoogleProvider from "next-auth/providers/google";
import type { NextAuthOptions } from "next-auth";
export const nextAuthOptions: NextAuthOptions = {
debug: true,
session: { strategy: "jwt" },
providers: [
TwitterProvider({
clientId: process.env.TWITTER_CLIENT_ID!,
clientSecret: process.env.TWITTER_CLIENT_SECRET!,
}),
GoogleProvider({
clientId: process.env.GOOGLE_CLIENT_ID!,
clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
}),
],
callbacks: {
jwt: async ({ token, user, account, profile }) => {
// 注意: トークンをログ出力してはダメです。
console.log("in jwt", { user, token, account, profile });
if (user) {
token.user = user;
const u = user as any;
token.role = u.role;
}
if (account) {
token.accessToken = account.access_token;
}
return token;
},
session: ({ session, token }) => {
console.log("in session", { session, token });
token.accessToken;
return {
...session,
user: {
...session.user,
role: token.role,
},
};
},
},
};
【参考】
Next.js 13 App Router での Auth.js の使い方
API ルートの作成
import NextAuth from "next-auth";
import { nextAuthOptions } from "@/app/_utils/next-auth-options";
const handler = NextAuth(nextAuthOptions);
export { handler as GET, handler as POST };
ログインページの作成
"use client";
import React from "react";
import { useEffect } from "react";
import { redirect } from "next/navigation";
import { signIn } from "next-auth/react";
import { useSession } from "next-auth/react";
const LoginPage = () => {
const { data: session, status } = useSession();
useEffect(() => {
// ログイン済みの場合はTOPページにリダイレクト
if (status === "authenticated") {
redirect("/");
}
}, [session, status]);
const handleLogin = (provider: string) => async (event: React.MouseEvent) => {
event.preventDefault();
const result = await signIn(provider);
// ログインに成功したらTOPページにリダイレクト
if (result) {
redirect("/");
}
};
return (
<div className="flex min-h-screen items-center justify-center bg-gray-100">
<form className="w-full max-w-xs space-y-6 rounded bg-white p-8 shadow-md">
<button
onClick={handleLogin("google")}
type="button"
className="w-full bg-red-500 text-white rounded-lg px-4 py-2"
>
Googleでログイン
</button>
<button
onClick={handleLogin("twitter")}
type="button"
className="w-full bg-blue-500 text-white rounded-lg px-4 py-2"
>
Twitterでログイン
</button>
</form>
</div>
);
};
export default LoginPage;
ページの背景色を白に
@tailwind base;
@tailwind components;
@tailwind utilities;
:root {
--foreground-rgb: 0, 0, 0;
--background-start-rgb: 214, 219, 220;
--background-end-rgb: 255, 255, 255;
}
@media (prefers-color-scheme: dark) {
:root {
--foreground-rgb: 255, 255, 255;
--background-start-rgb: 0, 0, 0;
--background-end-rgb: 0, 0, 0;
}
}
@layer utilities {
.text-balance {
text-wrap: balance;
}
}
TOP ページの作成
export default function Home() {
return (
<main>
<h1>TOPページだよ</h1>
</main>
);
}
プロバイダーの設定
"use client";
import { type ReactNode } from "react";
import { SessionProvider } from "next-auth/react";
export default function NextAuthProvider({
children,
}: {
children: ReactNode;
}) {
return <SessionProvider>{children}</SessionProvider>;
}
動作確認
npm run dev
で開発サーバーを起動し、http://localhost:3000ではなく、http://127.0.0.1:3000にアクセスします。
Google と X(Twitter)両方ともログインできるようになり、ログイン後にユーザーアイコンが右上に表示されるようになったら動作確認は終了です。
ログイン前の TOP ページ
ログインページ
ログイン後の TOP ページ
お疲れ様でした!!!
Discussion