Closed10

@auth0/nextjs-auth0 と @auth0/auth0-react の使い分け

Eringi_V3Eringi_V3

試したこと1

Next.jsを使っているので、Next.jsにより特化していそうな nextjs-auth0 から試してみる。

https://github.com/auth0/nextjs-auth0#getting-started をみながらAuth0側の設定を行う。
Application TypeにSingle Page ApplicationではなくRegular Web Applicationを選択するのがポイント。

ドキュメントどおりに進めると、ログイン機能ができた。

Cookieには多分SDK内部でセットされたappSessionという名前のセッションIDっぽいものがある。
LocalStorageは空でした。

Eringi_V3Eringi_V3

試してみたこと2

nextjs-auth0 でのアクセストークンの取得。

サーバーサイドでのアクセストークンの取得

ドキュメントによると、getAccessTokenという関数が提供されているのでそれを利用してみる。
https://auth0.github.io/nextjs-auth0/modules/session_get_access_token.html
getServerSidePropsの中で使用してみます。
引数にリクエストオブジェクトとレスポンスオブジェクトを渡す。
以下の通り、アクセストークンが取得できました。

クライアントサイドでのアクセストークンの取得

トークンをクライアントサイドで取得する方法がnextjs-auth0では提供されていないようです。
トークンそのものをCookieに載せてしまえばクライアントでトークンを扱うことはできてしまいますが、issueで「クライアントでトークンを使用しないほうがいいですよ」と言及されていました。
https://github.com/auth0/nextjs-auth0/issues/67#issuecomment-578869558

クライアントでトークンを使用するということは、JavaScriptからトークンにアクセスできるようにするということなので、Cookieにトークンを載せたとしてもhttpOnly属性をfalseにする必要があり、セキュリティ的なリスク(XSSによってトークンを盗まれる可能性)があります。
https://github.com/auth0/nextjs-auth0/issues/67#issuecomment-761203094

そのため、クライアントからAPIサーバーに対してリクエストするとき、Authorizationヘッダにトークンを乗せる必要がある場合、nextjs-auth0は適さないと理解しました。

Eringi_V3Eringi_V3

試してみたこと4

auth0-reactでのアクセストークンの取得。

サーバーサイドでのアクセストークンの取得

このSDKはNext.jsを意識して作られておらず、素のReact SPAでの使用を想定しているため、サーバーサイドでトークンを取得する方法はないです。

クライアントサイドでのアクセストークンの取得

  const { getAccessTokenSilently } = useAuth0();
  useEffect(() => {
    (async () => {
      try {
        const token = await getAccessTokenSilently()
        console.log({ token })
      } catch (e) {
        console.error(e)
      }
    })()
  }, [getAccessTokenSilently])

上記のコードでトークンを取得することができました。

Eringi_V3Eringi_V3

個人的な結論

クライアントから直接、認証が必要なAPIを呼びたい場合はauth0-reactを使う。
クライアントから認証が必要なAPIを呼ぶ必要がなく、サーバーサイドでのみ認証情報を扱えば済む場合はnextjs-auth0を使う。

Webアプリケーションでは認証が必要なAPIを使用する場面があって、画面遷移せずにそれらのAPIを呼んでUIを変化させたいケースがあると思うのでnextjs-auth0を使える場面ってなかなか少ないんじゃないだろうか。。
ちょっとnextjs-auth0が適するユースケースがあまり思いつかない

Eringi_V3Eringi_V3

疑問に思ったこと

auth0-react はCookieにもLocalStorageにもトークン保持してなくて、一体どこにトークンを保存しているのだろうか?
https://github.com/auth0/nextjs-auth0/tree/beta#comparison-with-auth0-react
↑にはトークンを直接ブラウザに保存してるって書いてあるように読み取れるけどどこに保存してるかわからなかった...ブラウザのどこ...?

auth0-react will store the user's ID Token and Access Token directly in the browser

Eringi_V3Eringi_V3

調べてみたらauth0-reactが依存しているauth0-spa-jsがiframeを介してトークンを取得しているみたいです。

この辺からソースコードを追ってみるとなんとなく流れが分かりそうです。
https://github.com/auth0/auth0-spa-js/blob/1da10cb344892b72471e5d709e32b68f26269a2e/src/Auth0Client.ts#L645

見えないiframe要素を作って、iframeの中でAuth0のサーバーと通信してメッセージを受信し、それに含まれるレスポンスからアクセストークンを取り出して返してくれているようです。
iframeでのメッセージ受信についてはこちら。
https://developer.mozilla.org/ja/docs/Web/API/Window/postMessage

トークンの取得が完了したあとはiframe要素を削除しています。
実際にChromeで確かめてみました。
ログイン状態でページをリロードしたところを録画しています。
https://gyazo.com/1f9c4e1690e876948c7820f5be58e6a3

iframe要素が現れたと思ったらすぐに消えました。

そして、一度取得されたトークンはインメモリのキャッシュに保存されるようです。
これならサードパーティーの悪意あるスクリプトが紛れ込んでいてもトークンを盗むのは厳しそうですね。

Eringi_V3Eringi_V3

余談

もうAuth0関係ないのですが、自分がAuth0の他に使用したことのあるIDaaSにAWS Cognitoがあります。

Amplifyライブラリを介してトークンを取得するのですが、Amplifyライブラリでは生のトークンをCookieに直接保存します。
そして、Cookieに保存されたトークンはhttpOnly属性がfalseです。
これは、サードパーティーのスクリプトがトークンを盗めることを意味します。
Amplifyライブラリを使用してアプリケーションに認証機能を組み込む場合、開発者は少なからずこのことは理解しておくべきだと感じました。
issueではトークンをhttpOnly: trueで保存しないことについて残念がっている人たちもいるようです。
https://github.com/aws-amplify/amplify-js/issues/3224

セキュリティ面ではAuth0に劣るAmplifyですが、良いところもあると思っています。
それは、クライアントサイドでもサーバーサイドでもトークンの取得が可能であることです。
AmplifyライブラリはSSRに対応しています。
https://aws.amazon.com/jp/blogs/mobile/ssr-support-for-aws-amplify-javascript-libraries/

クライアントでもサーバーでも同じインターフェースでトークンの取得ができるのがAmplifyの良さだと思っています。
前述の通り、Auth0ではnextjs-auth0を使用した場合はサーバーサイドでのみトークンの取得が可能、auth0-reactを使用した場合はクライアントサイドでのみトークンの取得が可能だったのでユニバーサルにトークンの取得を行うことは現状Auth0のSDKでは不可能である認識です。

このあたりは、セキュリティ的なリスクを承知でAmplifyを使うのか安全性を優先してAuth0を使用するのか開発者が要件に合わせて判断する必要がありそうですね。

このスクラップは2021/05/07にクローズされました