※参考記事: https://qiita.com/junkor-1011/items/38557ef4188749fe14f9
概要
やったこと:
- Next.js(TypeScript)にNextAuthを入れ、別途用意したCognitoのユーザープールと連携してログイン・ログアウトの機能が働くことまで確認した
- 少しやってみた程度で、あまり高度なことはやっていない
- なので、当然本番環境での実行などは全く考えていないので注意
動機、目的など:
- Next.js(TypeScript)でawsのcognitoを使った認証を試したかった
- cognitoを使うのはAWS上で環境構築を行う必要があるため
- とはいえ、他のプラットフォーム、プロバイダーを使う場合でも概ねそのまま通用するのが望ましい
- 既存のライブラリでデファクトスタンダードっぽいものがあればそれを使いたかった
- AWS上でやるということだけを考えるならAWS AmplifyのAuthを使うという選択肢もありそうだった。が、極力AWS、更にはAWS上の特定のサービスにがっちりくっついた構成は避けたかった
- →NextAuthに行き着く
- NextAuthは2021年末くらいにv3系からv4系にアップデートされており、最新版のv4系の記事が少ないため備忘録として書くことにした
- 検索して出てくる記事はv3系のものが多く、特にcognitoを使ったものはv4系で見つけるのが困難だった(執筆当時)
実行環境
- Ubuntu 20.04LTS on WSL2
- nodejs v16.14.2 + yarn 1.22.18
- (+ AWSアカウント)
- ap-northeast-1(東京リージョン)を使用
準備
cognitoのユーザープール準備
とりあえずなので、AWSマネジメントコンソールで手作業で行う。
(追記 2022-08-02)AWS CDK v2でユーザープールを作成する記事を書いたので、手作業よりcdkが良い場合はそちらを参考のこと。※そのままだと若干設定は変わってくるので注意
-
サービスの"Cognito"を選択し、"ユーザープールの管理"を開く
-
おそらく画面右上に出てくる"ユーザープールを作成する"を開いて作成画面に行く
↑今回はデフォルト設定を少しいじるだけなので、適当なプール名を入力したら"デフォルトを確認する"をクリックする -
アプリクライアントが必要になるので、画面左のメニューから"アプリクライアント"をクリックし、アプリクライアントを追加する
↑アプリクライアント名を適当に入力し、"アプリクライアントの作成"をクリックして作成する -
プールの作成が完了したら、"アプリの統合"の"アプリクライアントの設定"を編集する必要がある
-
公式の説明ページも頼りに、以下のように設定する
-
コールバックURLは
http://localhost:3000/api/auth/callback/cognito
とする- ローカル環境での開発用のものなので注意
-
許可されているOAuthフローは
Authorization code grant
にし、許可されているOAuthスコープはemail
,openid
,profile
を選択している- (選択している内容については正直、公式の例がこうしているからここでもそうしている、という程度なので、詳しいオプション内容については追って調査が必要)
また、Cognitoの認証を行う際に予め設定したドメインが必要になるので、"アプリの統合"の"ドメイン名"を開き、適当なドメインを設定しておく:
最後に、NextAuth側が作成したCognitoユーザープールを参照するためには、ユーザープールおよびアプリクライアントに関する情報をいくつか控えておく必要がある
- "全般設定"から、プールID:
ap-northease-1_XXXXXXXXXX
のような値をメモ-
https://next-auth.js.org/providers/cognito に書いてあるところの
issuer
に使う
-
https://next-auth.js.org/providers/cognito に書いてあるところの
- "アプリクライアント"の"詳細を表示"をクリックし、アプリクライアントIDとアプリクライアントのシークレットをそれぞれメモ
Next.jsのセットアップ
まずはNext.jsのプロジェクトを立ち上げる必要がある。
yarnを使う場合、
yarn create next-app <プロジェクト名> --typescript
のようにすれば、<プロジェクト名>
としたディレクトリ(パス)にNext.jsのプロジェクトが生成されている。
(詳細は公式のGetting Startを参照)
ついで、
yarn add next-auth
などとして、NextAuthを入れる。
念のため、yarnでインストールしたパッケージの主要なもの(dependencies)は以下の感じ:
- "next": "12.1.5"
- "next-auth": "4.3.3"
- "react": "17.0.2"
- "react-dom": "17.0.2"
なお、執筆時点(20224月下旬)で入るReactのバージョンは18.0.0だが、諸々の都合で17.0.2にしたかったので今回の検証では自分で入れ直している。
また、本質的でないがpages
とstyles
も別途src
ディレクトリを作成し、その下に移している。
next-auth用のapi設定
(exportしない)Nextjsはpages/api/
以下のソースによってAPI機能を使うことができるが、
NextAuthはapi/auth/auth/***
といったAPIを使用して動作するため、pages/api/auth/[...nextauth].ts
というファイルを作成・編集する必要がある。
Cognitoを使う場合、最低限以下のような記述が必要:
import NextAuth from 'next-auth';
import CognitoProvider from 'next-auth/providers/cognito';
export default NextAuth({
providers: [
CognitoProvider({
clientId: process.env.COGNITO_CLIENT_ID || '',
clientSecret: process.env.COGNITO_CLIENT_SECRET || '',
issuer: process.env.COGNITO_ISSUER,
}),
],
});
(公式の説明も参照)
ポイントとしては、process.env.***
によって環境変数を読み込ませている。
また、clientId
とclientSecret
は必須項目なので、ナイーブに書くとTypeScriptの型関連のエラーが出る。ワークアラウンドとして、process.env.COGNITO_*** || ''
のような書き方をすることで、とりあえずstringとして値が設定されるようにしている。
ローカルで動作させる際(今回)はプロジェクトのトップのディレクトリ上に.env.local
を作成し、そこに適宜必要な値を記入する.
COGNITO_CLIENT_ID=XXXXXXXXXXXXXXXXXXXXXXX
COGNITO_CLIENT_SECRET=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
COGNITO_ISSUER=https://cognito-idp.ap-northeast-1.amazonaws.com/ap-northeast-1_XXXXXXXXXXX
NEXTAUTH_URL=http://localhost:3000
NEXTAUTH_SECRET=secret-string
COGNITO_***
となっている3つは、先程cognitoの設定画面で控えておいた3つの値を使うことで埋められる。
CLIENT_ID
とCLIENT_SECRET
はアプリクライアントのIDとシークレット、ISSUER
は使用するリージョン名とプールIDの値から組み立てる。
また、個人的にハマったところだが、それ以外にも設定しないとErrorやWarningが出る部分がある。
(詳しいところは公式のEnvironments Variablesのあたりを参照)
まず、NEXTAUTH_SECRET
(string)が無いとエラーが発生して動作しない。
本番環境だと取り扱いが重要な項目だと思われるが、とりあえず手元で簡単に動かす分には適当な文字列を.env.local
に入れておく。
また、NEXTAUTH_URL
も設定しないと
[next-auth][warn][NEXTAUTH_URL]
ext-auth.js.org/warnings#nextauth_url
のような警告が多数出るので、動作環境(=localhost)の情報を書いておく。
次に、ページに認証をかけるための記述をしていく。
まず、pages/_app.tsx
を編集して、例えば以下のような形にする
import '../styles/globals.css';
import { SessionProvider } from 'next-auth/react';
import type { AppProps } from 'next/app';
const MyApp = ({ Component, pageProps: { session, ...pageProps } }: AppProps) => {
return (
<SessionProvider session={pageProps.session} refetchInterval={5 * 60}>
<Component {...pageProps} />
</SessionProvider>
);
};
export default MyApp;
本質的な変化はもともとあったComponent
をNextAuthのSessionProvider
で挟んでいるところ。
(↑はここを参考に書いている)
次に、認証が必要なページとして、pages/protected.tsx
を作成する。
必要な処理はセッション情報の取得、サインイン、サインアウトあたりになるので、
- https://next-auth.js.org/getting-started/client#usesession
- https://next-auth.js.org/getting-started/client#signin
- https://next-auth.js.org/getting-started/client#signin
あたりを参照しつつ、例えば以下のような感じで作成する:
import {useSession, signIn, signOut} from "next-auth/react";
import type React from 'react';
const Home: React.VFC = () => {
const { data: session, status } = useSession();
if (status === 'loading') {
return null;
}
if (session) {
return (
<>
Signed in as {session?.user?.email} <br/>
<button onClick={() => signOut()}>Sign Out</button>
</>
);
}
return (
<>
Not signed in <br/>
<button onClick={() => signIn()}>Sign in</button>
</>
)
}
export default Home;
参考までに、
- ログイン・ログアウト用のボタン
- ログイン時はログイン情報としてメールアドレスを表示
といった機能にしている。
動作
yarn build
yarn start
のようにするか、より簡易的で良ければ
yarn dev
のようにすることで、普通の環境ではlocalhost:3000
にnext.jsのアプリが立ち上がっている。
今回認証の対象として作ったページは、 http://localhost:3000/protected
でアクセスできる。
Sign in with Cognito
を押すと、今回は特にカスタマイズなどしていないので、AWSが用意したサインイン用のUIが表示される。(Sign upからログイン用のユーザーを作れる)
適当なユーザーを作ってログインすると、
のようにログイン情報としてメールアドレスが表示され、
Sign Out
を押すとログイン前の状態に戻る.
改善点など
- セッション情報としてメールアドレスを出しているが、実はログインユーザー名など、今回作成したソースではいろいろな必要そうな情報が取得できていない。
-
https://next-auth.js.org/configuration/callbacks を参考に、
jwt
およびsession
コールバックを記述していくことで可能そうであることまでは大体確認できている。(ログイン時に取得できるprofile情報からcognito:username
といったものを取得する必要がありそう) - とはいえ、書き方のお作法的な部分だったり、あるいはセキュリティ的な観点で注意点があるかもしれないので、引き続き調べている必要がある。
-
https://next-auth.js.org/configuration/callbacks を参考に、
- cognitoというよりほぼNextAuthの使い方の問題になりそうな気がしているが、expiredしたセッション情報の更新といった実用上必要な機能の実現には色々と調査が必要
参考
- 公式
-
https://zenn.dev/tatsurom/articles/next-auth-cognito
- NextAuthのバージョンがv3系列なので読み替えが必要