🔑

NextAuth.jsでNext.js13にGoogle認証機能を実装

2023/02/07に公開

はじめに

Next.js に NextAuth.js を使って Google アカウントでログイン・ログアウトする機能を実装します。
こちらに実装したサンプルコードを公開しています。

https://github.com/hayato94087/nextauth_google_provider

NextAuthとは❓

NextAuth.jsは、Next.js で認証機能を実装するためのライブラリです。

Next.jsプロジェクトを構築

Next.js プロジェクトを構築します。

terminal
$ yarn create next-app nextauth_google_provider --typescript --eslint --src-dir --import-alias "@/*"

プロジェクトのディレクトリに移動します。

terminal
$ cd nextauth_google_provider

NextAuth.jsをインストール

NextAuth.js をインストールします。

$ yarn add next-auth

Session Providerの設定

<SessionProvider> を使用することで、useSession() を通し Component 間で Session 情報を共有できます。src/pages/_app.tsx<SessionProvider> を設定します。

src/pages/_app.tsx
import '@/styles/globals.css'
import type { AppProps } from 'next/app'
import { SessionProvider } from 'next-auth/react'

export default function App({ Component, pageProps:{session, ...pageProps} }: AppProps) {
  return (<SessionProvider session={session}><Component {...pageProps} /></SessionProvider>);
}

NEXTAUTH_URLの設定

NextAuth.js を使用するには、NEXTAUTH_URL を環境変数に設定する必要があります。.env.local を作成し NEXTAUTH_URL を設定します。

.env.local
NEXTAUTH_URL=http://localhost:3000

[...nextauth].jsの作成

src/pages/api/auth/[...nextauth].ts を作成します。src/pages/api/auth/* への全てのリクエストは NextAuth.js の API が呼び出され処理されます。

ディレクトリを作成します。

terminal
$ mkdir -p src/pages/api/auth
src/pages/api/auth/[...nextauth].ts
import NextAuth, { NextAuthOptions } from 'next-auth';
import GoogleProvider from 'next-auth/providers/google';

type ClientType = {
  clientId: string;
  clientSecret: string;
};

const authOptions: NextAuthOptions = {
  providers: [
    GoogleProvider({
      clientId: process.env.GOOGLE_CLIENT_ID,
      clientSecret: process.env.GOOGLE_CLIENT_SECRET,
    } as ClientType),
  ],
  secret: process.env.NEXTAUTH_SECRET,
};

export default NextAuth(authOptions);

以下の 3 つの環境変数を設定する必要がありますが、後ほど設定します。

説明
GOOGLE_CLIENT_ID GoogleのOAuth2.0クライアントID
GOOGLE_CLIENT_SECRET GoogleのOAuth2.0クライアントシークレット
NEXTAUTH_SECRET シークレット

ログイン画面の実装

src/pages/login.tsx にてログイン画面を実装します。セッション情報を useSession() にて取得します。セッション情報が存在する場合はユーザー情報とログボタンを表示し、セッション情報が存在しない場合はログインボタンを表示します。

src/pages/login.tsx
import React from 'react';
import { useSession, signIn, signOut } from 'next-auth/react';
import { NextPage } from 'next';

const Login: NextPage = () => {
  // sessionには、以下のような値が入っています。
  // {
  //     "user":{
  //        "name":"John",
  //        "email":"john@examle.com",
  //        "image":"https://lh3.googleusercontent.com/a/AGNmyxZF7jQN_YTYVyxIx5kfdo3kalfRktVD17GrZ9n=s96-c"
  //     },
  //     "expires":"2023-04-01T00:29:51.016Z"
  // }
  const { data: session } = useSession();

  return (
    <>
      {
        // セッションがある場合、ログアウトを表示
        session && (
          <div>
            <h1>ようこそ, {session.user && session.user.email}</h1>
            <button onClick={() => signOut()}>ログアウト</button>
          </div>
        )
      }
      {
        // セッションがない場合、ログインを表示
        // ログインボタンを押すと、ログインページに遷移する
        !session && (
          <div>
            <p>ログインしていません</p>
            <button onClick={() => signIn()}>ログイン</button>
          </div>
        )
      }
    </>
  );
};

export default Login;

ローカル環境で実行

yarn dev でローカル環境で実行します。

terminal
$ yarn dev

http://localhost:3000/login にアクセスするとログインボタンが表示されます。

「Sign in with Google」をクリックして、ログインします。

エラーが表示されます。これは、認証情報が設定されていないためです。

認証情報の取得

NextAuth で Google の OAuth2.0 を使用するには、Google の OAuth2.0 の認証情報(クライアント ID とクライアントシークレット)が必要です。

取得方法の流れは以下の通りです。

  1. Google Developer Console にアクセスします。
  2. Google Cloud の利用について同意します。
  3. 新しいプロジェクトを作成します。
  4. OAuth の同意画面を作成します。
  5. アプリケーションを登録し認証情報(クライアント ID とクライアントシークレット)を取得します。

Google Developer Consoleにアクセス

Google Developer Console の認証情報ページにアクセスします。

Google Cloudの利用について同意

はじめて Google Cloud を使う場合のみ、Google Cloud の同意が求められます。

処理詳細

  1. 利用規約を確認します。
  2. 同意にチェックを入れます。

新しいプロジェクトの作成

既に作成済みのプロジェクトがある場合は、そのプロジェクトを選択します。
ない場合は、新しいプロジェクトを作成します。

処理詳細

  1. 「プロジェクトを作成」を押下します。

  1. 「プロジェクト名」は任意の名前を入力します。
  2. 「組織なし」のままにします。
  3. 「作成」を押下します。

OAuthの同意画面の作成

利用者に向けて開示する OAuth の同意画面を作成します。

処理詳細

  1. 「認証情報の作成」をクリックします。

  1. 「同意画面を設定」をクリックします。

  1. 「外部」を選択し、「作成」ボタンを押下します。

  1. アプリ情報に「アプリ名」「ユーザーサポートメール」、デベロッパー連絡先情報に「メールアドレス」を入力し「保存して次へ」をクリックします。

  1. 何もせず、「保存して次へ」をクリックします。

  1. 何もせず、「保存して次へ」をクリックします。

  1. 内容を確認し問題なければ「ダッシュボードに戻る」をクリックします。

認証情報の取得

認証情報(クライアント ID とクライアントシークレット)を取得します。

処理詳細

  1. 「認証情報を作成」をクリックします。

  1. 「OAuth クライアント ID」をクリックします。

  1. 「アプリケーションの種類」は「ウェブアプリケーション」を選択します。「名前」は任意の名前を入力します。

  1. 「承認済みの JavaScript 生成元」にhttp://localhost:3000を入力します。
  2. 「承認済みのリダクレイト URI」にhttp://localhost:3000/api/auth/callback/googleを入力します。

  1. 「クライアント ID」と「クライアントシークレット」をコピーします。

環境変数の設定

先程コピーした「クライアント ID」と「クライアントシークレット」を環境変数に設定します。

.env.local
NEXTAUTH_URL=http://localhost:3000
+GOOGLE_CLIENT_ID=263176362010-ut35otqp0qp3oq8676pjnlf359uk0tqf.apps.googleusercontent.com
+GOOGLE_CLIENT_SECRET=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

JWTのシークレットの取得

NextAuth.js は JWT を使用してセッションを管理します。JWT のシークレットは、JWT の署名に使用されます。JWT のシークレットは、ランダムな文字列である必要があります。JWT のシークレットを作成する方法を2つ記載します。

方法1:opensslコマンドを使用する

openssl コマンドを使用して JWT のシークレットを生成します。

$ openssl rand -base64 32
IHPcQI71tUBPOJ7jxkRhjKRv7Ak5nvnz9xCZEPBeN8U=

方法2:

方法 1 のコマンドが実行出来ない場合、Web アプリケーションで JWT のシークレットを生成できます。

https://generate-secret.vercel.app/32

設定

上記のいずれかの方法で取得した 32 桁の文字列をコピーし、環境変数に設定します。

.env.local
NEXTAUTH_URL=http://localhost:3000
GOOGLE_CLIENT_ID=263176362010-ut35otqp0qp3oq8676pjnlf359uk0tqf.apps.googleusercontent.com
GOOGLE_CLIENT_SECRET=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
+NEXTAUTH_SECRET=dbe2de07f88c3a9b60a4503bb1d015a8

動作確認

動作確認します。yarn dev で開発サーバーを起動します。

$ yarn dev

実行結果

yarn dev
yarn run v1.22.19
warning ../package.json: No license field
$ next dev
ready - started server on 0.0.0.0:3000, url: http://localhost:3000
info  - Loaded env from /Users/hayato94087/Work/nextauth-app_/.env.local

ブラウザでhttp://localhost:3000/loginにアクセスします。ログインボタンをクリックします。

「Sign in with Google」をクリックします。

アカウントを選択します。

ログインできました。

認証されたユーザーのみ閲覧できるページを作成

認証されたユーザーのみ閲覧できるページを作成する方法法を 2 つ紹介します。

  • 1 つ目は、クライアントサイドで閲覧可能な情報を制御する方法です。
  • 2 つ目は、サーバサイドでで閲覧可能な情報を制御する方法です。

クライアントサイドでの閲覧可能な情報の制御

ここでは、セッションを使い、クライアントサイドでの閲覧情報を制御する方法を紹介します。

具体的には以下になります。

  • useSession でセッションを取得します。
  • useSessionrequired オプションを true にし、認証されていない場合は NextAuth のログインページにリダイレクトします。

ソースコード

以下のコードで、クライアントサイドで Session を管理します。

  • セッションがある場合は、ユーザーの情報を画面に表示します。
  • セッションがない場合は、NextAuth のログイン画面にリダイレクトさせます。
src/pages/profile1.tsx
import { signOut, useSession } from 'next-auth/react';
import Image from 'next/image';
import { NextPage } from 'next';

const Profile1: NextPage = () => {
  // sessionには、以下のような値が入っています。
  // {
  //     "user":{
  //        "name":"Taro Yamada",
  //        "email":"taro@examle.com",
  //        "image":"https://lh3.googleusercontent.com/a/AGNmyxZF7jQN_YTYVyxIx5kfdo3kalfRktVD17GrZ9n=s96-c"
  //     },
  //     "expires":"2023-04-01T00:29:51.016Z"
  // }
  const { data: session } = useSession({ required: true });

  return (
    <>
      {
        // セッションがある場合は、プロファイルを表示する
        session && (
          <div>
            <h1>プロファイル</h1>
            <div>{session.user?.email}</div>
            {session.user?.image && (
              <div>
                <Image src={session.user?.image} alt="" width={96} height={96} />
              </div>
            )}
            <button onClick={() => signOut()}>Sign out</button>
          </div>
        )
      }
      {
        // セッションがない場合は、ログインページに遷移する
        !session && (
          <div>
            <p>You are not signed in.</p>
          </div>
        )
      }
    </>
  );
};

export default Profile1;

<Image> コンポーネントを使用するために、next.config.jsimages の設定を追加します。

next.config.js
/** @type {import('next').NextConfig} */
const nextConfig = {
  reactStrictMode: true,
  images: {
    domains: ['lh3.googleusercontent.com'],
  },
};

module.exports = nextConfig;

ソースコードの補足 : セッション管理

session の有無で表示するコンテンツをクライアントサイドで切り分けています。

  const { data: session } = useSession({ required: true });
  return (
    <>
      {
        // セッションがある場合は、プロファイルを表示する
        session && (
          <div>
            <h1>プロファイル</h1>
            <div>{session.user?.email}</div>
            {session.user?.image && (
              <div>
                <Image src={session.user?.image} alt="" width={96} height={96} />
              </div>
            )}
            <button onClick={() => signOut()}>Sign out</button>
          </div>
        )
      }
      {
        // セッションがない場合は、ログインページに遷移する
        !session && (
          <div>
            <p>You are not signed in.</p>
          </div>
        )
      }
    </>
  );

動作確認

サインインしていない状態でhttp://localhost:3000/profile1にアクセスすると、Google アカウントの認証ページにリダイレクトされます。

サインインしている状態でhttp://localhost:3000/profile1にアクセスすると、Google アカウントの情報が見えます。

サーバサイドでの閲覧可能な情報の制御

ここでは、セッションを使い、サーバサイドでの閲覧情報を制御する方法を紹介します。

具体的には以下になります。

  • getServerSession でセッションを取得します。
  • 認証されていない場合は、サーバ側で強制的にログインページにリダイレクトさせます。

ソースコード

以下のコードで、サーバサイドで Session を管理します。

  • セッションがある場合は、ユーザーの情報を画面に表示します。
  • セッションがない場合は、ログイン画面にリダイレクトさせます。
src/pages/profile2.tsx
import { signOut } from 'next-auth/react';
import { GetServerSideProps } from 'next';
import Image from 'next/image';
import { NextPage } from 'next';
import { getServerSession } from 'next-auth/next';
import { authOptions } from '@/pages/api/auth/[...nextauth]';

export const getServerSideProps: GetServerSideProps = async (context) => {
  // sessionには、以下のような値が入っています。
  // {
  //     "user":{
  //        "name":"Taro Yamada",
  //        "email":"taro@examle.com",
  //        "image":"https://lh3.googleusercontent.com/a/AGNmyxZF7jQN_YTYVyxIx5kfdo3kalfRktVD17GrZ9n=s96-c"
  //     },
  //     "expires":"2023-04-01T00:29:51.016Z"
  // }
  //
  const session = await getServerSession(context.req, context.res, authOptions);
  // 以下のように、getSessionを使うこともできますが、非推奨です。
  // https://next-auth.js.org/configuration/nextjs#unstable_getserversession
  // const session = await getSession(context);

  if (!session) {
    return {
      redirect: {
        destination: '/login',
        permanent: false,
      },
    };
  }

  // session データを用いてサーバサイドで処理を行います.
  // 例えば、userに紐づくデータをAPI経由で取得するなどの処理も可能です。
  // ここでは、何かしらAPIを呼び、ユーザーの誕生日を取得したと仮定し、birthdayを追加しています。
  const userItem = {
    name: session.user?.name,
    email: session.user?.email,
    image: session.user?.image,
    birthday: '2021-01-01',
  };

  return {
    props: userItem,
  };
};

// props type
type Props = {
  name: string;
  email: string;
  image: string;
  birthday: string;
};

// propsには、以下のような値が入っています。
// {
//   "name":"Taro Yamada",
//   "email":"taro@examle.com",
//   "image":"https://lh3.googleusercontent.com/a/AGNmyxZF7jQN_YTYVyxIx5kfdo3kalfRktVD17GrZ9n=s96-c"
//   "birthday":"2021-01-01"
// }
const Profile2: NextPage<Props> = (props) => {
  const { name, email, image, birthday } = props;

  return (
    <>
      <div>
        <h1>プロファイル</h1>
        <div>{name}</div>
        <div>{birthday}</div>
        <div>{email}</div>
        <div>
          <Image src={image} alt="" width={96} height={96} />
        </div>
        <button onClick={() => signOut()}>Sign out</button>
      </div>
    </>
  );
};

export default Profile2;

補足①︰getSessionについて

公式に記載の通り、getSession は使わずに、getServerSession を使用してセッション情報を取得します。

https://next-auth.js.org/configuration/nextjs#unstable_getserversession

src/pages/profile2.tsx
  const session = await getServerSession(context.req, context.res, authOptions);

補足②︰getServerSidePropsについて

getServerSideProps は、Next.js の機能で、サーバサイドで処理を行うための関数です。
getServerSideProps を使用することで、サーバサイドでセッション情報を取得し、画面に表示できます。
例えば、セッションがない場合はログイン画面に強制的にリダイレクトさせることができます。

src/pages/profile2.tsx
  if (!session) {
    return {
      redirect: {
        destination: '/login',
        permanent: false,
      },
    };
  }

補足③︰props

getServerSideProps で取得したセッション情報を、元になにかした処理を行い、クライアントサイドで表示するために、props として渡せます。
例えば、認証済みのユーザーに紐づくデータを API 経由で取得し、props で渡すことも可能です。

src/pages/profile2.tsx
  return {
    props: userItem,
  };

動作確認

サインインしていない状態でhttp://localhost:3000/profile2にアクセスすると、Google アカウントの認証ページにリダイレクトされます。

サインインしている状態でhttp://localhost:3000/profile2にアクセスすると、Google アカウントの情報が見えます。

APIの保護

NextAuth を利用し API を保護する方法を説明します。

getServerSession を利用し、セッション情報を取得することで、セッションの有無に合わせて API を保護できます。

保護されたAPIの作成

以下の API はセッションがない場合は、401 エラーを返します。

src/api/hello_auth.ts
import { authOptions } from '@/pages/api/auth/[...nextauth]';
import { getServerSession } from 'next-auth/next';
import type { NextApiRequest, NextApiResponse } from 'next';

type Data = {
  message: string;
};

const handler = async (req: NextApiRequest, res: NextApiResponse<Data>) => {
  const session = await getServerSession(req, res, authOptions);

  console.log(session);

  if (!session) {
    res.status(401).json({ message: 'You must be logged in.' });
    return;
  }

  return res.json({
    message: 'Success',
  });
};

export default handler;

動作確認

セッションがない場合は、You must be logged in.というメッセージが表示されます。


セッションがある場合は、Success というメッセージが表示されます。


認証情報の削除

最後に作成した認証情報を削除します。

Google Developer Console の認証情報ページにアクセスします。ゴミ箱の削除ボタンをクリックします。

「削除」をクリックします。

削除が完了しました。

まとめ

  • NextAuth.js を利用し Google アカウントでログイン・ログアウトする機能を実装しました。
  • ログイン画面、認証されたユーザーのみ閲覧できる画面を作成しました。
  • ユーザー認証を利用した API を作成しました。

参考

NextAuth.js の公式サイトです。
https://next-auth.js.org/

NextAuth.js を使った Google 認証機能を実装している動画です。これを見れば実装が出来ます。
https://www.youtube.com/watch?v=A5ZN--P9vXM

Goole 認証に関する NextAuth.js の公式ドキュメントです。
https://next-auth.js.org/providers/google

NextAuth.js の公式サンプルコードです。
https://github.com/nextauthjs/next-auth-example

Discussion