本文書ではNext.jsの専用認証ライブラリであるNextAuth.jsの実装方法について解説を行なっています。NextAuth.jsを利用することでユーザはGoogle, Slackなどのクラウドサービス, LINE, Facebook, TwitterなどのSNSサービスの登録済みのアカウントを利用して認証を行うことができます。設定もシンプルなので短時間で認証機能を実装することができます。

Next.jsではさまざまなクラウド/SNSサービス(Providers)をサポートしていますが本文書ではユーザの所有率が最も高いGoogleを利用して設定、動作確認を行っていきます。Next.jsプロジェクトの作成からGoogleのアカウント側の設定、データベースの連携まで幅広い内容を網羅しています。データベースの連携ではPrismaを利用しているのでまだ利用したことがない人、知らない人もいるかもしれませんが本書の範囲内であれば難しい設定もないので安心してください。NextAuth.jsはクラウド/SNSサービスを利用したOauth認証の他にパスワード認証などもありますが本書で扱うのはOauth認証です。

Next.jsの基本的な内容については下記の文書で公開しています。

NextAuth.jsとは

NextAuth.jsはNext.jsの認証に特化したライブラリです。本書ではOauthを利用した認証の設定方法について解説していますがOauth認証にのみ特化したライブラリではないので従来のメールアドレスとパスワード認証、メールアドレスを使ったパスワードレス認証(Magic Link送信)にも対応しています。

本文書の公開当時はNextAuth.jsという名前でNext.jsに特化した認証ライブラリでしたが現在Auth.jsが開発中でNext.jsだけではなくSvelteKit, SolidStartでも利用することができます。
fukidashi

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

NextAuth.jsの動作確認を行うためにNext.jsのプロジェクトの作成を行います。npm create-next-app@latestコマンドを実行するとプロジェクトの名前を聞かれるので任意の名前をつけてください。


 % npx create-next-app@latest
Need to install the following packages:
  create-next-app
Ok to proceed? (y) y
✔ What is your project named? … nextjs-nextauth

プロジェクト作成後に作成されるnextjs-nextauthフォルダに移動してnpm run devコマンドを実行すると開発サーバが起動します。


 % cd nextjs-nextauth
 % npm run dev

> dev
> next dev

ready - started server on 0.0.0.0:3000, url: http://localhost:3000
event - compiled client and server successfully in 2.4s (171 modules)
wait  - compiling / (client and server)...
event - compiled client and server successfully in 486 ms (189 modules)

NextAuth.jsのインストールと初期設定

プロジェクトの作成が完了したらnext-authライブラリのインストールを行います。


 % npm install --save next-auth

next-authのインストール完了後にプロジェクトフォルダ直下のpages/apiフォルダに下にauthフォルダを作成して[…nextauth].jsファイルを作成します。作成したファイルには下記のコードを記述します。コードの中身はNextAuth.jsのドキュメントに記載しているコードをコピー&ペーストしています。デフォルトではGithubのプロバイダーの情報が記載されており環境変数のGITHUB_ IDとGITHUB_SECRETが取得できれば認証機能を利用することができます。


import NextAuth from 'next-auth';
import GithubProvider from 'next-auth/providers/github';

export default NextAuth({
  // Configure one or more authentication providers
  providers: [
    GithubProvider({
      clientId: process.env.GITHUB_ID,
      clientSecret: process.env.GITHUB_SECRET,
    }),
    // ...add more providers here
  ],
});

NextAuth.jsのドキュメントのREST APIのページでは/api/auth/providersにGETリクエストを送信すると提供するOAuthのサービスプロダイバーと各サービスのURLなどの詳細な情報が表示されるということなので試しに実行してみましょう。

アクセスするURLは以下です。


http://localhost:3000/api/auth/providers
HTTPリクエストを送信したい場合、エディターにVisual Studio Codeを利用している場合には拡張機能のREST Clientを利用すると簡単です。REST Clientを利用しない場合はPostmanやcurlコマンドを利用してください。
fukidashi

[…nextauth].jsにはサービスプロバイダーにGithubの設定のみ行なっているので戻される結果はGithubの情報のみ表示されます。


HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
ETag: "b3-hb/zqixMhiKCJHOCn33BK3aR5OA"
Content-Length: 179
Vary: Accept-Encoding
Date: Tue, 28 Dec 2021 01:06:16 GMT
Connection: close

{
  "github": {
    "id": "github",
    "name": "GitHub",
    "type": "oauth",
    "signinUrl": "http://localhost:3000/api/auth/signin/github",
    "callbackUrl": "http://localhost:3000/api/auth/callback/github"
  }
}

ブラウザから直接/api/auth/providersにアクセスすることも可能です。

callbackへのアクセス
callbackへのアクセス

結果に含まれいるsigninUrlのURLであるhttp://localhost:3000/api/auth/signin/githubにアクセスすると”Sign in with Github”の文字列が入ったボタンが表示されます。

GitHubへのサインイン画面
GitHubへのサインイン画面

“Sign in with GitHub”をクリックします。クリックすると画面には”Try signing in with a diffrenct account”というエラーメッセージが表示されます。メッセージが表示されるのは[…nextauth].jsで記述したコードのclientIdとclientSecretに値が設定されていないためです。この値はGithubの場合はGithub、他のサービスプロバイダーなら他のサービスプロバイダーの管理画面から情報を取得する必要があります。この値を取得するためにはNextAuth.jsを実装する開発者がそれぞれのサービスのアカウントを保持しOauthのサービスを利用するための許可を持っている必要があります。

エラーメッセージが表示
エラーメッセージが表示

[…nextauth].jsファイルには複数のプロバイダーを設定することができるのでGoogleのプロバイダー情報を追加します。


import NextAuth from 'next-auth';
import GithubProvider from 'next-auth/providers/github';
import GoogleProvider from 'next-auth/providers/google';

export default NextAuth({
  providers: [
    GithubProvider({
      clientId: process.env.GITHUB_ID,
      clientSecret: process.env.GITHUB_SECRET,
    }),
    GoogleProvider({
      clientId: process.env.GOOGLE_CLIENT_ID,
      clientSecret: process.env.GOOGLE_CLIENT_SECRET,
    }),
  ],
});

設定後再度http://localhost:3000/api/auth/providersにGETリクエストを送信するとgithub, googleの2つの情報が表示されることがわかります。


HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
ETag: "165-RRH7+9incHVEOXnstaYTlU/D4jM"
Content-Length: 357
Vary: Accept-Encoding
Date: Tue, 28 Dec 2021 01:32:43 GMT
Connection: close

{
  "github": {
    "id": "github",
    "name": "GitHub",
    "type": "oauth",
    "signinUrl": "http://localhost:3000/api/auth/signin/github",
    "callbackUrl": "http://localhost:3000/api/auth/callback/github"
  },
  "google": {
    "id": "google",
    "name": "Google",
    "type": "oauth",
    "signinUrl": "http://localhost:3000/api/auth/signin/google",
    "callbackUrl": "http://localhost:3000/api/auth/callback/google"
  }
}

github, googleのどちらからのsinginUrlにブラウザからアクセスしてください。サインインのボタンが複数になっていることがわかります。複数のプロバイダーの設定を行なっている場合にはユーザは利用したいサービスを選択してサインインすることが可能となります。

複数サービスのサインインボタン
複数サービスのサインインボタン

ここまでの動作確認で[…nextauth].jsにプロバイダーの設定を行うとサインインするためのURLが提供されそのURLにアクセスするとサインインを行うための画面が表示されるということまではわかりました。

次はGoogleプロバイダーを利用した場合のclientId, clientSecretの取得方法の手順を説明し、取得後の設定を行っていきます。

Google Providerの場合

Google Providerのみの認証だけ提供する場合は[…nextauth].jsファイルを下記のように更新します。


import NextAuth from 'next-auth';
import GoogleProvider from 'next-auth/providers/google';

export default NextAuth({
  providers: [
    GoogleProvider({
      clientId: process.env.GOOGLE_CLIENT_ID,
      clientSecret: process.env.GOOGLE_CLIENT_SECRET,
    }),
  ],
});

Googleのアカウントを利用してサインインを行うためにはclientId、clientSecretが必要となります。Googleプロバイダーを利用する場合はGoogle Cloud Platform(GCP)からそれらの値を取得する必要があります。GCPの中で行うことはOAuthクライアントIDの取得と同意画面の設定、リダイレクトURLの設定です。

cliendIdとclientSecretの取得はFirebaseのAuthenticationからも行うことができます。その方法については付録で説明しています。
fukidashi

Google Cloud Platfom(GCP)の設定

GCPはGoogleのサービスなのでサービスを利用するNextAuth.jsを実装する開発者がGoogleアカウントを持っている必要があります。

https://console.cloud.google.com/にアクセスするとGCPのログイン画面が表示されるのでログインを行ってください。

本文書ではGoogle Cloud Platformを利用するのが初めての人を想定して手順を確認していきます。今回の認証の動作確認を行うために費用が発生することはありません。

GCPにログインするとようこそ画面が表示されるので利用規約を確認して問題がなければチェックを行って、”同意して続行”ボタンをクリックしてください。”最新情報をメールで通知”は必要でなければチェックする必要はありません。

利用規約の確認画面
利用規約の確認画面

Oauthの設定は認証に関する設定なので左側のサイドバーメニューからAPIとサービスを選択して認証情報を選択します。

認証情報を選択
認証情報を選択

認証情報の画面が表示されるのでプロジェクトを作成するために画面右側にある”プロジェクトを作成”をクリックしてください。

認証情報画面
認証情報画面

プロジェクトには名前をつける必要があります。自動で名前が表示されるのでそのまま利用することができますし、任意の名前をつけることができます。

プロジェクトの作成画面
プロジェクトの作成画面

プロジェクト名を変更して”作成”ボタンをクリックします。

プロジェクト名の変更
プロジェクト名の変更

プロジェクトの作成が完了すると認証情報の画面が表示されます。

プロジェクトの作成完了
プロジェクトの作成完了

画面上には”必ず、アプリケーションに関する情報を使用してOAuth同意画面を構成してくださ”と表示されているので気になるところですがOAuth2.0のクライアントIDを設定するため上部にある”認証情報を作成”をクリックしてください。

OAuthクライアントIDの選択
OAuthクライアントIDの選択

残念ながらOAuthクライアントIDを選択すると”OAuthクライアントIDを作成するためには同意画面で設定が必要”というメッセージが表示されるので”同意画面を設定”ボタンをクリックします。

同意画面作成に関するメッセージ
同意画面作成に関するメッセージ

OAuthクライアントIDの情報を作成するためには同意画面の設定が必須であることがわかりました。

OAuth同意画面が表示されるのでUser Typeでは外部を選択します。

同意画面のUser Typeの選択
同意画面のUser Typeの選択

次の画面ではアプリ情報、アプリのドメイン、デベロッパーの連絡先情報など必須情報のみ入力していきます。アプリのロゴは必須ではありませんがどこに表示されるのか確認するため試しに選択してみました。アプリ情報は後ほど確認でユーザのサインイン画面で表示されることがわかりますが画像の表示場所はわかりませんでした。

OAuth同意画面の入力
OAuth同意画面の入力

次にスコープの画面が表示されます。今回は何も設定せずそのまま進むため”保存して次へ”ボタンをクリックします。

スコープ画面
スコープ画面

メールアドレスを使ってテストユーザを追加することができますが”保存して次へ”ボタンをクリックします。

テストユーザーの設定画面
テストユーザーの設定画面

最後にこれまでの設定の流れの中で設定した概要が表示されるので確認してください。

概要画面
概要画面

同意画面の作成が完了したのでダッシュボードに戻り再度認証情報を作成するためOAuthクライアントIDを選択します。

OAuthクライアントIDを選択
OAuthクライアントIDを選択

同意画面の作成が完了している場合はOAuthクライアントIDの作成画面が表示されます。

アプリケーションの種類を選択する必要があるのでウェブアプリケーションを選択します。名前は任意の名前をつけられますがそのまま利用しています。承認済みのJavaScript生成元のURLに動作確認を行なっている開発サーバのURL(http://localhost:3000)を入力します。承認済みのリダイレクトURLにはhttp://localhost:3000/api/auth/providersに対してGETリクエストを送信した際に取得したcallbackUrl(http://localhost:3000/api/auth/callback/google)を入力します。入力が完了したら”生成”ボタンをクリックしてください。

OAuthクライアントIDの作成画面
OAuthクライアントIDの作成画面

作成が完了すると画面上にクライアントIDとクライアントシークレットの情報が表示されます。

取得したIDとシークレット
取得したIDとシークレット

これらの情報をプロジェクトフォルダの直下に.env.localを作成してコピー&ペーストを行います。下記の値は利用できないので各自自分で取得を行なってください。


GOOGLE_CLIENT_ID=645459399876-1ovj7joIhdj9626hvsg366va12dkku3o.apps.googleusercontent.com
GOOGLE_CLIENT_SECRET=GOCSPX-J2H0ykqevms_U8zljC50uvn_7f

設定完了後は.env.localファイルを作成したので開発サーバの再起動を行なってください。

再起動後にhttp://localhost:3000/api/auth/signin/googleに行い”Sign in with Google”をクリックしてください。

googleのみのサインイン画面
googleのみのサインイン画面

先程は”Try signing in with a different account”というメッセージが表示されましたが今回はメールアドレスの入力画面が表示されます。ログイン画面にはGCP(Google Console Platform)の同意画面の設定で設定した”NextAuthの認証テスト”も表示されていることが確認できます。

ログイン画面
ログイン画面
Googleにログインした状態でアクセスした場合にはアカウントの選択画面が表示されます。
fukidashi

ログインを行い認証が完了するとlocalhost:3000にリダイレクトされてNext.jsのデフォルトの初期ページが表示されます。

Next.jsの初期ページ
Next.jsの初期ページ

npm run devコマンドを実行したコンソールには下記のような警告メッセージが表示されます。


[next-auth][warn][NEXTAUTH_URL] 
https://next-auth.js.org/warnings#nextauth_url

[next-auth][warn][NO_SECRET] 
https://next-auth.js.org/warnings#no_secret

NEXTAUTH_URLの警告に対しては表示されているリンクにアクセスすると.envファイルにNEXTAUTH_URLを設定するように記載されているので.env.localに環境変数を追加します。


GOOGLE_CLIENT_ID=645459399876-1ovj7joIhdj9626hvsg366va12dkku3o.apps.googleusercontent.com
GOOGLE_CLIENT_SECRET=GOCSPX-J2H0ykqevms_U8zljC50uvn_7f
NEXTAUTH_URL=http://localhost:3000/

NO_SECRETの場合は[…nextauth].jsにsercretのオプションを追加して文字列を設定することで警告は消えます。本番環境ではランダムな文字列を設定する必要があります。設定する値についてはhttps://github.com/nextauthjs/next-auth-example/blob/main/.env.local.exampleに記述されている方法を参考にしてください。https://generate-secret.now.sh/32にアクセスするとランダムな数字が表示されるのでその値を利用することができます。


export default NextAuth({
  providers: [
    GoogleProvider({
      clientId: process.env.GOOGLE_CLIENT_ID,
      clientSecret: process.env.GOOGLE_CLIENT_SECRET,
    }),
  ],
  secret: 'secret',
});

サインインを行えましたが本当に認証できているのか気になるのでサインインを行なったGoogleアカウント側で確認を行います。Googleアカウント設定のセキュリティから”他のサイトへのログイン”を確認すると新たにNextAuthの認証テストが追加されていることがわかります。

Googleアカウントのセキュリティを確認
Googleアカウントのセキュリティを確認
GCPで設定を行なったGoogleアカウントではなくサインインを行なったGoogleアカウントです。アカウントが一つしかない場合は同じGoogleアカウントになる可能性もあります。
fukidashi

Session Providerの設定

サインインを行うことでNextAuth.jsが動作していることはわかりましたがこのままの設定ではユーザのアクセスの制限を行うことができません。NextAuth.jsではSessionにユーザ情報を保存して利用するのでSessionの中身を確認するための設定を行っていきます。

認証が完了するとSessionの中にProvider(今回はGoogle)から取得したユーザ情報が保存されることになります。Sessionの中身をアプリケーションからアクセスするためにはSessionProviderの設定が必要になります。pagesフォルダにある_app.jsファイルでSessionProviderの設定を行います。


import { SessionProvider } from 'next-auth/react';

function MyApp({ Component, pageProps: { session, ...pageProps } }) {
  return (
    <SessionProvider session={session}>
      <Component {...pageProps} />
    </SessionProvider>
  );
}

export default MyApp;

useSessionの確認

設定が完了するとNextAuth.jsが提供するuseSession Hookを利用することでコンポーネントからsession情報へのアクセスが可能となります。index.jsファイルを下記のように更新することでuseSessionによりsession情報の取り出すことができます。

useSession以外にもこれまでに説明していないsingIn関数とsignOut関数がnext-auth/reactからimportされています。signIn関数を実行するとサインインページにリダイレクトされます。signOut関数を実行するとサインアウトが行われます。


import { useSession, signIn, signOut } from "next-auth/react"

export default function Component() {
  const { data: session } = useSession()
  if (session) {
    return (
      <>
        Signed in as {session.user.name} <br />
        <button onClick={() => signOut()}>Sign out</button>
      </>
    )
  }
  return (
    <>
      Not signed in <br />
      <button onClick={() => signIn()}>Sign in</button>
    </>
  )

認証が正常に完了するとsessionにユーザ情報が保存されるので認証が完了するとユーザ名とSign Outのボタンが表示されます。sessionに値がない場合は画面上にはSign Inのボタンが表示されます。sessionに値があるかどうかでアクセス可能かどうかを決定します。

動作確認を行うためにサインインが行われていない状態でhttp://localhost:3000にアクセスします。サインインがおこなわれていないので”Sign in”ボタンが表示されます。

Sign Inのボタンの表示
Sign Inのボタンの表示

“Sign in”ボタンをクリックすると”Sign in with Google”が表示されます。利用するプロバイダーによって名前を変わります。

googleのみのサインイン画面
googleのみのサインイン画面

一度認証が完了して許可されている場合(Googleのアカウント管理でアカウントがアクセスできるアプリに登録されている場合)はそのままサインインが完了します。初めてアプリケーションに対してアクセスするGoogleアカウントの場合はGoogleアカウントへのログイン画面やアカウントの選択画面が表示されます。

サインインが完了するとGoogleアカウントに登録されているユーザ名とSign Outのボタンが表示されます。下記はGoogleアカウントの設定で名前がJohn Doeに設定されている場合です。Sign outを押すとまたSign Inの画面が表示されます。

サインインが完了した時の画面
サインインが完了した時の画面

ここまででGoogle Providerを利用した認証の実装が完了しました。NextAuth.jsの理解をさらに深めるために詳細を確認していきましょう。

sessionの中身

sessionの中にnameが保存されていることはわかりましたがその他にはどのような情報が含まれているのか確認していきます。ドキュメントを確認するとsessionの中にはuserオブジェクトとexpiresの時間が設定されると記述されています。


{
  user: {
    name: string
    email: string
    image: string
  },
  expires: Date 
}

それでは実際のsessionの中身を確認してみましょう。console.logを利用して表示します。


  if (session) {
    console.log(session);

サインインを行うかサインインをした状態でリロードを行うとブラウザのデベロッパーのコンソールにsession情報が表示されます。name, email, imageとexpiresが含まれていることがわかります。session情報はデフォルトではこれだけの情報しか含まれていません。Providerが提供するリソースにアクセスするためのaccess_tokenをsessionに保存することも可能でその場合はこれから説明するcallbacksで行います。


expires: "2022-01-27T05:01:34.145Z"
​user: Object { name: "John Doe", email: "johndoe@gmail.com", 
image: "https://lh3.googleusercontent.com/a/ABTXJxV4DEJgWmVIOdZ8tjcnIPiKQAg30Sn9QIEDGj=s96-c" }​​

sessionの中身を確認すると方法としてサインインした状態でhttp://localhost:3000/api/auth/sessionにアクセスしてもJSONでsessionの中身を確認することができます。

callbacksの確認

認証に関する処理の中で何か処理を追加したい場合に利用できるのがcallbacksです。callbacksの処理は[…nextauth].jsファイルに記述することができます。

callbacksがどのようなものか確認するために例えばSignInが行われたらコンソールに”サインイン”と表示させたい場合は下記のように行うことができます。signInのcallbacksではreturn trueを戻す必要があります。falseに変更するとAccess Deniedとなりアクセスを拒否されます。


export default NextAuth({
  providers: [
    GoogleProvider({
      clientId: process.env.GOOGLE_CLIENT_ID,
      clientSecret: process.env.GOOGLE_CLIENT_SECRET,
    }),
  ],
  callbacks: {
    async signIn({ user, account, profile, email, credentials }) {
      console.log('サインイン');
      return true;
    },
  },
callbacksで利用できる関数にはsignIn, redirect, session, jwtがあります。それぞれ決められたタイミングで実行されます。
fukidashi

設定を行いsignInが完了するとnpm run devコマンドを実行したコンソールに”サインイン”の文字列が表示されます。サインインした場合のみ表示されるのでブラウザのリロード等を行なってもcallbacksのsingInに設定した内容が実行されることはありません。

singIn以外のcallbacksも確認していきましょう。

sessionオブジェクトに情報を追加したい場合にはcallbacksのsessionを利用することができます。ユーザのidをsessionにも含めたいという場合には以下のように行うことができます。sessionの場合はsessionを戻す必要があります。


callbacks: {
  async session({ session, user, token }) {
    session.user.id = 1;
    return session;
  },
},

サインイン後にuseSession Hookを利用してsessionのオブジェクトの中身を見るとuserオブジェクトにidは1が追加されていることが確認できます。

Tokenの確認

Providerで認証が行われるとaccess_tokenやid_tokenも受け取ります。それらの内容はcalbacksのjwtの中で確認することができます。access_token, id_tokenの情報はaccountの中に含まれているので見てみましょう。


callbacks: {
  async jwt({ token, user, account, profile, isNewUser }) {
    console.log(`account:${JSON.stringify(account)}`);
    return token;
  },
},

サインインを行うとnpm run devコマンドを実行したコンソールにaccountの内容が表示されます。access_tokenやid_tokenは長いので省略していますがaccountの中にはproviderからid_tokenまでさまざまな情報が含まれています。


{
  "provider": "google",
  "type": "oauth",
  "providerAccountId": "102830692313943333333",
  "access_token": "XXXXXXXX.....",
  "expires_at": 1640678246,
  "scope": "https://www.googleapis.com/auth/userinfo.email openid https://www.googleapis.com/auth/userinfo.profile",
  "token_type": "Bearer",
  "id_token": "XXXXXX..."
}

id_tokenについてはJSON WEB TOKENなのでhttps://jwt.ioを使ってデコードすることができます。JWTはHeader, Payload, Signatureの3つで構成されており、Headerにはアルゴリズムやtypeが記述されています。Payloadにはsessionに含まれていたnameやemailだけではなくiss(issuer), aud(audience), sub(subject), localやgiven_name, family_nameなども確認することができます。興味がある人はぜひid_tokenをhttps://jwt.ioを利用してデコードしてどのような情報が含まれているか確認してください。

id_tokenとは異なりaccess_tokenを利用することでプロバイダーが提供するリソースにアクセスする際に利用することができます。アプリケーション内でaccess_tokenを利用するためにjwtとsession callbacksを利用してsessionにaccess_tokenを設定することができます。この設定によりuseSessionからaccessTokenを取り出して利用することができます。


callbacks: {
  async session({ session, user, token }) {
    session.accessToken = token.accessToken
    return session;
  },
  async jwt({ token, user, account, profile, isNewUser }) {
    if (account) token.accessToken = account.access_token;
    return token;
  },
},

accountはjwtのcallbacksが呼ばれた最初の時しか値が含まれていないのでif文で値がある時のみtokenにaccessTokenの値を設定しています。user, profile, isNewUserもaccountと同様に最初にjwt関数が呼ばれた時しか値が入っていません。

設定後sessionを確認するとuseSession Hookから取得したsessionの中にaccess_tokenを確認することができます。

サーバサイドでのsessionの確認

サーバサイドでsession情報を取得したい場合はuseSession Hookは利用できないのでgetSessionを利用します。


export async function getServerSideProps(ctx) {
  const session = await getSession(ctx);
//略
}

pageフォルダにabout.jsファイルを作成して新しいページを作成してサーバサイドで取得したデータをクライアント側にpropsで渡して表示する方法を確認します。サインインが完了している場合にはユーザの名前が表示され、サインインが完了していない場合にはNo Pageが表示されます。getSessionの引数にはctx(context)を入れる必要があります。


import { getSession } from 'next-auth/react';

export default function about({ user }) {
  if (user) {
    return <h1>{user.name}</h1>;
  }
  return <h1>No Page</h1>;
}

export async function getServerSideProps(ctx) {
  const session = await getSession(ctx);
  if (!session) {
    return {
      props: {},
    };
  }
  const { user } = session;
  return {
    props: { user },
  };
}

サーバサイド側でsessionの値がない場合にはリダイレクトすることもできます。下記のコードではサインインしていない状態で/aboutにアクセスするとリダイレクトされて/(ルート)の画面が表示されます。


import { getSession } from 'next-auth/react';

export default function about({ user }) {
  return <h1>{user.name}</h1>;
}

export async function getServerSideProps(context) {
  const session = await getSession(context);

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

  return {
    props: { user: session.user },
  };
}

データベースの設定

NextAuth.jsはデータベースは必須ではありませんがデータベースを利用することでサインインしたユーザ情報をデータベース内に保存することができます。デフォルトではデータベースに保存されるユーザ情報はid, name, email, emailVerified, imageですが他の情報を保存したい場合はテーブルに列を追加することもできます。

データベースを利用することで4つのテーブルが作成されます。Account, Session, User, VerificationTokenです。VerificationTokenテーブルはOAuth認証では利用しません。

複数のProviderの中から利用したいProviderを選択できたようにNextAuth.jsによって決められたデータベースではなく利用したいデータベースを選択することができます。しかしNextAuth.jsと連携するためにAdapterが必要となります。Adapterは自作することができますがNextAuthで公開しているものがあるのでそのAdapterを利用してデータベースとの連携を行います。

本文書ではAdapterにPrismaを利用します。PrismaはORM(オブジェクトリレーショナルマッピング)でPrismaを介するすることでMySQL, PostgresSQL, SQLiteさらにmongoDBをNextAuth.jsの情報の保管先のデータベースとして利用することができます。PrismaではSQLを記述することなくオブジェクトのメソッドを利用してデータベースの操作を行うことができ、接続するデータベースの種類を意識することなく同じオブジェクトのメソッドを利用することができます。テーブルの作成もスキーマファイルを作成してマイグレートコマンドで行うことができます。テーブル作成時にSQLを記述することはありません。

Prismaのインストール

NextAuth.jsでPrismaを利用するためにはprismaと@prisma/client以外にAdapterの@next-auth/prisma-adapterをインストールする必要があります。


 % npm install @prisma/client @next-auth/prisma-adapter

 % npm install prisma --save-dev

スキーマの作成

データベースに作成するテーブルのスキーマについてはNextAuth.jsのドキュメントに記載されているものを利用します。プロジェクトフォルダにprismaフォルダを作成しその下にschema.prismaファイルを作成してドキュメントの内容をコピー&ペーストします。本文書では簡易的に利用できるSQLiteを利用するためproviederをpostgresqlからsqliteに変更し、urlにはsqliteのファイルのパスを設定しています。SQLiteデータベースファイルはschema.prismaファイルと同じフォルダにdev.dbという名前で作成します。dev.dbを作成する際は中身は空で構いません。


datasource db {
  provider = "sqlite"
  url      = "file:dev.db"
}

generator client {
  provider        = "prisma-client-js"
}

model Account {
  id                 String  @id @default(cuid())
  userId             String
  type               String
  provider           String
  providerAccountId  String
  refresh_token      String?  @db.Text
  access_token       String?  @db.Text
  expires_at         Int?
  token_type         String?
  scope              String?
  id_token           String?  @db.Text
  session_state      String?
  oauth_token_secret String?
  oauth_token        String?

  user User @relation(fields: [userId], references: [id], onDelete: Cascade)

  @@unique([provider, providerAccountId])
}

model Session {
  id           String   @id @default(cuid())
  sessionToken String   @unique
  userId       String
  expires      DateTime
  user         User     @relation(fields: [userId], references: [id], onDelete: Cascade)
}

model User {
  id            String    @id @default(cuid())
  name          String?
  email         String?   @unique
  emailVerified DateTime?
  image         String?
  accounts      Account[]
  sessions      Session[]
}

model VerificationToken {
  identifier String
  token      String   @unique
  expires    DateTime

  @@unique([identifier, token])
}
Visuall Studio Codeを利用してPrismaを初めて利用する場合schema.prismaにコードを記述してもハイライトされません。拡張機能のPrismaをインストールするとコードがハイライトされるのでインストールをおすすめします。
fukidashi

作成したスキーマファイルを元にデータベースにテーブルを作成するためmigrateコマンドを実行します。実行するとエラーメッセージが表示されます。スキーマファイルはすべてのデータベースで共通の内容を記述できるわけではなくデータベースによって利用できる列のタイプが異なるため下記のようにエラーが表示される場合があります。エラーの原因は利用したスキーマファイルの内容がpostgresqlに対応しているためです。


 % npx prisma migrate dev
Prisma schema loaded from prisma/schema.prisma
Datasource "db": SQLite database "dev.db" at "file:dev.db"

Error: Schema parsing
error: Native type Text is not supported for sqlite connector.
  -->  schema.prisma:16
   | 
15 |   providerAccountId  String
16 |   refresh_token      String?  @db.Text
   | 
error: Native type Text is not supported for sqlite connector.
  -->  schema.prisma:17
   | 
16 |   refresh_token      String?  @db.Text
17 |   access_token       String?  @db.Text
   | 
error: Native type Text is not supported for sqlite connector.
  -->  schema.prisma:21

SQLiteでは@db.Textは利用することはできないので削除します。削除後にマイグレーションを再度実行するとマイグレーションの名前を聞かれるのでこことでは”init”としています。実行が完了するとprismaフォルダにmigrationsフォルダが作成されその下に日付_initという名前のフォルダが作成されさらにその下にmigration.sqlファイルが作成されます。この時点でデータベースへのテーブルの作成も完了しています。


 % npx prisma migrate dev
Prisma schema loaded from prisma/schema.prisma
Datasource "db": SQLite database "dev.db" at "file:dev.db"

✔ Enter a name for the new migration: … init
Applying migration `20211229004038_init`

The following migration(s) have been created and applied from new schema changes:

migrations/
  └─ 20211229004038_init/
    └─ migration.sql

Your database is now in sync with your schema.

✔ Generated Prisma Client (3.7.0 | library) to ./node_modules/@prisma/client in 225ms

作成されたmigration.sqlファイルにはテーブル作成のためのcreate tableのSQLが記述されています。すべてファイルの内容を表示すると多いのでUserテーブルのSQLのみ表示しておきます。スキーマファイルからmigrateコマンドによってSQLite用のSQL文が作成されることがわかります。


//略
CREATE TABLE "User" (
    "id" TEXT NOT NULL PRIMARY KEY,
    "name" TEXT,
    "email" TEXT,
    "emailVerified" DATETIME,
    "image" TEXT
);
//略

次にPrisma Clientの作成を行うために以下のコマンドを実行します。実行すると作成したPrisma Clientのimport方法など利用方法のメッセージも表示されます。


 % npx prisma generate
Prisma schema loaded from prisma/schema.prisma

✔ Generated Prisma Client (3.7.0 | library) to ./node_modules/@prisma/client in 591ms
You can now start using Prisma Client in your code. Reference: https://pris.ly/d/client
```
import { PrismaClient } from '@prisma/client'
const prisma = new PrismaClient()
```

作成したPrismaClientの設定とPrisma Adapterの設定を[…nextauth].jsファイルで行います。


import NextAuth from 'next-auth';
import GoogleProvider from 'next-auth/providers/google';
import { PrismaAdapter } from "@next-auth/prisma-adapter"
import { PrismaClient } from '@prisma/client'
const prisma = new PrismaClient()

export default NextAuth({
  adapter: PrismaAdapter(prisma),
  providers: [
    GoogleProvider({
      clientId: process.env.GOOGLE_CLIENT_ID,
      clientSecret: process.env.GOOGLE_CLIENT_SECRET,
    }),
  ],
  secret: 'secret',
});

これでデータベースを利用するための設定は完了です。データベース作成後にユーザのサインインを実行して問題がないことを確認してください。

データベースの確認

本文書ではTablePlusという名前のデータベース管理ソフトウェアGUIを利用してSQLiteのデータベースにアクセスしています。

TablePlusを利用してSessionテーブルを確認するとサインインが完了している場合はそのSessionの情報が書き込まれます。

データベースの内容を確認
データベースの内容を確認

SessionテーブルについてはサインアウトするとテーブルからSession情報が消去されます。User, Accountテーブルについてはサインインと同時に情報が書き込まれサインアウトしてもデータが消去されることはありません。

データベースへの列の追加

データベースを利用する前はcallbacksのsessionのuserにアクセスしても中身はありませんでしたがデータベースを作成するとデータベースから取得したuserデータが含まれるようになります。


callbacks: {
 async session({ session, user, token }) {
   console.log(user);
   return session;
},

サインインが完了している場合はデータベースから取得したユーザ情報が表示されます。


{
  id: 'bkxatlv5b008c1m1xdy423g8',
  name: 'John Doe',
  email: 'john0@gmail.com',
  emailVerified: null,
  image: 'https://lh3.googleusercontent.com/a/BATXAJx4DEJgWmvIOdZ8tjcnIPiKQAg306n9wQIEDGj=s96c',
}

id, name, email, image以外に電話番号を保存してアプリケーションで利用したいという場合どのようにデータベースへ列を追加し利用できるのかを確認していきます。

追加したい情報はそれぞれのアプリケーションによって異なる複数の場合もあるかと思います。動作確認のために電話番号を追加します。
fukidashi

データベースに列を追加するためにはschema.prismaファイルにmobileという名前の列情報を追加します。


//略
model User {
  id            String    @id @default(cuid())
  name          String?
  email         String?   @unique
  emailVerified DateTime?
  image         String?
  mobile         String?
  
  accounts      Account[]
  sessions      Session[]
}
//略

mobile列を追加後にmigrateコマンドを指定します。今回はnameオプションをつけて実行しています。


 % npx prisma migrate dev --name add_mobile_to_user

実行が完了してTablePlusを使ってデータベースを見ると新たにmobile列が追加されていることがわかります。

mobileを追加後エラーが発生する場合は一度開発サーバの再起動を行なってください。再起動後にサインインを行なってcallbacksのsessionのuserを確認するとmobile列が追加されていることがわかります。


{
  id: 'ckxr4nb3w0006ldm1h7i9oyjo',
  name: 'John Doe',
  email: 'bulltablet10@gmail.com',
  emailVerified: null,
  image: 'https://lh3.googleusercontent.com/a/BATXAJx4DEJgWmVIOdZ8tjcnIPiKQAg30Sn9wQIEDGj=s96c',
  mobile: null
}

sessionへの追加データの設定

callbacksのsessionのuserにmobileが表示されることはわかりましたがuseSession Hookを利用して取得したsessionにはmobileは含まれていません。アプリケーション内で利用するためにsession内に含めたい場合はcallbacksの中でsessionにmobileの値を設定します。


callbacks: {
    async session({ session, user, token }) {
      session.user.mobile = user.mobile;
      return session;
},

値はnullですがuseSessionから取得したsessionにmobileが含まれるようになります。

mobileへの値の挿入はフォームを作成するなどで行うことができますがここではNextAuth.jsの理解を深めるためeventsを利用して行なってみましょう。

callbacksと同様にeventsは[…nextauth].jsに設定することができます。例えばsignInが完了するとeventが発火してあらかじめ決めた処理を実行するといったことが可能になります。callbacksとは異なるeventsでは戻り値は必要ではありません。以下のようなイベントが準備されています。

  • signIn
  • signOut
  • createUser
  • updateUser
  • linkAccount
  • session

サインインが完了してユーザが作成される時に何か処理を行いたいという場合に利用できるcreateUserイベントを利用してmobileに値を設定してみましょう。

動作確認をする前に一度サインアウトを行い、テーブルに保存されているUser, Accountの情報を削除してください。これまでの動作確認とは異なるGoogleアカウントを利用する場合は削除の必要はありません。

[…nextauth].jsファイルにeventsを追加します。eventsのcreateUserの中でuser.idを元にユーザを検索してmobileの値を’090-1111-1111’に設定しています。


export default NextAuth({
  adapter: PrismaAdapter(prisma),
  providers: [
    GoogleProvider({
      clientId: process.env.GOOGLE_CLIENT_ID,
      clientSecret: process.env.GOOGLE_CLIENT_SECRET,
    }),
  ],
  callbacks: {
    async session({ session, user, token }) {
      session.user.mobile = user.mobile;
      return session;
    },
  },
  events: {
    createUser: async ({ user }) => {
      await prisma.user.update({
        where: {
          id: user.id,
        },
        data: {
          mobile: '090-1111-1111',
        },
      });
    },
  },
  secret: 'secret',
});

useSessionを使ってsessionの中身を見るとmobileが含まれていることを確認できます。


{
  id: 'ckxr4nb3w0006ldm1h7i9oyjo',
  name: 'John Doe',
  email: 'bulltablet10@gmail.com',
  emailVerified: null,
  image: 'https://lh3.googleusercontent.com/a/BATXAJx4DEJgWmVIOdZ8tjcnIPiKQAg30Sn9wQIEDGj=s96c',
  mobile: "090-111-1111"
}

このようにデータベース、callbacks. eventsを利用することでカスタムなユーザ情報をsessionに持たせることができます。ここまで読み進めてもらえればNextAuth.jsの理解も少し深まったかと思います。

【付録】FirebaseのAuthenticationを利用

FirebaseのAuthenticationを利用した場合のNextAuth.jsの設定の手順についても確認しておきます。

FirebaseのAuthenticationを利用するためには事前にプロジェクトの作成を行っておく必要があります。

プロジェクトの概要画面のAuthenticationまたは左側のメニューからAuthenticationをクリックすると下記のAuthenticationの画面が表示されます。”始める”ボタンをクリックしてください。

Authentificationの画面
Authentificationの画面

ログインプロバイダーの選択画面が表示されるのでGoogleをクリックしてください。

ログインプロダイバーの選択画面
ログインプロダイバーの選択画面

スイッチボタンをクリックして有効にするに変更をしてください。任意の名前のプロジェクトの公開名を入力し、プロジェクトのサポートメールを選択してください。プロジェクトの公開名はユーザがサインインする際の同意画面に表示されます。メールアドレスを選択したら”保存”ボタンをクリックしてください。

Googleのログインプロバイダの設定
Googleのログインプロバイダの設定

保存が完了するとログインプロバイダにGoogleが表示されます。ステータスが有効になることを確認できます。確認後、表示されているGoogleをクリックしてください。

ステータスの確認
ステータスの確認

ウェブSDK構成の項目を開くと以下のようにウェブクライアントIDをウェブクライアントシークレットが表示されます。保存ボタンをクリックします。

ウェブSDK構成からIDの確認
ウェブSDK構成からIDの確認

表示されていたクライアントIDとクライアントシークレットの値は[…nextauth].jsに設定を行います。


GOOGLE_CLIENT_ID=98617415367-bebp4c9j8uKtjgijv37kkne1tbberefn.apps.googleusercontent.com
GOOGLE_CLIENT_SECRET=G1CKPX-K6D_vwVcbOGCJmpP7KGINqa7c1ES
NEXTAUTH_URL=http://localhost:3000/

動作確認に利用するindex.jsファイルの中身は下記の通りです。


import { useSession, signIn, signOut } from 'next-auth/react';

export default function Component() {
  const { data: session } = useSession();
  if (session) {
    return (
      <>
        Signed in as {session.user.name} <br />
        <button onClick={() => signOut()}>Sign out</button>
      </>
    );
  }
  return (
    <>
      Not signed in <br />
      <button onClick={() => signIn()}>Sign in</button>
    </>
  );
}

.env.localファイルを更新したのでnpm run devで開発サーバを再起動してhttp://localhost:3000にアクセスします。”Sign in”ボタンが表示されるのでクリックします。

Sign Inのボタンの表示
Sign Inのボタンの表示

クリックすると画面上には”Sign in with Google”ボタンが表示されます。

googleのみのサインイン画面
googleのみのサインイン画面

”Sign in with Google”ボタンをクリックすると以下のエラーが表示されます。

承認のエラー画面
承認のエラー画面

エラーメッセージの通りFirebaseのAuthentificationの設定を行なったGoogleアカウントを利用してGoogle Cloud Console(GCP)でリダイレクトのURL(http://localhost:3000/api/auth/callback/google)を設定する必要があります。

GCPにログインするとGCPで作成されたプロジェクトが表示されるはずです。下記のプロジェクトはNextAuthでFirebaseで作成したプロジェクト名とは異なるのでプロジェクトの検索を行う必要があります。右上部にあるプロジェクト名をクリックすると検索画面が表示されます。

Google Cloud Platformの画面
Google Cloud Platformの画面

プロジェクトの先頭の文字列を入力すると候補が表示されるはずなのでFirebaseでのAuthenticationを設定したプロジェクトを選択してください。

プロジェクトの選択画面
プロジェクトの選択画面

プロジェクトが切り替わったら右側のメニューからAPIとサービスを選択して認証情報をクリックしてください。

認証情報のメニューを選択
認証情報のメニューを選択

OAuth2.0クライアントIDのところにWeb client(auto create by Google Service)が作成されているのでクリックします。

OAuth2.0クライアントの確認
OAuth2.0クライアントの確認

承認済みのリダイレクトURLにhttp://localhost:3000/api/auth/callback/googleを追加して保存してください。

承認済みのリダイレクトURLの確認
承認済みのリダイレクトURLの確認
リダイレクトURLを追加
リダイレクトURLを追加

これでGCPでの設定は完了です。

再度”Sign with in Google”をクリックすると以下のログイン画面が表示されます。FirebaseのAuthenticationの公開名の設定した”FirebaseのAuthentication”が表示されています。

Googleへのログイン画面
Googleへのログイン画面

アカウントを選択するとサインインが完了して以下の画面が表示されます。Googleアカウントのユーザ名も表示されていることが確認できます。

サインインが完了した時の画面
サインインが完了した時の画面

FirebaseのAuthenticationを利用した場合は同意画面の細かな設定などを行う必要がありませんでしたがFirebaseで設定を行なったユーザでGCPにアクセスしてcallbackurlの設定を行う必要があります。