Next.js App Router + Auth.js (Next Auth v5) で Google 認証を実装する
はじめに
はじめまして。ソフトウェアエンジニアもどきの しんりうです。
現在、私が開発しているプロダクトで Next.js App Router と Auth.js を使用しており、今回はその振り返りを兼ねて記事にしました。
本記事では、 Auth.js の概要に触れた上で実際に Google 認証を実装するところまでまとめたいと思います。
※ 正直なところ本記事に特段目新しい知見があるわけではないのですが、本記事公開後に執筆予定の 「Auth.js への AWS Cognito + 外部 ID プロバイダの繋ぎ込み」、「バックエンド側でのトークン検証」 への足がけとしてまとめることにしました。
Auth.js (NextAuth.js v5) とは
Auth.js は、認証機能を包括的に提供する Web アプリケーション向けの OSS ライブラリです。以前は NextAuth.js と呼ばれていましたが、v4 → v5 への大幅アップデートに伴い、現在は Auth.js という名称に移行しています。ただし、v5 は beta 版としてのみリリースされています。
Auth.js の特徴
複数の認証プロバイダのサポート
Auth.js では 4 つの認証方式をサポートしています。
- OAuth/OpenID Connect(Google, Github 等)
- メール認証
- パスワード認証
- WebAuthn (パスキー等)
本記事では、OAuth/OIDC プロバイダによる実装を取り扱います。
セッション管理機能およびデータ永続化
また、JWT またはデータベースを利用したセッション管理機能や、データの永続化にも対応しています。セッションの自動更新やセキュアな Cookie 管理など、認証に必要な処理を開発者が意識することなく実装できます。
複数のフレームワークのサポート
フレームワークのサポートも充実しており、Next.js をはじめ SvelteKit、Qwik, Express なども対応しています。npm パッケージとしては現在も next-auth
という名前が使用されていますが、Auth.js では @auth/*
パッケージへと移行される予定です。
v4 と v5 の違い
破壊的変更
公式 docs によると、NextAuth.js は v4 から v5 へのアップデートに伴い、5 点の破壊的変更があったとのことです。
- Auth.js は
@auth/core
をベースに再構築され、OAuth/OIDC の仕様準拠がより厳密になった。これにより一部の OAuth プロバイダで互換性の問題が発生する可能性がある。 - OAuth 1.0 のサポートは非推奨になった。
- Next.js の最低要求バージョンが 14.0 になった。
-
インポート方法が変更された。
-
next-auth/next
の使用方法が変更 -
next-auth/middleware
の使用方法が変更
-
-
idToken: false
の動作が変更された。- 以前:ID トークンのチェックを完全にスキップ
- 現在:ID トークンのチェックは行いつつ、
userinfo_endpoint
からもユーザー情報を取得するように変更
Edge ランタイムでのサポート
今回筆者が v5 を採用した理由はここが大きいのですが、v5 から Edge ランタイムでの互換性がサポートされ始めました。
現在開発しているプロダクトでは当初、Next.js + NextAuth.js v4 を Cloudflare Pages でホスティングしていたのですが、Edge ランタイム上では NextAuth.js が動かないという問題が発覚しました。
そこで公式 docs を確認したところ以下のような記述が見つかり、v5 へのアップグレードを決めたという次第です。
NextAuth.js v4 での仕様:
Edge ランタイムでは動かなかった。
NextAuth.js cannot use the run Edge Runtime for initialization. The upcoming @auth/nextjs library (which will replace next-auth) on the other hand will be fully compatible.
Auth.js (NextAuth v5) での仕様:
コア機能は Edge 互換性が最適化されている。
Edge compatibility is something Auth.js has optimized for. That means that you can run the core Auth.js functionality on any JavaScript runtime you choose. The key word here, however, being core functionality. If you use only Auth.js / next-auth and no other library in your Auth.js callbacks, Middleware, etc. then you can use it wherever you want!
ただし、Edge ランタイムでの動作はコア機能(next-auth
)のみサポートされており、Auth.js のコールバックやミドルウェア中で他のライブラリを使用する場合はそれらのライブラリも Edge ランタイム上での互換性がある必要がある、ということに注意しなければいけません。
実装
ここから実際に手を動かしていく部分になります。
本記事の通り、Next.js App Router + Auth.js (NextAuth.js v5) で Google 認証を実装していきます。
なお、動作環境については Node.js バージョンが 21.2.0
、ライブラリのバージョンは以下のとおりです。
"next": "14.2.16",
"next-auth": "5.0.0-beta.25",
Next.js プロジェクト作成
プロジェクトフォルダで create-next-app
します。
$ npx create-next-app@14 next-auth-sample --typescript
※ 今回は App Router を選択をすることに注意してください。
Auth.js のセットアップ
基本的に公式 docs のInstallationに従うだけです。
パッケージの追加
Auth.js をインストールします。パッケージ名はまだ next-auth
という名前みたいです。
Auth.js (NextAuth.js v5) をインストールするため、バージョン指定(@beta
)することを忘れないように気をつけてください。
$ pnpm add next-auth@beta
AUTH_SECRET
変数の設定
トークンの暗号化やハッシュ化するための環境変数を設定します。
値は何でもいいですが、以下のコマンドで生成することができます。
$ npx auth secret
AUTH_SECRET=xxxxx
auth.config.ts
の設定
一つのファイル(auth.ts
)にまとめることも可能ですが、責務の分離のために設定部分は別ファイル(auth.config.ts
)に切り出すことにします。
ここで Auth.js のコールバック関数やセッション設定なども定義することができます。
import type { NextAuthConfig } from "next-auth";
const authConfig: NextAuthConfig = {
providers: [], // 後ほどここにGoogle プロバイダを追加していきます
};
auth.ts
の設定
上の config を読み込んで Auth.js の初期化を行います。
import NextAuth from "next-auth";
import authConfig from "./auth.config";
export const { handlers, auth, signIn, signOut } = NextAuth(authConfig);
Route Handlers にエンドポイントを追加
このエンドポイントで認証周りの API リクエストをキャッチし、サインイン/サインアウト処理、セッション管理、コールバック処理などを行います。
import { handlers } from "../../../../../auth";
export const { GET, POST } = handlers;
Google プロバイダの追加
Auth.js の基本的なセットアップが完了したら、実際にプロバイダの追加をして認証機能が動作する様子を確認していきます。
Google Cloud でのセットアップ
Google 認証を実装するには、Google Cloud(旧称 GCP)でちょっとした設定をする必要があります。
新規プロジェクト作成
まず、Google Cloud にアクセスして新規プロジェクトを作成してください。
OAuth 同意画面の作成
次に「API とサービス」にアクセスし、「OAuth 同意画面」を作成してください。
ここで特別な設定は必要ないため、適当に必須項目を埋めてください。
OAuth クライアントの作成
次に「認証情報」に移動し、認証情報を新規作成してください。
ここで、種類は OAuth クライアント ID を選択してください。
OAuth クライアント ID の作成は以下のように設定してください。
- アプリケーションの種類 ... ウェブアプリケーション
- 名前 ... 任意の名前
- 承認済みの JavaScript 生成元 ... http://localhost:3000
- 承認済みのリダイレクト URI ... http://localhost:3000/api/auth/callback/google
※ 承認済み URL はローカルホストを例にしています。Vercel や Cloudflare Pages などでホスティングする際は適切な URL を指定してあげてください。
auth.config.ts
の編集
Google プロバイダを追加します。
Auth.js (NextAuth v5)より、プロバイダに必要な環境変数は自動で読み込まれるようになったため、クライアント ID とクライアントシークレットを引数として渡してあげる必要がなくなりました。
import type { NextAuthConfig } from "next-auth";
+ import Google from "next-auth/providers/google";
const authConfig: NextAuthConfig = {
providers: [
+ Google, // たったこれだけ
],
};
環境変数の追加
ということで環境変数を追加します。
先ほど Google Cloud セットアップ時に発行したクライアント ID とクライアントシークレットを渡してあげる形になります。
AUTH_SECRET=xxxxx
+ AUTH_GOOGLE_ID=xxxxx-yyyyy.apps.googleusercontent.com
+ AUTH_GOOGLE_SECRET=xxxxx
Session
, JWT
型の拡張
今回は 例として ID トークンをセッション管理できるようにしたいと思います。
そのため、まずは Session
, JWT
型を拡張します。
import type { JWT } from "next-auth/jwt";
// Session を拡張
declare module "next-auth" {
interface Session {
idToken: string;
}
}
// JWT を拡張
declare module "next-auth/jwt" {
interface JWT {
idToken: string;
}
}
Callbacks 関数の追加
Callbacks 関数はサインインやセッション更新をトリガーに呼び出される非同期関数です。
今回はjwt
コールバックと session
コールバックを追加します。
詳細はこちら に明示されています。
import type { NextAuthConfig } from "next-auth";
import Google from "next-auth/providers/google";
const authConfig: NextAuthConfig = {
providers: [
Google,
],
+ callbacks: {
+ async jwt({ token, user, account }) {
+ if (user && account?.id_token) {
+ token.idToken = account?.id_token;
+ }
+ return token;
+ },
+ async session({ token, session }) {
+ session.idToken = token.idToken;
+ return session;
+ },
+ },
};
動作確認
それでは実際にサインイン/サインアウト処理を実装して動作確認をしてみます。
サインイン/サインアウトボタンコンポーネントの作成
サインイン/サインアウトイベントを発火させるためのボタンコンポーネントを作成します。
※ 本記事の例ではボタンコンポーネントに shadcn/ui
を使っています。
import { signIn, signOut } from "next-auth/react";
import { Button } from "@/components/ui/button";
interface AuthButtonProps {
onClick: () => void;
children: React.ReactNode;
variant: "default" | "outline";
}
const AuthButton = ({ onClick, children, variant }: AuthButtonProps) => {
return (
<Button onClick={onClick} variant={variant}>
{children}
</Button>
);
};
export const LogInButton = () => {
return (
<AuthButton onClick={() => signIn()} variant={"default"}>
Log In
</AuthButton>
);
};
export const LogOutButton = () => {
return (
<AuthButton onClick={() => signOut()} variant={"outline"}>
Log Out
</AuthButton>
);
};
サインイン/サインアウトの動作確認およびセッション情報の取得
それでは作成したボタンコンポーネントを設置して動作確認を行い、サインイン時に取得したセッション情報を表示してみます。
セッション情報の取得は以下のようにして行います。
サーバーサイド(React Server Component)での取得方法であることに気をつけてください。
import { auth } from "../../auth";
export default async function Home() {
const session = await auth();
console.log(session?.idToken); // ID トークンを sessionに格納できている
return (
<div className="w-full text-center">
<h1 className="text-2xl my-12">Hello World</h1>
<div>
{!session && (
<div>
<p>Hi, please log in!</p>
</div>
)}
{session && (
<div>
<p>Hi, {session.user?.email}!</p>
<p> You logged in. </p>
</div>
)}
</div>
</div>
);
}
実際にサインインボタンを押すと OAuth 同意画面が表示され、承認すると無事にサインインできました。
クライアントサイドでのセッション情報取得
もしクライアントサイドで Auth.js の hooks を呼び出す場合、React コンテキストとして SessionProvider
を設置しておく必要があります。
+ import { SessionProvider } from "next-auth/react";
import "./globals.css";
export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
return (
<html>
<body>
+ <SessionProvider>
{children}
+ </SessionProvider>
</body>
</html>
);
}
こうしておけば useSession
hooks を呼び出すことができ、クライアントサイドでもセッション情報を取得できます。
+ "use client";
+ import { useSession } from "next-auth/react";
- export default async function Home() {
+ export default function Home() {
- const session = await auth();
+ const { data: session } = useSession();
console.log(session.user?.email) // auth() と同様に取得できる
...
}
おわりに
本記事では、Auth.js (NextAuth.js v5) の概要について説明した上で Next.js App Router と組み合わせて Google 認証を実装しました。
Auth.js は、Edge ランタイム対応や OIDC 仕様への準拠強化など、大きな進化を遂げています。
後日、別の記事で Auth.js への AWS Cognito + 外部 ID プロバイダの繋ぎ込み、バックエンド側でのトークン検証についてもまとめる予定です。
ここまで読んでいただきありがとうございました。
Discussion