🐯

【Next.js】NextAuth.jsでGoogleログイン機能の実装方法

2024/06/30に公開

はじめに

個人開発でNextAuth.jsを使用して、Next.jsでGoogleログイン機能を作成したので、手順を紹介します。
Googleログインに必要な情報は別記事にて紹介しています。
https://zenn.dev/aya1357/articles/bb291f8b4a31cb

環境

Next.js 14.2.4
NextAuth 5.0.0-beta.19

参照

https://next-auth.js.org/
https://authjs.dev/

実装手順

1. アプリの新規作成

以下のコマンドでNext.jsのアプリの新規作成します。

command
npx create-next-app@latest

必要な項目に回答していきます。

✔ What is your project named? … google_auth_login
✔ Would you like to use TypeScript? … No / Yes
✔ Would you like to use ESLint? … No / Yes
✔ Would you like to use Tailwind CSS? … No / Yes
✔ Would you like to use `src/` directory? … No / Yes
✔ Would you like to use App Router? (recommended) … No / Yes
✔ Would you like to customize the default import alias (@/*)? … No / Yes
✔ What import alias would you like configured? … @/* ←ここはEnterキーを押す

新規作成されていることを確認したら、

command
ls
google_auth_login

ディレクトリに移動して、エディタで立ち上げます。

command
cd google_auth_login/

以下のコマンドでNext.jsの画面が立ち上がったらセットアップは完了です。

command
npm run dev

2. Googleから必要な情報を取得

記事を参考にClientIdClientSecretを取得します。
https://zenn.dev/aya1357/articles/bb291f8b4a31cb

3. Googleから取得した情報をenvファイルの設定

Googleの情報をenvファイルに記載して参照出来るようにします。

AUTH_URLにはCallback URLを設定します。
https://authjs.dev/getting-started/providers/google#callback-url

AUTH_SECRETには32バイトのランダムデータを生成し、それをBase64形式でエンコードします。

command
openssl rand -base64 32
.env
// GoogleのCallback URL
AUTH_URL="http://localhost:3000/api/auth"
AUTH_SECRET="openssl rand -base64 32で生成されたデータを記載"

GOOGLE_CLIENT_ID="Googleから取得したClientId"
GOOGLE_CLIENT_SECRET="Googleから取得したClientSecret"

4. NextAuth.jsをインストール

https://next-auth.js.org/getting-started/example
公式を参考にNextAuth.jsをインストールします。

command
npm install next-auth

5. 認証機能を設定

画像の設定

themaに画像を設定すると、指定した画像の表示が出来ます。
Image from Gyazo

Google認証プロバイダの設定

Google OAuthを使ってユーザー認証を行うために、Googleプロバイダをproviders配列に追加しています。これにはClientIdClientSecretが必要で、これらは環境変数から取得されます。

NextAuthの設定

NextAuth関数を使用して認証関連の設定を行います。設定内容にはプロバイダの指定、APIのベースパス/api/auth、そしてコールバック関数が含まれます。

認証コールバックの定義

authorizedコールバックで、ログイン後に利用できるページ(ここでは/protected-page)へのアクセスがログイン認証されたユーザーに限定されるようにしています。
jwtコールバックでは、JWTトークンが更新された際にユーザー名をトークンに追加しています。

src/app/api/auth/[...nextauth]/route.ts
import { handlers } from '@/contexts/auth'

export const { GET, POST } = handlers
src/contexts/auth.tsx
import Google from 'next-auth/providers/google'
import NextAuth, { NextAuthConfig } from 'next-auth'
import { Provider } from 'next-auth/providers'

const providers: Provider[] = [
  Google({
    clientId: process.env.GOOGLE_CLIENT_ID,
    clientSecret: process.env.GOOGLE_CLIENT_SECRET,
  }),
]

export const config: NextAuthConfig = {
  theme: {
    logo: '/login.png',
  },
  providers: providers,
  basePath: '/api/auth',
  callbacks: {
    authorized({ request, auth }) {
      try {
        const { pathname } = request.nextUrl
        // ログイン後のみ表示出来るページのパスを指定します。
        if (pathname === '/protected-page') return !!auth
      } catch (err) {
        console.error(err)
      }
    },
    jwt({ token, trigger, session }) {
      if (trigger === 'update') token.name = session.user.name
      return token
    },
  },
}

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

ミドルウェアのエクスポートと設定

認証機能をmiddlewareとしてエクスポートし、特定のパス(APIやNext.jsの内部処理に使われるパスを除く)でこのミドルウェアを適用するように設定します。

src/contexts/middleware.ts
export { auth as middleware } from './auth'

export const config = {
  matcher: ['/((?!api|_next/static|_next/image|favicon.ico).*)'],
}

6. 動作確認を行うためのViewの設定

src/app/page.tsx
import { auth } from '@/contexts/auth'
import { SigninBtn } from '@/components/auth/signin/btn'
import { SignoutBtn } from '@/components/auth/signout/btn'

export default async function Home() {
  const session = await auth()
  if (!session?.user) return <SigninBtn provider="Google" />

  return (
    <>
      {session && (
        <>
          <p>ユーザーはログインしています。</p>
          <SignoutBtn />
        </>
      )}
    </>
  )
}

注)src/app/globals.cssの記載は全て削除した状態で試しています。

ログインボタンのコンポーネント

src/components/auth/signin/btn.tsx
import { signIn } from '@/contexts/auth'
import React from 'react'

export function SigninBtn({
  provider,
  ...props
}: { provider?: string } & React.ComponentPropsWithRef<'button'>) {
  return (
    <form
      action={async () => {
        'use server'
        await signIn(provider)
      }}
    >
      <button
        {...props}
        className="hover:bg-green-100 border-green-500"
      >
        <div className="ml-2" />
        <span>Googleでログイン</span>
      </button>
    </form>
  )
}

ログアウトボタンのコンポーネント

src/components/auth/signout/btn.tsx
import { signOut } from '@/contexts/auth'
import React from 'react'

export function SignoutBtn({
  provider,
  ...props
}: { provider?: string } & React.ComponentPropsWithRef<'button'>) {
  return (
    <form
      action={async () => {
        'use server'
        await signOut({ redirectTo: provider })
      }}
      className="w-full"
    >
      <button className="w-full p-0" {...props}>
        ログアウト
      </button>
    </form>
  )
}

7. 実装の確認

Googleでログインをクリックすると、Googleログイン画面へ遷移し、アカウントを選択すると、画像のようにログイン処理を行うことができました。
Image from Gyazo

ログアウトボタンを押すと、ログイン前の画面に戻ります。
Image from Gyazo

参照動画

こちらの動画を参考にしました。とてもわかりやすかったです。
https://www.youtube.com/watch?v=2xexm8VXwj8

Discussion