🐎

Next.js 13でAWS Cognitoを使用したGoogle認証を試す(NextAuth.jsを使って。)

2023/09/13に公開

やりたいこと

AWSのCognitoを使用してGoogleアカウントの認証を行いたい。使うフレームワークはNext.js 13とする。

環境

  • Windows 11
  • Next.js version 13.4.19
  • NextAuth.js version 4.23.1
  • TypeScript version 5.2.2
  • TailWindCSS

Google APIの設定から

Google Cloud でプロジェクトを作成する。作成したプロジェクトを選び、認証情報タブでOAuth2.0クライアントを作成する。
その後下記の表通りに設定する。

入力欄 入力内容
アプリケーションの種類 ウェブアプリケーション
名前 自分の好きな名前
承認済みのJavascript生成元 http://localhost:12000
承認済みのリダイレクトURL 空欄

これらを入力したら作成する。

AWS Cognitoの設定

ユーザプールの作成

ユーザプールの作成ボタンを押す。

認証プロバイダーの設定

下記の表のとおりにする。

入力欄 入力内容
プロバイダーのタイプ フェデレーテッドアイデンティティプロパイダ―
Cognito ユーザープールのサインインオプション Eメール
フェデレーティッドサインインのオプション Google

パスワードポリシー

入力欄 入力内容
パスワードポリシーモード カスタムでもデフォルトでもいい

多要素認証とユーザーアカウントの復旧

好みの設定でいい。

サインアップエクスペリエンスを設定

セルフサービスのサインアップ

ここでサインアップできるようにするか選択する。
いろいろ好きに設定する。

フェデレーテッドアイデンティティプロバイダーを接続

この設定でGoogleプロバイダーと連携する。

入力欄 入力内容
クライアントID Google APIの設定で確認したクライアントID
クライアントシークレット Google APIで確認したシークレットキ―
許可されたスコープ emailとか
Googleとユーザープール間でマッピング emailとemail

「アプリケーションを統合」の設定

入力欄 入力内容
ユーザープール名 なんでもいい
Cognitoドメイン 使えるやつなら何でもいい
最初のアプリケーションクライアント 秘密クライアント
アプリケーションクライアント名 なんでもいい
クライアントシークレット クライアントのシークレットを作成する
許可されているコールバックURL http://localhost:12000/api/auth/callback/cognito

この設定でユーザープールを作成する。

アプリケーションの統合の確認

作成したユーザープールを選択し、「アプリケーションの統合」タブを選択する。そして一番下の「アプリケーションクライアントのリストのアプリクライアントと分析」の最初のアプリケーションクライアントで入力した名前をクリックする。
クライアントIDとクライアントシークレットを確認する。また、ホストされたUIで編集ボタンを押して、IDプロバイダーにCognitoだけでなく、Googleも追加する。(自分はこの部分を行っていなかったためログインページにGoogleログインボタンが出なかった。)

Google APIの追加設定

OAuthクライアントIDの認証情報の追加設定

承認済みのリダイレクトURLに「アプリケーションを統合」の設定で決めたCognitoドメインを入力する。
例:https://XXXXXX.auth.ap-northeast-1.amazoncognito.com/oauth2/idpresponse

Next.js 13の設定 (App Router使用)

Next-Authの設定

next-authのインストール

npm install next-auth

route.tsxの作成

src/app/api/auth/[...nextauth]/route.tsxを作成し、下記のコードを入力する・

/src/app/api/auth/[...nextauth]/route.tsx
import NextAuth from 'next-auth'
import CognitoProvider from 'next-auth/providers/cognito'

const handler = NextAuth({
  secret: process.env.NEXTAUTH_SECRET,
  providers: [
    CognitoProvider({
    //本当は型定義したほうがいいけどmんどくさいので''を使う。
      clientId: process.env.COGNITO_CLIENT_ID ?? '',
      clientSecret: process.env.COGNITO_CLIENT_SECRET ?? '',
      issuer: process.env.COGNITO_ISSUER ?? '',
      checks: 'nonce',
      //checks: 'nonce'がないとサインインできるけどエラーが起きて別のアカウントで試せと言われる
    }),
  ],
})


export { handler as GET, handler as POST }

プロジェクトのルートディレクトリに.envファイルを作成する

/.env
COGNITO_CLIENT_ID = CognitoのクライアントID
COGNITO_CLIENT_SECRET = Cognitoのシークレットキー
COGNITO_ISSUER = https://cognito-idp.{cognitoのregion}.amazonaws.com/{ユーザープールID}
NEXTAUTH_URL=http://localhost:12000
//サインイン後のリダイレクトURL
NEXTAUTH_SECRET="ランダムな文字列"

NextAuth.tsxを作成

/src/providers/NextAuth.tsxを作成する。

/src/providers/NextAuth.tsx
'use client'

import { SessionProvider } from 'next-auth/react'
import React from 'react'
import { ReactNode } from 'react'

const NextAuthProvider = ({ children }: { children: ReactNode }) => {
  return <SessionProvider>{children}</SessionProvider>
}

export default NextAuthProvider

このファイルでSessionProviderをwrapしている。

layout.tsxの設定

NextAuthコンポーネントを/src/app/layout.tsxで読み込んでアプリのどこからでもログイン状態を確認できるようにする。

/src/app/layout.tsx
import './globals.css'
import { Inter } from 'next/font/google'
import NextAuthProvider from '@/providers/NextAuth'

const inter = Inter({ subsets: ['latin'] })

export const metadata = {
  title: 'Create Next App',
  description: 'Generated by create next app',
}

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang='ja'>
      <body className={inter.className}>
        <NextAuthProvider>{children}</NextAuthProvider>
      </body>
    </html>
  )
}

ログインボタンなどのコンポーネントの作成

/src/components/AuthenticateButton.tsx
"use client";
import {signIn, signOut} from "next-auth/react";
import React from "react";

// ログインボタン
export const LoginButton = () => {
    return (
        <button  onClick={() => signIn()} className="mt-4 bg-blue-500 hover:bg-blue-300 active:scale-90 transition-all duration-200 text-white py-2 px-4 rounded-full mx-auto">
        サインイン
        </button>
    );
};

// ログアウトボタン
export const LogoutButton = () => {
    return (
        <button className="mt-4 bg-red-500 hover:bg-red-300 active:scale-90 transition-all duration-200 text-white py-2 px-4 rounded-full mx-auto" onClick={() => signOut()}>
            サインアウト
        </button>
    );
};
/src/coponents/IsLoggedin.tsx
'use client'
import { useSession } from 'next-auth/react'
import React from 'react'
export default function isLoggedIn() {
  const { data: session } = useSession()

  if (!session) {
    return <p>ログインしていません。</p>
  }

  return (
    <div>
      {session.user ? `${session.user.email}としてログインしています。`  : '読み込み中...'}
    </div>
  )
}

page.tsxの設定

/src/app/page.tsx
'use client';
import React, { use } from 'react'
import { LoginButton, LogoutButton } from '@/components/AuthenticateButton'
import  IsLoggedin from '@/components/IsLoggedin'

export default function Home() {
    return (
    <main className='flex h-screen justify-center items-center'>
  <div className='text-center'>
    <h1 className='text-4xl font-bold'>Next Auth</h1>
    <IsLoggedin/>
    <LoginButton/>
    <LogoutButton/>
  </div>
</main>

  )
}

テストしてみる

URL http://localhost:12000 にアクセスする。
サインインできる!!

参考

https://zenn.dev/osusyami/articles/next-auth-hello
https://zenn.dev/tatsurom/articles/next-auth-cognito#next.js-設定
https://next-auth.js.org/getting-started/example
https://qiita.com/kage1020/items/8224efd0f3557256c541

つまづいたところ

  1. CognitoのクライアントIDなのかGoogleAPIのクライアントIDなのかが曖昧な部分があり、エラーが多発した。
  2. アプリケーションクライアントの統合を忘れてCognitoのボタンしか出ず、解決に時間を要した。
  3. Cognito側の問題なのだがサードパーティ製の認証を行おうとすると、nonce:dismatchというエラーが出てしまい、ログインはできるのだがリダイレクト先がエラーページになってしまい、その解決に時間がかかった。結局Next.jsのCognitoProviderにchecks:'nonce'を設定し解決した。

Discussion