セキュリティ
アプリ開発において、セキュリティは往々にして見過ごされがちです。完全に侵入不可能なソフトウェアを構築することは不可能であることは事実です。完全に侵入不可能な錠前はまだ発明されていません(銀行の金庫だって、結局は破られることがあります)。しかし、悪意のある攻撃の被害者になる確率、またはセキュリティの脆弱性を突かれる確率は、そのような事態に対するアプリケーションの保護に費やす努力に反比例します。普通の南京錠はピッキングできますが、それでも戸棚のフックよりもはるかに突破するのが難しいのです!
このガイドでは、機密情報の保存、認証、ネットワークセキュリティに関するベストプラクティス、そしてアプリのセキュリティを強化するのに役立つツールについて学習します。これは飛行前のチェックリストではありません。これはオプションのカタログであり、それぞれのオプションがアプリとユーザーの保護をさらに強化するのに役立ちます。
機密情報の保存
アプリコードに機密のAPIキーを保存しないでください。コードに含まれるものは何でも、アプリバンドルを検査する者なら誰でもプレーンテキストでアクセスできます。react-native-dotenvやreact-native-configなどのツールは、APIエンドポイントなどの環境固有の変数を追加するのに最適ですが、サーバー側の環境変数(多くの場合、シークレットやAPIキーを含む可能性があります)と混同しないでください。
アプリから何らかのリソースにアクセスするためにAPIキーまたはシークレットが必要な場合は、アプリとリソースの間にオーケストレーションレイヤーを構築するのが最も安全な方法です。これは、必要なAPIキーまたはシークレットを使用してリクエストを転送できるサーバーレス関数(AWS LambdaまたはGoogle Cloud Functionsなどを使用)にすることができます。サーバー側のコード内のシークレットは、アプリコード内のシークレットと同様にAPIコンシューマーからアクセスできません。
**永続的なユーザーデータについては、その機密性に基づいて適切なストレージの種類を選択してください。**アプリの使用に伴い、アプリのオフライン使用をサポートするため、ネットワークリクエストを削減するため、またはセッション間でユーザーのアクセストークンを保存して、アプリを使用するたびに再認証する必要がないようにするために、デバイスにデータを保存する必要が生じる場合があります。
**永続的 vs 非永続的** - 永続的なデータはデバイスのディスクに書き込まれます。これにより、データをフェッチするために別のネットワークリクエストを行うことなく、またはユーザーに再入力させることなく、アプリの起動全体でアプリがデータを読み取ることができます。しかし、これにより、攻撃者によるデータへのアクセスも容易になる可能性があります。非永続的なデータはディスクに書き込まれないため、アクセスできるデータはありません!
Async Storage
Async Storageは、非同期で暗号化されていないキーバリューストアを提供する、React Native向けのコミュニティ管理モジュールです。Async Storageはアプリ間で共有されません。各アプリには独自のサンドボックス環境があり、他のアプリのデータにアクセスすることはできません。
**使用する**場合… | **使用しない**場合… |
---|---|
アプリの実行間で非機密データを永続化する場合 | トークンの保存 |
Redux状態の永続化 | シークレット |
GraphQL状態の永続化 | |
グローバルなアプリ全体変数の保存 |
開発者向けノート
- ウェブ
Async Storageは、ウェブのローカルストレージに相当するReact Nativeの機能です。
セキュアストレージ
React Nativeには、機密データを保存する組み込みの方法はありません。ただし、AndroidおよびiOSプラットフォームには既存のソリューションがあります。
iOS - Keychain Services
Keychain Servicesを使用すると、ユーザーの機密情報を少量安全に保存できます。これは、証明書、トークン、パスワード、およびAsync Storageに保存すべきではないその他の機密情報を保存するのに理想的な場所です。
Android - セキュア共有設定
Shared Preferencesは、永続的なキーバリューデータストアのAndroid相当です。**Shared Preferencesのデータはデフォルトでは暗号化されていません**が、Encrypted Shared PreferencesはAndroidのShared Preferencesクラスをラップし、キーと値を自動的に暗号化します。
Android - Keystore
Android Keystoreシステムを使用すると、デバイスからの抽出をより困難にするために、コンテナに暗号化キーを保存できます。
iOS KeychainサービスまたはAndroidセキュア共有設定を使用するには、自分でブリッジを作成するか、それらをラップして独自のAPIを提供するライブラリを使用できます(自己責任で)。検討すべきライブラリをいくつかご紹介します。
**意図せず機密情報を保存したり公開したりしないように注意してください。**これは、たとえば、Redux状態に機密のフォームデータを保存し、Async Storageに状態ツリー全体を永続化することによって、誤って発生する可能性があります。または、SentryやCrashlyticsなどのアプリケーション監視サービスにユーザーのトークンや個人情報を送信することなどです。
認証とディープリンク
モバイルアプリには、Webには存在しない独自の脆弱性があります。**ディープリンク**です。ディープリンクは、外部ソースからネイティブアプリケーションに直接データを送信する方法です。ディープリンクは「app://」のようになります。「app」はアプリのスキームであり、「//」の後のものは、リクエストを処理するために内部的に使用できます。
たとえば、eコマースアプリを構築している場合、「app://products/1」を使用してアプリにディープリンクし、IDが1の製品の詳細ページを開くことができます。これらはWebのURLのようなものですが、重要な違いが1つあります。
ディープリンクは安全ではなく、機密情報を送信しないでください。
ディープリンクが安全でない理由は、URLスキームを登録するための集中管理された方法がないためです。アプリケーション開発者として、iOSの場合はXcodeで設定すること、Androidの場合はインテントを追加することによって、ほとんど任意のURLスキームを使用できます。
悪意のあるアプリケーションが、同じスキームに登録することによってディープリンクをハイジャックし、リンクに含まれるデータにアクセスすることを妨げるものはありません。「app://products/1」のようなものを送信しても害はありませんが、トークンを送信することはセキュリティ上の問題です。
オペレーティングシステムでリンクを開くときに2つ以上のアプリケーションを選択できる場合、Androidはユーザーに曖昧さの解消ダイアログを表示し、リンクを開くために使用するアプリケーションを選択するように求めます。ただし、iOSでは、オペレーティングシステムがユーザーに代わって選択するため、ユーザーは気づきません。Appleは、先着順の原則を導入した後のiOSバージョン(iOS 11)でこの問題に対処するための措置を講じていますが、この脆弱性はこちらで詳しく説明されているように、さまざまな方法で悪用される可能性があります。ユニバーサルリンクを使用すると、iOSでアプリ内のコンテンツに安全にリンクできます。
OAuth2とリダイレクト
OAuth2認証プロトコルは現在非常に人気があり、最も完全で安全なプロトコルとして高く評価されています。OpenID Connectプロトコルもこれに基づいています。OAuth2では、ユーザーはサードパーティを介して認証するように求められます。正常に完了すると、このサードパーティは検証コードを使用してJWT(JSON Web Token)と交換できるリクエストアプリケーションにリダイレクトされます。JWTは、Web上の当事者間で情報を安全に送信するためのオープン標準です。
Webでは、このリダイレクトステップは安全です。なぜなら、Web上のURLは一意であることが保証されているからです。これはアプリには当てはまりません。前述のように、URLスキームを登録するための集中管理された方法がないためです!このセキュリティ上の問題に対処するために、PKCEという形式で追加のチェックを追加する必要があります。
PKCE(「Pixy」と発音)は、Proof Key for Code Exchangeの略で、OAuth 2仕様への拡張です。これには、認証とトークン交換リクエストが同じクライアントから来たことを検証する追加のセキュリティレイヤーを追加することが含まれます。PKCEはSHA 256暗号化ハッシュアルゴリズムを使用します。SHA 256は、任意のサイズのテキストまたはファイルに対して一意の「署名」を作成しますが、
- 入力ファイルに関係なく常に同じ長さです。
- 同じ入力に対して常に同じ結果を生成することが保証されています。
- 一方向(つまり、元の入力を明らかにするために逆エンジニアリングすることはできません)。
これで2つの値ができました。
- **code_verifier** - クライアントによって生成された大きなランダム文字列
- **code_challenge** - code_verifierのSHA 256
最初の/authorize
リクエスト時に、クライアントはメモリに保持しているcode_verifier
に対するcode_challenge
も送信します。承認リクエストが正しく返された後、クライアントはcode_challenge
の生成に使用されたcode_verifier
も送信します。その後、IDPはcode_challenge
を計算し、最初の/authorize
リクエストで設定されたものと一致するかどうかを確認し、値が一致した場合のみアクセストークンを送信します。
これは、最初の承認フローをトリガーしたアプリケーションだけが検証コードをJWTと交換できることを保証します。そのため、悪意のあるアプリケーションが検証コードにアクセスできたとしても、それだけでは無効です。これが実際にどのように機能するかを確認するには、この例をご覧ください。
ネイティブOAuthで検討すべきライブラリは、react-native-app-authです。React-native-app-authは、OAuth2プロバイダーと通信するためのSDKです。ネイティブのAppAuth-iOSとAppAuth-Androidライブラリをラップしており、PKCEをサポートできます。
React-native-app-authは、アイデンティティプロバイダーがPKCEをサポートしている場合のみ、PKCEをサポートできます。
ネットワークセキュリティ
APIは常にSSL暗号化を使用する必要があります。SSL暗号化は、サーバーからクライアントに到達するまでの間、要求されたデータがプレーンテキストで読み取られるのを防ぎます。エンドポイントが安全であることは、http://
ではなくhttps://
で始まることでわかります。
SSLピニング
httpsエンドポイントを使用しても、データは傍受される可能性があります。httpsを使用する場合、クライアントは、クライアントにプリインストールされている信頼できる認証機関によって署名された有効な証明書を提供できる場合のみ、サーバーを信頼します。攻撃者は、悪意のあるルートCA証明書をユーザーのデバイスにインストールすることでこれを悪用し、クライアントは攻撃者によって署名されたすべての証明書を信頼するようになります。したがって、証明書だけに依存すると、中間者攻撃に対して脆弱なままになる可能性があります。
SSLピニングは、クライアント側でこの攻撃を回避するために使用できる手法です。開発中に信頼できる証明書のリストをクライアントに埋め込む(またはピニングする)ことで機能します。これにより、信頼できる証明書のいずれかで署名されたリクエストのみが受け入れられ、自己署名証明書は受け入れられません。
SSLピニングを使用する場合は、証明書の有効期限に注意する必要があります。証明書は1〜2年ごとに期限切れになり、期限切れになると、アプリとサーバーの両方で更新する必要があります。サーバーの証明書が更新されるとすぐに、古い証明書が埋め込まれたアプリは機能しなくなります。
概要
セキュリティを完全に処理する方法は存在しませんが、意識的な努力と注意深さによって、アプリケーションのセキュリティ侵害の可能性を大幅に低減できます。アプリケーションに保存されているデータの機密性、ユーザー数、ハッカーがアカウントにアクセスした場合の被害の程度に応じて、セキュリティに投資してください。そして覚えておいてください。そもそも要求されなかった情報にアクセスするのは、はるかに困難です。