😽

Next.js App Router + Auth.js (Next Auth v5) で Google 認証を実装する

2024/11/02に公開

はじめに

はじめまして。ソフトウェアエンジニアもどきの しんりうです。
現在、私が開発しているプロダクトで 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.

https://next-auth.js.org/configuration/initialization

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!

https://authjs.dev/guides/edge-compatibility#authjs

ただし、Edge ランタイムでの動作はコア機能(next-auth)のみサポートされており、Auth.js のコールバックやミドルウェア中で他のライブラリを使用する場合はそれらのライブラリもEdge ランタイム上での互換性がある必要がある、ということに注意しなければいけません。

https://authjs.dev/getting-started/migrating-to-v5#edge-compatibility

実装

ここから実際に手を動かしていく部分になります。
本記事の通り、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
.env
AUTH_SECRET=xxxxx

auth.config.ts の設定

一つのファイル(auth.ts)にまとめることも可能ですが、責務の分離のために設定部分は別ファイル(auth.config.ts)に切り出すことにします。

ここでAuth.js のコールバック関数やセッション設定なども定義することができます。

auth.config.ts
import type { NextAuthConfig } from "next-auth";

const authConfig: NextAuthConfig = {
  providers: [], // 後ほどここにGoogle プロバイダを追加していきます
};

auth.ts の設定

上のconfig を読み込んで Auth.js の初期化を行います。

auth.ts
import NextAuth from "next-auth";
import authConfig from "./auth.config";

export const { handlers, auth, signIn, signOut } = NextAuth(authConfig);

Route Handlers にエンドポイントを追加

このエンドポイントで認証周りのAPIリクエストをキャッチし、サインイン/サインアウト処理、セッション管理、コールバック処理などを行います。

./app/api/auth/[...nextauth]/route.ts
import { handlers } from "../../../../../auth";

export const { GET, POST } = handlers;

Google プロバイダの追加

Auth.js の基本的なセットアップが完了したら、実際にプロバイダの追加をして認証機能が動作する様子を確認していきます。

Google Cloud でのセットアップ

Google 認証を実装するには、Google Cloud(旧称 GCP)でちょっとした設定をする必要があります。

新規プロジェクト作成

まず、Google Cloud にアクセスして新規プロジェクトを作成してください。
https://console.cloud.google.com/welcome

OAuth 同意画面の作成

次に「APIとサービス」にアクセスし、「OAuth 同意画面」を作成してください。
ここで特別な設定は必要ないため、適当に必須項目を埋めてください。

OAuth クライアントの作成

次に「認証情報」に移動し、認証情報を新規作成してください。
ここで、種類は OAuth クライアントID を選択してください。

https://console.cloud.google.com/welcome?project=next-auth-sample-1

OAuth クライアントID の作成は以下のように設定してください。

※ 承認済みURL はローカルホストを例にしています。Vercel や Cloudflare Pages などでホスティングする際は適切なURL を指定してあげてください。

auth.config.ts の編集

Google プロバイダを追加します。
Auth.js (NextAuth v5)より、プロバイダに必要な環境変数は自動で読み込まれるようになったため、クライアントIDとクライアントシークレットを引数として渡してあげる必要がなくなりました。

auth.config.ts
import type { NextAuthConfig } from "next-auth";
+ import Google from "next-auth/providers/google";

const authConfig: NextAuthConfig = {
  providers: [
+       Google, // たったこれだけ
  ],
};

環境変数の追加

ということで環境変数を追加します。
先ほど Google Cloud セットアップ時に発行したクライアントIDとクライアントシークレットを渡してあげる形になります。

.env
AUTH_SECRET=xxxxx

+ AUTH_GOOGLE_ID=xxxxx-yyyyy.apps.googleusercontent.com
+ AUTH_GOOGLE_SECRET=xxxxx

Session, JWT 型の拡張

今回は 例として ID トークンをセッション管理できるようにしたいと思います。
そのため、まずは Session, JWT 型を拡張します。

src/types/auth.d.ts
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 コールバックを追加します。

詳細はこちら に明示されています。

auth.config.ts
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 を使っています。

src/components/AuthButton.tsx
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)での取得方法であることに気をつけてください。

src/app/page.tsx
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 を設置しておく必要があります。

src/app/layout.tsx
+ 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 を呼び出すことができ、クライアントサイドでもセッション情報を取得できます。

src/app/page.tsx
+ "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