追記:有名なNextAuht.jsでも同じことができるのでそっちのほうがいいかも、、、。NextAuthは多機能すぎるのでこっちでもいいが、、、
zenn.dev
next-firebase-authの概要
・サーバ側でFirebaseのIDトークンの情報(Twitter認証していたらアイコンとかスクリーンネームが入ってる)が使える
・クライアント側ではconst user = useAuthUser でFirebaseのIDトークンの情報が使える
・firebase v9もbeta版では対応中
・一応、Next.js公式からリンクがある
GitHub - gladly-team/next-firebase-auth: Simple Firebase authentication for all Next.js rendering strategies
認証の流れ
- Firebase Auth(react-firebaseuiとかで実装する)でTwitte等で認証する。これでFirebaseのIDトークンが生成される。
- headerのauthorizationに入ってるFirebaseのIDトークンを取得。FirebaseのIDトークンに入ってるスクリーンネームなどを情報をカスタムIDトークンに入れる
- カスタムIDトークンをhttp-onlyのクッキーに入れる(setAuthCookies.js)
- サーバ側ではクッキーからIDトークンを取得することで情報を利用。
- トークンの検証にはverifyIdTokenを使うことができる(これ用にfirebase-adminを使っている)。APIを保護する(認証済みユーザ以外には使わせない)際などに利用できる(exampleではapi/example.jsに例がある)
- logout時はクッキーを削除する
トークンをクッキーに入れる理由
IDトークン(JWT)をlocalStorageに入れることもあるが、localStorageに入れると
- XSSに対して脆弱になる
(XSSされる状況でわざわざIDトークンを盗むとかしないだろという反論はある)
- Google Analyticsなど第三者が作成したScriptを読み込むことが多いが、それらからも読み取ることが可能になってしまう
(Googleはそんなことしないとは思うが、じゃあどのScriptまでは信用できるのか、第三者が作成するScriptを追加するたびに問題ないかソースコードを全部チェックするのかという問題)
ということらしい。
手順
インストール
git clone https://github.com/gladly-team/next-firebase-auth
cd next-firebase-auth
yarn add next-firebase-auth
yarn add firebase firebase-admin next react react-dom
exampleディレクトリでサンプル用のprojectがあるのでそれをいじっていく
設定
next-firebase-auth/example/.env.local.exampleを開いて
- NEXT_PUBLIC_FIREBASE_PUBLIC_API_KEY
- NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN
- NEXT_PUBLIC_FIREBASE_PROJECT_ID
- FIREBASE_PRIVATE_KEY
の4つをfirebaseのコンソール画面の中から探して埋める。
(左上の歯車クリックして、Firebase SDK snippetのところに書いてある)
他は埋めなくても動く。
※NEXT_PUBLIC_FIREBASE_DATABASE_URLはRealtimedatabaseを使う時に使うっぽい?
Firebase Authの設定画面でメールアドレスの認証をオンにする
実行
next-firebase-authのexampleディレクトリに戻り、
npm run dev
を実行する。
localhost:3000を開くと画面が立ち上がっているので、
Example: SSR + data fetching with ID token
とかをクリックして適当なメールアドレスを入力する。
Firebase Authのコンソール画面で見るとユーザができていると思う
なお、もう一度Example: SSR + data fetching with ID tokenを開くとYour favorite color is: が表示されるが、これはapi/example.jsを叩いてるだけなので毎回変わる
なお、api/example.jsではAPI保護のためのIDトークン検証を行っっている。
各ページの内容
static-auth-required-loader.js(Example: static + loader + data fetching with ID token)
クライアント側でデータを取得している。このページに遷移した際の挙動は以下の部分で指定している
whenUnauthedBeforeInit: AuthAction.SHOW_LOADER, whenUnauthedAfterInit: AuthAction.REDIRECT_TO_LOGIN, LoaderComponent: FullPageLoader,
- whenUnauthedBeforeInit : クライアントのJS SDKを初期化する前だった場合、一度ローディング画面を表示する設定。ローティング画面自体はLoaderComponentで指定している。
- whenUnauthedAfterInit : 初期化が終わっているが、ログインされていない場合にはログインページに遷移する設定。
SNS認証
メール認証からTwitter認証に変える場合は
components/FirebaseAuth.jsにある
変更前:
provider: firebase.auth.EmailAuthProvider.PROVIDER_ID,
変更後:
provider: firebase.auth.TwitterAuthProvider.PROVIDER_ID,
に変更する
他のSNSに変えたい場合はこちらのドキュメントを参照する
AuthProvider | JavaScript SDK | Firebase
ちなみにexample.jsはemail情報がないとサインインボタンを表示してしまうため、
各ページのHeaderコンポーネントを
変更前:
<Header email={AuthUser.email} signOut={AuthUser.signOut} />
変更後:
<Header email={AuthUser.id} signOut={AuthUser.signOut} />
に変えるとちゃんとサインアウトボタンが出る。
トラブルシューティング
ページを開いている途中で
Module not found: Can't resolve 'react-firebaseui/StyledFirebaseAuth'
がでたら
npm install react-firebaseui --save --legacy-peer-deps
でなおる
設定
utilsディレクトリのinitAuth.js内で以下を設定できる
- authPageURL : 未ログイン時のリダイレクト先
- appPageURL : ログイン成功時のリダイレクト先
- loginAPIpoint : ログイン用のendpoint
基本的な使い方
クライアント側でログインしているアカウント情報を取得する
import { useAuthUser, withAuthUser } from 'next-firebase-auth' const Demo = () => { const AuthUser = useAuthUser() return ( <div> <p>Your email is {AuthUser.email ? AuthUser.email : "unknown"}.</p> </div> ) } export default withAuthUser({ whenUnauthedAfterInit: AuthAction.REDIRECT_TO_LOGIN, })(Demo)
- AuthUserに今ログインしているユーザの情報が入ってる
- withAuthUserでルートとなるコンポーネントをラップする
- {whenUnauthedAfterInit: AuthAction.REDIRECT_TO_LOGIN,}をつけると未ログイン時はログインページに飛ぶ。ホームページなどログイン状態が関係ないページの場合は消して良い。
Cloud Firestoreとの連携方法
クライアント側
クライアント側でfirestoreからフェッチするやり方はここに書いてある
https://github.com/gladly-team/next-firebase-auth#getfirebaseclient--firebaseapp
サーバ側
サーバサイドでfirestoreからフェッチするやり方。
ログインしたユーザのみに見せたいページの時とかに使う。
withAuthUserを使うことで未ログイン時にはログインページに遷移する。
import { withAuthUser, AuthAction, withAuthUserTokenSSR, getFirebaseAdmin } from 'next-firebase-auth' export const getServerSideProps = withAuthUserTokenSSR({ whenUnauthed: AuthAction.REDIRECT_TO_LOGIN, })(async (context) => { const db = getFirebaseAdmin().firestore() const doc = await db.collection('example').get() // Firestoreから取得したデータを処理する (中略) // クエリ取得 console.log('query', context.query) // トークン検証済みのユーザ情報 console.log('AuthUser', context.AuthUser) return { props: { uid: context.AuthUser.id } } }) const MyLoader = () => <div>Loading...</div> export default withAuthUser({ whenUnauthedBeforeInit: AuthAction.SHOW_LOADER, whenUnauthedAfterInit: AuthAction.REDIRECT_TO_LOGIN, LoaderComponent: MyLoader, })(MainComponent)
ちなみに
・サーバ側でfirestoreからデータを取得
・ログインしていないユーザの場合、閲覧できないようにログイン画面へリダイレクト
のつもりで以下のようなコードを書くと
zenn.dev
にある脆弱性を引き起こすので注意。
※ getServerSideProps = withAuthUserTokenSSRのようにすると何もreturnせずにリダイレクトされる
import { getFirebaseAdmin } from 'next-firebase-auth' // ...other imports const Artist = ({artists}) => { return ( <ul> {artists.map((artist) => <li>{artist.name}</li>)} </ul> ) } export async function getServerSideProps({ params: { id } }) { const db = getFirebaseAdmin().firestore() const doc = await db.collection('artists').get() return { props: { artists: artists.docs.map((a) => { return { ...a.data(), key: a.id } }), } } } export default withAuthUser({ whenUnauthedAfterInit: AuthAction.REDIRECT_TO_LOGIN, })(Artist)