web-dev-qa-db-ja.com

Laravel 5.6-Passport JWT httponly cookie SPA自己消費型APIの認証?

注:この質問には4つの賞金がありましたが、この質問に必要な答えは以下の投票された答えではありません。必要なものはすべて、以下のUpdate 3にあり、実装するLaravelコードを探しているだけです。


UPDATE 3:このフローチャートは、exactly私が達成しようとしているフローです。以下はすべて、古いアップデートの元の質問です。このフローチャートは、必要なものすべてをまとめたものです。

以下のフローチャートの緑色の部分は、私が行う方法を知っている部分です。赤い部分とそのサイドノートは、Laravelコードを使用して達成するためのヘルプを探しています。

enter image description here


私は多くの研究をしましたが、自己消費型APIのJWT httponly CookieでLaravelを使用することになると、情報は常に短くなり、完全ではありません(オンラインのほとんどのチュートリアルでは、JWTあまり安全ではないストレージ)。 PassportによるJWTを含むhttponly Cookieを使用して、サーバーへのすべてのリクエストでJavascript側のユーザーを識別し、ユーザーが本人であることを検証する必要があります。

また、このセットアップを機能させる方法の全体像を把握するために必要な追加事項もいくつかありますが、これについては1つのチュートリアルでは説明していません。

  1. Laravel Passport(tymon authではない)は、暗号化されたJWTを生成し、JS側からのログイン後に応答としてhttponly cookieとして送信します。どのミドルウェアを使用しますか?更新トークンによりセキュリティが強化される場合、実装方法は?
  2. JavaScript(たとえば、axios)authエンドポイントを呼び出すapi擬似コード、httponly cookieがバックエンドに渡される方法、およびバックエンドがトークンを検証する方法を確認します。
  3. 単一のアカウントが複数のデバイスからログインしている場合、デバイスが盗まれた場合、認証されたすべてのユーザーデバイスからのアクセスを取り消す方法(ユーザーが制御しているログイン済みデバイスからパスワードを変更すると仮定します)?
  4. 通常、ログイン/登録、ログアウト、パスワード変更、パスワードを忘れたコントローラーのメソッドは、トークンの作成/検証/失効をどのように処理しますか?
  5. CSRFトークンの統合。

この質問への回答が、将来の読者や、自己消費型APIの上記のポイントをカバーする回答を見つけるのに苦労している読者のための簡単なガイドとして役立つことを願っています。

更新1:

  1. 以前にCreateFreshApiTokenを試してみましたが、ユーザーのトークンの取り消しに関してはうまくいきませんでした(上記のポイント3および4)。これは、CreateFreshApiTokenミドルウェアについて話しているとき、コアlaravel開発者による このコメント に基づいています。

このミドルウェアによって作成されたJWTトークンはどこにも保存されません。取り消すことも、「存在しない」こともできません。これらは、laravel_token Cookieを介してAPI呼び出しを認証する方法を提供するだけです。アクセストークンとは関係ありません。また、通常は、クライアントが発行したトークンを、それらを発行する同じアプリ上で使用することはありません。ファーストパーティまたはサードパーティのアプリでそれらを使用します。ミドルウェアを使用するか、クライアントがトークンを発行しますが、両方を同時に使用することはできません。

そのため、ポイント3および4に対応してトークンを取り消すことができるようです。CreateFreshApiTokenミドルウェアを使用している場合、これを行うことはできません。

  1. クライアント側では、Authorization: Bearer <token>は、安全なhttpOnly Cookieを処理する場合の方法ではないようです。 laravel docsに基づいた次のように、リクエスト/レスポンスにはリクエスト/レスポンスヘッダーとして安全なhttpOnly Cookieが含まれると考えられます。

この認証方法を使用する場合、デフォルトのLaravel JavaScript足場は、X-CSRF-TOKENおよびX-Requested-Withヘッダーを常に送信するようにAxiosに指示します。

headerswindow.axios.defaults.headers.common = {
    'X-Requested-With': 'XMLHttpRequest',
    'X-CSRF-TOKEN': (csrf_token goes here)
};

これは、上記のすべてのポイントをカバーするソリューションを探している理由でもあります。おApび申し上げます。5.5ではなくLaravel 5.6を使用しています。

更新2:

Password Grant/Refresh Token Grantコンボが道のりのようです。 Password Grant/Refresh Token Grantコンボを使用して、わかりやすい実装ガイドを探しています。

パスワード付与:この付与は、当社のWebサイト用のモバイルアプリのように、信頼するクライアントを扱う場合に適しています。この場合、クライアントはユーザーのログイン資格情報を承認サーバーに送信し、サーバーはアクセストークンを直接発行します。

トークンリフレッシュの付与:サーバーがアクセストークンを発行すると、アクセストークンの有効期限も設定されます。更新トークンの付与は、期限切れになったアクセストークンを更新するときに使用されます。この場合、承認サーバーは、アクセストークンを発行するときに更新トークンを送信します。これを使用して、新しいアクセストークンを要求できます。

上記のオリジナルのすべての部分をカバーするPassword Grant/Refresh Token Grantコンボを使用して、実装が簡単で簡単な、全体的な答えを探していますhttpOnlyセキュアCookie、トークンの作成/取り消し/更新、ログインCookieの作成、ログアウトCookieの取り消し、コントローラーメソッド、CSRFなどの5ポイント.

49
Wonka

Laravel Passport JWT

  1. この機能を使用するには、Cookieのシリアル化を無効にする必要があります。 Laravel 5.5には、Cookie値のシリアル化/非シリアル化に関する問題があります。詳細については、こちらをご覧ください( https://laravel.com/docs/5.5/upgrade

  2. 確認してください

    • ブレードテンプレートヘッドに<meta name="csrf-token" content="{{ csrf_token() }}">があります

    • axiosは、各リクエストでcsrf_tokenを使用するように設定されます。

resources/assets/js/bootstrap.jsにこのようなものがあるはずです

window.axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';
let token = document.head.querySelector('meta[name="csrf-token"]');

if (token) {
  window.axios.defaults.headers.common['X-CSRF-TOKEN'] = token.content;
} else {
  console.error('CSRF token not found: https://laravel.com/docs/csrf#csrf-x-csrf-token');
}
  1. ここで説明されているセットアップ認証ルート( https://laravel.com/docs/5.5/authentication
  2. ここで説明されているセットアップパスポート( https://laravel.com/docs/5.5/passport )。

重要な部品は次のとおりです。

  • Laravel\Passport\HasApiTokens特性をUserモデルに追加します
  • config/auth.phpdriver認証ガードのapiオプションをpassportに設定します
  • \Laravel\Passport\Http\Middleware\CreateFreshApiToken::class,ミドルウェアをapp/Http/Kernel.phpwebミドルウェアグループに追加します

おそらく移行とクライアントの作成をスキップできることに注意してください。

  1. POSTを要求して、/loginに認証情報を渡します。 AJAXリクエストまたは通常のフォーム送信を行うことができます。

ログイン要求がAJAX(axiosを使用)の場合、応答データはHTMLになりますが、興味があるのはステータスコードです。

axios.get(
  '/login, 
  {
    email: '[email protected]',
    password: 'secret',
  },
  {
    headers: {
      'Accept': 'application/json', // set this header to get json validation errors.
    },
  },
).then(response => {
  if (response.status === 200) {
      // the cookie was set in browser
      // the response.data will be HTML string but I don't think you are interested in that
    }
    // do something in this case
}).catch(error => {
  if (error.response.status === 422) {
    // error.response.data is an object containing validation errors
  }
  // do something in this case
});

ログイン時に、サーバーは提供された資格情報でユーザーを見つけ、ユーザー情報(id、email ...)に基づいてトークンを生成し(このトークンはどこにも保存されません)、サーバーは生成されたトークンを含む暗号化されたCookieで応答を返します。

  1. 保護されたルートへのAPI呼び出しを行います。

保護されたルートがあると仮定します

Route::get('protected', 'SomeController@protected')->middleware('auth:api');

通常どおりaxiosを使用してajax呼び出しを行うことができます。クッキーは自動的に設定されます。

axios.get('/api/protected')
  .then(response => {
    // do something with the response
  }).catch(error => {
    // do something with this case of error
  });

サーバーが呼び出しを受信すると、リクエストlaravel_cookieを解読し、ユーザー情報(例:id、email ...)を取得します。その後、そのユーザー情報を使用してデータベース検索を行い、ユーザーが存在するかどうかを確認します。ユーザーが見つかった場合、ユーザーは要求されたリソースへのアクセスを許可されます。そうでない場合、401が返されます。

JWTトークンの無効化。コメントについて言及したように、このトークンはサーバー上のどこにも保存されないため、これについて心配する必要はありません。

更新

ポイント3に関してLaravel 5.6 Authには新しいメソッドlogoutOtherDevicesがあります。ドキュメントが非常に軽いため、ここから詳細を確認できます( https://laracasts.com/series/whats-new-in-laravel-5-6/episodes/7 )。

Laravelバージョンを更新できない場合は、5.6での実行方法を確認し、5.5用の独自の実装を構築できます

質問のポイント4。 app/Http/Controllers/Authにあるコントローラーを見てください。

Access_tokensとrefresh_tokensに関しては、これはまったく異なる、より複雑なアプローチです。その方法を説明する多数のチュートリアルをオンラインで見つけることができます。

役に立てば幸いです。

PS。よいお年をお迎えください!! :)

10
Mirceac21

また、プロジェクトにLaravelパスポートを実装しました。あなたが質問で述べたポイントのほとんどをカバーしたと思います。

  1. パスワードグラントを使用して、アクセストークンとリフレッシュトークンを生成しました。 これら の手順に従って、パスポートをセットアップし、パスポート許可を実装できます。ログイン方法では、ユーザー資格情報を検証し、トークンを生成して、Cookieを応答に添付する必要があります( 応答へのCookieの付加 )。必要な場合は、いくつかの例を入手できます。
  2. CORS(着信要求ヘッダーの処理)用に2つのミドルウェアを追加し、着信アクセストークンが有効かどうかを確認して、有効でない場合は、格納された更新トークンからアクセストークンを生成します( Refreshing token )。例を示します。
  3. ログイン後、クライアント側からのすべてのリクエストにはAuthorizationヘッダー(Authorization: Bearer <token>)が含まれている必要があります。

上記の点について明確であるかどうかを教えてください。

6
Wellwisher
  • Laravel PassportはPHP LeagueのOAuth Serverの実装です
  • パスワード付与タイプは、ユーザー名+パスワード認証に使用できます
  • プロキシで認証リクエストを作成して、クライアントの資格情報を非表示にすることを忘れないでください
  • 更新トークンをHttpOnly Cookieに保存して、XSS攻撃のリスクを最小限に抑えます

ここで見ることができる詳細情報

http://esbenp.github.io/2017/03/19/modern-rest-api-laravel-part-4/

6
Ismoil Shifoev

すべての質問に対する回答は一般的なプロトコルまたはアルゴリズムの仕様から導出できるため、フレームワーク、実装、および言語間で回答が適用されるように、一般的な方法でこれに回答しようとします。

どのOAuth 2.0許可タイプを使用すべきですか?

これが最初に決定されることです。 SPAに関しては、2つの選択肢があります。

  1. 認証コードの付与(クライアントシークレットがサーバー側に保存されている場合に推奨)
  2. リソース所有者のパスワード資格情報の付与

暗黙的な付与タイプをオプションとして言及しない理由は次のとおりです。

  1. クライアントシークレットと認証コードを提供することによるクライアント認証手順がありません。セキュリティが低い
  2. アクセストークンはURLフラグメントとして返送され(トークンがサーバーに移動しないように)、ブラウザーの履歴に残ります
  3. XSS攻撃が発生した場合、悪意のあるスクリプトは攻撃者の制御下にあるリモートサーバーにトークンを送信することができます。

(クライアント資格情報付与タイプは、クライアントがユーザーに代わって行動していないときに使用されるため、この説明の範囲外に保たれます。たとえば、バッチジョブ)

認証コード付与タイプの場合、通常、認証サーバーはリソースサーバーとは別のサーバーです。承認サーバーを分離して、組織内のすべてのSPAの共通承認サーバーとして使用することをお勧めします。これは常に推奨されるソリューションです。

ここ(認可コード付与タイプ)では、フローは次のようになります。

  1. ユーザーがSPAランディングページのログインボタンをクリックする
  2. ユーザーは許可サーバーのログインページにリダイレクトされます。クライアントIDはURLクエリパラメーターで提供されます
  3. ユーザーは自分の資格情報を入力し、ログインボタンをクリックします。ユーザー名とパスワードは、HTTP POSTを使用して承認サーバーに送信されます。資格情報は、URLではなく要求本文またはヘッダーで送信する必要があります(URLはブラウザーの履歴とアプリケーションサーバーに記録されるため)。また、資格情報がキャッシュされないように、適切なキャッシュHTTPヘッダーを設定する必要があります。Cache-Control: no-cache, no-storePragma: no-cacheExpires: 0
  4. 認可サーバーは、ユーザーデータベース(たとえば、LDAPサーバー)に対してユーザーを認証します。ユーザーデータベースとユーザーパスワードのハッシュ(Argon2、PBKDF2、Bcrypt、Scryptなどのハッシュアルゴリズム)はランダムソルトで保存されます
  5. 認証が成功すると、承認サーバーはデータベースから、URLクエリパラメーターで指定されたクライアントIDに対するリダイレクトURLを取得します。リダイレクトURLはリソースサーバーURLです
  6. ユーザーは、URLクエリパラメーターに認証コードを指定してリソースサーバーエンドポイントにリダイレクトされます
  7. 次に、リソースサーバーは、HTTP POST要求をアクセストークンの承認サーバーに要求します。認証コード、クライアントID、クライアントシークレットは、リクエスト本文に含める必要があります。 (上記の適切なキャッシングヘッダーを使用する必要があります)
  8. 認可サーバーは、レスポンスボディまたはヘッダー(上記の適切なキャッシングヘッダー付き)でアクセストークンとリフレッシュトークンを返します。
  9. リソースサーバーは、適切なCookieを設定することで、ユーザー(HTTP応答コード302)をSPA URLにリダイレクトします(詳細は後述)

一方、リソース所有者のパスワード資格情報付与タイプの場合、認可サーバーとリソースサーバーは同じです。実装が簡単であり、要件と実装のタイムラインに適合する場合にも使用できます。

リソース所有者の付与タイプの詳細については、これに関する私の答え here も参照してください。

ここで重要なのは、SPAでは、適切なサービスを呼び出してから、リクエストに有効なトークンが存在することを確認してから、保護されたすべてのルートを有効にする必要があることです。同様に、保護されたAPIにも、アクセストークンを検証するための適切なフィルターが必要です。

ブラウザーのlocalstorageまたはsessionstorageにトークンを保存しないのはなぜですか?

多くのSPAは、ブラウザのlocalstorageまたはsessionstorageにアクセスおよび/または更新トークンを保存します。これらのブラウザストレージにトークンを保存するべきではないと思う理由は次のとおりです。

  1. XSSが発生した場合、悪意のあるスクリプトはそこからトークンを簡単に読み取り、リモートサーバーに送信できます。そこでは、リモートサーバーまたは攻撃者が被害者のユーザーになりすますことに問題はありません。

  2. localstorageとsessionstorageは、サブドメイン間で共有されません。したがって、異なるサブドメインで2つのSPAを実行している場合、1つのアプリで保存されたトークンは組織内の他のアプリで使用できないため、SSO機能を取得できません

ただし、これらのブラウザストレージのいずれかにトークンがまだ保存されている場合は、適切な指紋を含める必要があります。指紋は、暗号的に強力なランダムなバイト列です。生の文字列のBase64文字列は、名前接頭辞__Secure-を持つHttpOnlySecureSameSite Cookieに保存されます。 DomainおよびPath属性の適切な値。文字列のSHA256ハッシュもJWTのクレームで渡されます。したがって、XSS攻撃が攻撃者が制御するリモートサーバーにJWTアクセストークンを送信したとしても、Cookie内の元の文字列を送信できず、その結果、サーバーはCookieがないことに基づいてリクエストを拒否できます。また、XSSおよびスクリプトインジェクションは、適切なcontent-security-policy応答ヘッダーを使用することでさらに軽減できます。

注意:

  1. SameSite=strictは、指定されたCookieが別のサイト(AJAXまたは次のハイパーリンクを経由)から発信されたリクエストに付随しないことを保証します。簡単に言えば、ターゲットサイトと同じ「登録可能なドメイン」を持つサイトからのリクエストはすべて許可されます。例えば。 「 http://www.example.com 」がサイトの名前である場合、登録可能なドメインは「example.com」です。詳細については、参照番号を参照してください。以下の最後のセクションの3。したがって、CSRFに対する保護を提供します。ただし、これは、URLがフォーラムである場合、認証されたユーザーがリンクをたどることができないことも意味します。これがアプリケーションにとって重大な制限である場合、SameSite=laxを使用して、HTTPメソッドが安全である限り、クロスサイトリクエストを許可できます。 GET、HEAD、OPTIONS、およびTRACE。 CSRFはPOST、PUT、DELETEなどの安全でないメソッドに基づいているため、laxはCSRFに対する保護を提供します

  2. 「example.com」のサブドメインへのすべてのリクエストでCookieが渡されるようにするには、Cookieのドメイン属性を「example.com」として設定する必要があります

Cookieにアクセストークンやリフレッシュトークンを保存する必要があるのはなぜですか?

  1. トークンをクッキーに保存する場合、クッキーをsecureおよびhttpOnlyとして設定できます。したがって、XSSが発生した場合、悪意のあるスクリプトはそれらを読み取り、リモートサーバーに送信できません。 XSSはユーザーのブラウザーからユーザーになりすますことができますが、ブラウザーが閉じている場合、スクリプトはそれ以上の損害を与えることはできません。 secureフラグは、安全でない接続を介してトークンを送信できないようにします-SSL/TLSは必須です
  2. たとえば、Cookieのルートドメインをdomain=example.comとして設定すると、Cookieがすべてのサブドメインでアクセス可能になります。したがって、組織内の異なるアプリとサーバーは同じトークンを使用できます。ログインは1回のみ必要です

トークンを検証するにはどうすればよいですか?

トークンは通常JWTトークンです。通常、トークンの内容は秘密ではありません。したがって、通常は暗号化されません。暗号化が必要な場合(おそらく、一部の機密情報もトークン内で渡されるため)、別の仕様JWEがあります。暗号化が不要な場合でも、トークンの整合性を確保する必要があります。誰(ユーザーまたは攻撃者)がトークンを変更できるべきではありません。その場合、サーバーはそれを検出し、偽造トークンを使用したすべての要求を拒否できる必要があります。この整合性を確保するために、JWTトークンはHmacSHA256などのアルゴリズムを使用してデジタル署名されます。この署名を生成するには、秘密鍵が必要です。許可サーバーが秘密を所有して保護します。認可サーバーAPIが呼び出されてトークンを検証するたびに、認可サーバーは渡されたトークンのHMACを再計算します。入力HMACと一致しない場合、否定応答を返します。 JWTトークンは、Base64エンコード形式で返されるか、保存されます。

ただし、リソースサーバー上のすべてのAPI呼び出しでは、承認サーバーはトークンの検証に関与しません。リソースサーバーは、承認サーバーによって発行されたトークンをキャッシュできます。リソースサーバーは、メモリ内データグリッド(つまりRedis)を使用できます。または、すべてをRAMに保存できない場合は、LSMベースのDB(レベルDBを備えたRiak)を使用してトークンを保存できます。

すべてのAPI呼び出しについて、リソースサーバーはキャッシュをチェックします。

  1. キャッシュにアクセストークンが存在しない場合、APIは適切な応答メッセージと401応答コードを返して、SPAがユーザーを再ログインするように要求される適切なページにリダイレクトできるようにする必要があります。

  2. アクセストークンは有効だが期限が切れている場合(注、JWTトークンには通常、ユーザー名と有効期限が含まれます)、APIは適切な応答メッセージと401応答コードを返して、SPAが適切なリソースサーバーAPIを呼び出して更新トークン(適切なキャッシュヘッダーを使用)でアクセストークンを更新します。サーバーは、アクセストークン、更新トークン、およびクライアントシークレットを使用して承認サーバーを呼び出し、承認サーバーは新しいアクセストークンと更新トークンを返し、最終的に(適切なキャッシュヘッダーを使用して)SPAに流れます。その後、クライアントは元の要求を再試行する必要があります。これらはすべて、ユーザーの介入なしにシステムによって処理されます。アクセストークンと同様ですが、Path属性に適切な値を持つリフレッシュトークンを格納するための個別のCookieを作成して、リフレッシュトークンがすべてのリクエストに付随するのではなく、更新リクエストでのみ使用できるようにすることができます

  3. リフレッシュトークンが無効または期限切れの場合、APIは適切な応答メッセージと401応答コードを返して、SPAがユーザーを再ログインするように要求される適切なページにリダイレクトできるようにする必要があります。

アクセストークンとリフレッシュトークンの2つのトークンが必要な理由

  1. 通常、アクセストークンの有効期間は短く、たとえば30分です。リフレッシュトークンの有効期間は通常6か月など、より長いものです。アクセストークンが何らかの方法で侵害された場合、攻撃者はアクセストークンが有効である限り、被害者のユーザーになりすますことができます。攻撃者はクライアントシークレットを取得できないため、承認サーバーに新しいアクセストークンを要求することはできません。ただし、攻撃者はリソースサーバーにトークン更新を要求できます(上記の設定のように、更新要求はリソースサーバーを経由してブラウザにクライアントシークレットを保存しないようにします)。 IPアドレスに基づいて追加の保護対策を講じます。

  2. アクセストークンのこの短い有効期間が必要な場合、承認サーバーがクライアントから発行されたトークンを取り消すのに役立ちます。許可サーバーは、発行されたトークンのキャッシュを保持することもできます。システムの管理者は、必要に応じて、特定のユーザーのトークンを失効としてマークできます。アクセストークンの有効期限が切れると、リソースサーバーが承認サーバーに移動すると、ユーザーは再度ログインするように強制されます。

CSRFはどうですか?

  1. ユーザーをCSRFから保護するために、Angular(Angular HttpClient documentation その特定のセッションの一意の予測不可能な値を含む非HttpOnly Cookie(つまり、読み取り可能なCookie)を送信します。これは、暗号的に強力なランダム値である必要があります。クライアントは、常にCookieを読み取り、カスタムHTTPヘッダー(状態を変更するロジックを持たないGET&HEADリクエストを除く。注:同じOriginポリシーのため、CSRFはターゲットWebアプリから何も読み取ることができません)ヘッダーとCookie:クロスドメインフォームはCookieを読み取ったり、カスタムヘッダーを設定できないため、CSRFリクエストの場合、カスタムヘッダーの値が失われ、サーバーは攻撃を検出できます。

  2. ログインCSRFからアプリケーションを保護するには、refererヘッダーを常に確認し、refererが信頼できるドメインの場合のみ要求を受け入れます。 refererヘッダーが存在しないか、ホワイトリストに登録されていないドメインの場合、リクエストを単に拒否します。 SSL/TLSを使用する場合、通常referrerが存在します。ランディングページ(主に情報であり、ログインフォームまたは保護されたコンテンツを含まない)は、少し緩和される場合があり、refererヘッダーのないリクエストを許可します

  3. TRACE Cookieの読み取りに使用できるため、サーバーでhttpOnly HTTPメソッドをブロックする必要があります

  4. また、ヘッダーStrict-Transport-Security: max-age=<expire-time>; includeSubDomainsを設定して、セキュリティで保護された接続のみを許可し、中間者がサブドメインからのCSRF Coo​​kieを上書きしないようにします

  5. さらに、上記のSameSite設定を使用する必要があります

最後に、SSL/TLSはすべての通信に必須です-今日のように、1.1より前のTLSバージョンはPCI/DSS準拠には受け入れられません。前方秘匿性と認証された暗号化を確保するには、適切な暗号スイートを使用する必要があります。また、アクセストークンとリフレッシュトークンは、ユーザーが明示的に「ログアウト」をクリックしてすぐにブラックリストに登録し、トークンの誤用の可能性を防止する必要があります。

参照資料

  1. RFC 6749-OAuth2.
  2. OWASP JWTチートシート
  3. SameSite Cookie IETF Draft
  4. Cookieプレフィックス
  5. RFC 6265-Cookie
3
Saptarshi Basu