認証方式の方針
Status: Accepted
要約
- 認証手段としてCookieベースの認証を採用します。
- ログイン時(自動ログインを含む)に端末認証を要求することで、モバイル端末の紛失や盗難リスクに対応します。
- 匿名サインアップや自動ログインにより、アプリの利便性を向上させます。
コンテキスト
アプリケーションを安全かつ利便的に使用するためには、ユーザが本人その人であることを確認する必要があります。 認証とは、ユーザがその人自身であることを確認する手段です。 通常はログイン機能として提供されます。
認証は、利用者が提示する認証要素を確認することで実現します。 認証要素には次のものがあります。
- 知識情報。その人自身しか知らない情報。「パスワード」「PINコード」や「秘密の質問」など。
- 所持情報。その人自身が保有する情報。「電話番号」「ハードウェアトークン」「クライアント証明書」など。
- 生体情報。その人自身の特徴。「指紋」「静脈」「声紋」や「顔の特徴」など。
一般的には、パスワードを用いた認証方式を採用するサービスが多いです。 しかし、昨今の高まるセキュリティリスクに対応するため、安全性が求められるサービスでは多要素認証を採用するケースも増えています。
ここでは、サンプルアプリケーションの認証方式について検討します。 目指す認証方式は次の通りです。
- アプリを安全かつ利便的に使用できること
- 他のプロジェクトでも参考にしやすいこと
サンプルアプリケーションの存在趣旨を踏まえ、他のプロジェクトでの参考のし易さを第一とします。 一般的に認証に関連して必要となるアカウント機能(アカウントのライフサイクルにまつわる機能群)は対象外とし、サインアップ・ログイン・ログアウトといった認証機能にのみ焦点を当てます。 また、プロジェクトによって異なる箇所の多い認証方式は検討対象外とします。
次の観点で認証方式について議論・調査を進めていきます。
- 認証手段
- サインアップ
- ログイン
- ログアウト
- 想定される脅威(攻撃手法)とその対応
- 想定される課題とその対応
議論
認証手段
OIDC vs Cookieベースの認証
OIDC(OpenID Connect)は、OAuth 2.0プロトコルを使用して構築された認証方式です。 Cookieベースの認証とは、パスワードを用いて認証し、CookieでセッションIDを受け取る一般的な認証方式です。
認証手段の検討にあたり、真っ先に検討の場に上がるのはOIDC(OpenID Connect)です。 OIDCは標準化された仕様を持つ優れた認証方式ですが、次の理由により検討対象外としました。
- OIDCを用いる場合、認証サーバ(IdP)に外部サービス(Firebase AuthenticationやAmazon Cognitoなど)を用いる場合が多い。外部サービスから開発ガイドやSDKが提供されており、実装する場合はそちらを参考にできる。
- Cookieベースの認証機能を備えた既存資産の流用を考えた場合、OIDCへの移行はコストがかかる。
よって、他のプロジェクトでの参考のし易さを第一とし、サンプルアプリケーションではCookieベースの認証を採用します。
アプリ自身の実装としてCookieは操作しますが、保存先や保存期間などの管理はネイティブに委ねています。 そのため、アプリではCookieを管理しません。
React NativeでCookieを扱う場合、Known Issues with fetch and cookie based authenticationの確認が必要です。 こちらについては、React NativeでCookieを扱う場合の懸念点についてに調査結果を纏めていますので参考にしてください。
端末認証
モバイルアプリの場合、Webアプリケーションとは異なり、端末の紛失や盗難といったセキュリティリスクがあります。 そうしたリスクへの対応として端末認証があります。 端末認証は、PINコード(またはパスコード)、指紋や顔の特徴などを用いて、ユーザが端末の所持者であることを認証します。 これにより、所持者以外が不正に端末を利用することを防げます。
このアプリでも、ログイン時に端末認証を要求することで、上記リスクに対応します。
サインアップ
匿名サインアップ
アプリへのサインアップ要求は、ユーザの心理的抵抗が大きいです。 「アカウント情報の登録が面倒」という理由からくるユーザの離脱は、大きな機会損失に繋がります。
そこで、このアプリでは匿名サインアップ機能を提供します。 ユーザはサインアップ操作なしに匿名アカウントを使用してアプリの利用を開始できます。 匿名サインアップは次の流れで行います。
- アカウントIDとパスワードをアプリケーションが自動作成
- 自動生成したアカウントIDとパスワードでサインアップ
- ログイン資格情報を端末内の安全な場所に保存
ログイン資格情報は、次回ログイン時に必要となる情報です。 ログイン資格情報の詳細については、ログインで検討します。
端末内の安全な場所の詳細は、ログイン資格情報の管理を参照してください。
パスワードは知識情報(その人自身しか知らない情報)に当たるので、アプリケーションが自動生成するのはおかしいのではという疑問が生じます。 このアプリでは、自動生成されたパスワード(から得たログイン資格情報)を知識情報ではなく所持情報(その人自身が保有する情報)として扱います。 他の例でいうと、パスワード管理サービス(1Passwordのような)を利用することで、ユーザはパスワードを記憶しなくて済みます。 これは実質的に、パスワードを所持情報として扱っていると言えるでしょう。
匿名アカウントと通常アカウント
匿名サインアップ後のアカウントは匿名アカウントとして扱います。
一方で、アカウント登録やアカウント連携が完了したアカウントを通常アカウントとして扱います。 開発リソース上の理由により、通常アカウントの導入は今回の開発範囲外とします。
ログイン
自動ログイン
頻繁なログイン操作の要求は、ユーザの利便性低下に繋がります。 またこういった要求が、単純な(安全性の低い)パスワードの利用やパスワードの使いまわしにも繋がります。 そもそもこのアプリの場合、匿名アカウント使用ユーザは自身の(自動生成された)パスワードを知らないので、パスワードを入力できません。
そこで、このアプリでは自動ログイン機能を提供します。 アプリ起動時に、端末内に保存されたログイン資格情報を用いて(前回ログインしたアカウントで)自動でログインします。
ログイン資格情報
ログイン資格情報の仕様について、次の案を検討しました。
- アカウントID + パスワード
- 認証トークン
ここでいう認証トークンとは、ログイン時にバックエンドが発行する文字列(トークン)です。 トークンには有効期限などの付加情報を追加したり、電子署名による改ざん対策が可能です。
ログイン資格情報は、上記いずれの案を用いたとしても、通信経路上および端末内での安全性が保たれています。 今回はアプリの特性と既存資産の流用のし易さを考慮し、ログイン資格情報に「アカウントID + パスワード」を採用します。
認証状態の保持
セッションの有効期限が切れた場合、アプリが自動で再度ログインしてセッションを更新します。 詳細は、【補足】有効期限が切れたログイン資格情報の更新方法 - Cookieベースの認証を参照してください。
ログアウト
ログアウト時に端末内のログイン資格情報を削除します。 匿名アカウントはログアウトすると再ログイン出来なくなるため、ログアウト操作が出来ません。 このアプリの機能要件にある「過去に作成したアカウントでログインできる」については、通常アカウントの導入で対応する予定です。
想定される脅威(攻撃手法)とその対応
ログイン機能に対する攻撃
ログイン機能に対する攻撃として、SQLインジェクション、OSコマンド・インジェクションやバッファオーバーフローなどの攻撃があります。 これらの攻撃は、脆弱性を利用して認証機能を迂回したり、パスワードを盗み出します。 こういった脆弱性への対策はバックエンド側となる為、アプリの対応範囲外です。
パスワード認証に対する攻撃
パスワード認証に対する攻撃として、次のようなものがあります。
- 辞書攻撃
- ジョーアカウント探索
- ブルートフォース攻撃(総当たり攻撃)
- パスワードスプレー攻撃
- リバースブルートフォース攻撃
- パスワードリスト攻撃
パスワードを自動生成することで結果的に防げている攻撃もありますが、この攻撃への対策はバックエンド側となる為、アプリの対応範囲外です。
ソーシャルエンジニアリング
ソーシャルエンジニアリングは、ユーザをだましてパスワードを聞き取る攻撃手法です。 この攻撃はアプリケーションで対策するものではありません。一般的に教育での対応となります。 ただし、このアプリの匿名アカウント使用ユーザは自身のパスワードを知らないので、結果的に攻撃を防げています。
フィッシング
本物そっくりの画面を備えた偽サイトを仕立てて、利用者の情報を入手する攻撃手法です。 基本的にユーザの注意により防ぐ攻撃です。 一般的にWebアプリケーションを標的とした攻撃であることと、このアプリの匿名アカウント使用ユーザはパスワードを手入力しないことから、脅威レベルとしては低いものとなります。
クロスサイト・リクエストフォージェリ(CSRF)
CSRFは、Cookieの特性を利用した攻撃手法です。 アプリケーションにCSRF脆弱性が存在すると、ログイン中のユーザは意図せず「重要な処理」を実行させられる場合があります。 一般的にWebアプリケーションを標的とした攻撃であるため、モバイルアプリとしての脅威レベルは低いものとなります。ただし、WebViewやIn-App Browserを利用している場合は脅威となりえます。 今回はバックエンドがCSRF対応をしている前提のもと、バックエンドから受け取ったCSRFトークンをHTTP通信時にHTTPヘッダへ含めます。
セッションハイジャック
セッションハイジャック手法としては次のようなものがあります。
- セッションIDの推測
- セッションIDの盗み出し
- セッションIDの強制
この攻撃への対策はバックエンド側となる為、アプリの対応範囲外です。
モバイル端末の紛失や盗難
このアプリでは、次の方式でモバイル端末の紛失や盗難リスクに対応します。
- ログイン時(自動ログインを含む)の端末認証
- 端末内の安全な場所に秘密情報(ログイン資格情報)を保存
ただし、モバイル端末の紛失や盗難リスクは、このアプリのみに留まりません。 そういったリスクに対しては、ユーザが端末のロック機能を有効にするなど、端末レベルでの紛失・盗難対策をしていることを前提としています。
不正サインアップ
サービスの妨害を目的として、外部からの自動操作により不正なサインアップが大量に行われる場合があります。 このような攻撃への対処法として、WAFの導入があります。 また、ユーザのメールアドレスを利用した不正防止やCAPTCHAという仕組みでの対策方法も考えられます。 いずれにしても、アプリケーションのセキュリティ特性により対応レベルが異なりますので、今回は対応範囲外とします。
想定される課題とその対応
アプリの移行
端末機種の切替などの理由によりアプリを移行する場合、その機能がこのアプリにはありません。 将来的には通常アカウントの機能を導入し、別の端末からログインする方法で対応する予定です。
ログイン資格情報の消失
匿名アカウント使用ユーザが次の理由によりログイン資格情報を消失した場合、復旧手段がこのアプリにはありません。
- 端末故障
- アプリのアンインストール(Androidのみ発生。詳細はログイン資格情報のライフサイクル管理における注意点を参照)
将来的には通常アカウントの機能を導入し、別の端末からログインする方法で対応する予定です。
ログイン資格情報が盗まれた場合の対応
ログイン資格情報が盗まれた場合の対応は、アカウント機能による運用対処とします。 システム管理者によるアカウント停止などを想定していますが、いずれにしても今回の検討対象外とします。
決定
認証手段の検討にあたり、OIDCとCookieベースの認証を検討しました。 OIDCは優れた認証方式ですが、他のプロジェクトでの参考のし易さを第一としてCookieベースの認証を採用します。
モバイルアプリの場合、端末の紛失や盗難といったセキュリティリスクがあります。 そこで、ログイン時(自動ログインを含む)に端末認証を要求することで、モバイル端末の紛失や盗難リスクに対応します。
セキュリティとユーザの利便性は相反しやすい品質特性です。 このアプリではユーザの利便性を優先し、匿名サインアップや自動ログインを採用します。 サインアップ時に作成した「アカウントID + パスワード」を、ログイン資格情報として端末内の安全な場所に保存することで、自動ログインを実現します。
付録
React NativeでCookieを扱う場合の懸念点について
React NativeでCookieを扱う場合、Known Issues with fetch and cookie based authenticationの確認が必要です。 こちらでは、Fetch APIとCookieベースの認証を組合わせた場合における、次の問題が報告されています。
- 以下のオプションがfetchで動作しない
redirect:manual
credentials:omit
- Androidで同じ名前のレスポンスヘッダーを受け取ると、最後に与えられたものだけが使用される
302
によるリダイレクト時にSet-Cookie
ヘッダが存在すると、少なくともiOSでCookieが正しく設定されない
このアプリではHTTP通信にaxiosを使います。 そのため、上記問題についてFetch APIとaxiosの双方で動作検証しました。 使用した環境は次の通りです。
- React Native: 0.64.3
- Android実機、エミュレータ
- iOS実機、シミュレータ
検証方法は次の通りです。
2つの
Set-Cookie
とHTTPステータス302を返すバックエンドAPI(以下、リダイレクト元API)を用意するリダイレクト先のバックエンドAPI(以下、リダイレクト先API)を用意する
Fetch APIでリダイレクト元APIにアクセスする
redirect
オプションを指定した場合credentials
オプションを指定した場合302
によるリダイレクト時にSet-Cookie
ヘッダが存在した場合- 同じ名前のレスポンスヘッダー(今回は
Set-Cookie
を使用)を受け取った場合
axiosでリダイレクト元APIにアクセスする
maxRedirects
オプションを指定した場合withCredentials
オプションを指定した場合302
によるリダイレクト時にSet-Cookie
ヘッダが存在した場合- 同じ名前のレスポンスヘッダー(今回は
Set-Cookie
を使用)を受け取った場合
Fetch APIのredirect
、およびcredentials
オプションの詳細は、次のリンクをご確認ください。
axiosのmaxRedirects
、およびwithCredentials
オプションの詳細は、次のリンクをご確認ください。
検証結果は次の通りです。
Fetch API
検証内容 | Android | iPhone | |||
---|---|---|---|---|---|
実機 | エミュレータ | 実機 | シミュレータ | ||
redirect | follow | 302応答でリダイレクトされ、リダイレクト先APIのリソースが応答として返却される。 | |||
error | |||||
manual | |||||
credentials | include | Cookieが送られる。 | |||
same-origin | |||||
omit | Cookieが送られない。 | Cookieが送られる。 | |||
302によるリダイレクト時にSet-Cookieヘッダが存在した場合 | Cookieが正しく設定される。 | ||||
レスポンスヘッダーで2つのSet-Cookieを受け取った場合 | 2つとも正しく設定される。 |
axios
検証内容 | Android | iPhone | |||
---|---|---|---|---|---|
実機 | エミュレータ | 実機 | シミュレータ | ||
maxRedirects | 0 | 302応答でリダイレクトされ、リダイレクト先APIのリソースが応答として返却される。 | |||
1 | |||||
2 | |||||
withCredentials | true | Cookieが送られる。 | |||
false | Cookieが送られない。 | Cookieが送られる。 | |||
302によるリダイレクト時にSet-Cookieヘッダが存在した場合 | Cookieが正しく設定される。 | ||||
レスポンスヘッダーで2つのSet-Cookieを受け取った場合 | 2つとも正しく設定される。 |
検証結果の考察
以下、検証結果の考察です。
redirect
オプションについて、どのオプションを指定しても302応答でリダイレクトされ、リダイレクト先APIのリソースが返ってきました。
redirect
オプションは機能していないようです。
maxRedirects
オプションも同様に機能していないようです。
credentials
オプションについて、credentials:include
、credentials:same-origin
ではCookieがバックエンドに送信されました。
一方でcredentials:omit
を指定すると、AndroidはCookieが送信されませんでしたが(想定される動作)、iOSは送信されました。
withCredentials
オプションも同様の動作に見えます。
Androidで同じ名前のヘッダーを持つ場合の問題を検証するため、リダイレクト元APIからSet-Cookie
ヘッダを2つ返しましたが、Android、iOS共にCookie値が正しく設定されました。
こちらで問題が解決されたようです。
リダイレクトされた時にSet-Cookie
ヘッダが存在しても、Cookieは正しく設定されました。
これらのことから、報告されていた問題のうち後者2つについては既に解消しています。 fetchの一部オプションに対応していない問題については、報告のとおり、Fetch APIの仕様通りに実装が提供されているわけではなさそうです。 このアプリでは、バックエンドAPIのリダイレクトを無視したいケースがないので、redirectオプションやmaxRedirectオプションの影響は受けません。 credentialsオプションやwithCredentialsオプションについても、モバイルアプリという特性上クロスオリジンを意識する必要はありません。リクエスト先のドメインによって発行されたCookieを送信できれば問題ないでしょう。 よって、Cookie-basedの認証を採用して開発を進めても問題ないと判断します。