注:この質問には4つの賞金がありましたが、この質問に必要な答えは以下の投票された答えではありません。必要なものはすべて、以下のUpdate 3にあり、実装するLaravelコードを探しているだけです。
UPDATE 3:このフローチャートは、exactly私が達成しようとしているフローです。以下はすべて、古いアップデートの元の質問です。このフローチャートは、必要なものすべてをまとめたものです。
以下のフローチャートの緑色の部分は、私が行う方法を知っている部分です。赤い部分とそのサイドノートは、Laravelコードを使用して達成するためのヘルプを探しています。
私は多くの研究をしましたが、自己消費型APIのJWT httponly CookieでLaravelを使用することになると、情報は常に短くなり、完全ではありません(オンラインのほとんどのチュートリアルでは、JWTあまり安全ではないストレージ)。 PassportによるJWTを含むhttponly Cookieを使用して、サーバーへのすべてのリクエストでJavascript側のユーザーを識別し、ユーザーが本人であることを検証する必要があります。
また、このセットアップを機能させる方法の全体像を把握するために必要な追加事項もいくつかありますが、これについては1つのチュートリアルでは説明していません。
この質問への回答が、将来の読者や、自己消費型APIの上記のポイントをカバーする回答を見つけるのに苦労している読者のための簡単なガイドとして役立つことを願っています。
更新1:
CreateFreshApiToken
を試してみましたが、ユーザーのトークンの取り消しに関してはうまくいきませんでした(上記のポイント3および4)。これは、CreateFreshApiToken
ミドルウェアについて話しているとき、コアlaravel開発者による このコメント に基づいています。このミドルウェアによって作成されたJWTトークンはどこにも保存されません。取り消すことも、「存在しない」こともできません。これらは、laravel_token Cookieを介してAPI呼び出しを認証する方法を提供するだけです。アクセストークンとは関係ありません。また、通常は、クライアントが発行したトークンを、それらを発行する同じアプリ上で使用することはありません。ファーストパーティまたはサードパーティのアプリでそれらを使用します。ミドルウェアを使用するか、クライアントがトークンを発行しますが、両方を同時に使用することはできません。
そのため、ポイント3および4に対応してトークンを取り消すことができるようです。CreateFreshApiToken
ミドルウェアを使用している場合、これを行うことはできません。
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ポイント.
Laravel Passport JWT
この機能を使用するには、Cookieのシリアル化を無効にする必要があります。 Laravel 5.5には、Cookie値のシリアル化/非シリアル化に関する問題があります。詳細については、こちらをご覧ください( https://laravel.com/docs/5.5/upgrade )
確認してください
ブレードテンプレートヘッドに<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');
}
重要な部品は次のとおりです。
Laravel\Passport\HasApiTokens
特性をUser
モデルに追加しますconfig/auth.php
でdriver
認証ガードのapi
オプションをpassport
に設定します\Laravel\Passport\Http\Middleware\CreateFreshApiToken::class,
ミドルウェアをapp/Http/Kernel.php
のweb
ミドルウェアグループに追加しますおそらく移行とクライアントの作成をスキップできることに注意してください。
/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で応答を返します。
保護されたルートがあると仮定します
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。よいお年をお迎えください!! :)
また、プロジェクトにLaravelパスポートを実装しました。あなたが質問で述べたポイントのほとんどをカバーしたと思います。
Authorization: Bearer <token>
)が含まれている必要があります。上記の点について明確であるかどうかを教えてください。
ここで見ることができる詳細情報
http://esbenp.github.io/2017/03/19/modern-rest-api-laravel-part-4/
すべての質問に対する回答は一般的なプロトコルまたはアルゴリズムの仕様から導出できるため、フレームワーク、実装、および言語間で回答が適用されるように、一般的な方法でこれに回答しようとします。
これが最初に決定されることです。 SPAに関しては、2つの選択肢があります。
暗黙的な付与タイプをオプションとして言及しない理由は次のとおりです。
(クライアント資格情報付与タイプは、クライアントがユーザーに代わって行動していないときに使用されるため、この説明の範囲外に保たれます。たとえば、バッチジョブ)
認証コード付与タイプの場合、通常、認証サーバーはリソースサーバーとは別のサーバーです。承認サーバーを分離して、組織内のすべてのSPAの共通承認サーバーとして使用することをお勧めします。これは常に推奨されるソリューションです。
ここ(認可コード付与タイプ)では、フローは次のようになります。
Cache-Control: no-cache, no-store
、Pragma: no-cache
、Expires: 0
一方、リソース所有者のパスワード資格情報付与タイプの場合、認可サーバーとリソースサーバーは同じです。実装が簡単であり、要件と実装のタイムラインに適合する場合にも使用できます。
リソース所有者の付与タイプの詳細については、これに関する私の答え here も参照してください。
ここで重要なのは、SPAでは、適切なサービスを呼び出してから、リクエストに有効なトークンが存在することを確認してから、保護されたすべてのルートを有効にする必要があることです。同様に、保護されたAPIにも、アクセストークンを検証するための適切なフィルターが必要です。
多くのSPAは、ブラウザのlocalstorageまたはsessionstorageにアクセスおよび/または更新トークンを保存します。これらのブラウザストレージにトークンを保存するべきではないと思う理由は次のとおりです。
XSSが発生した場合、悪意のあるスクリプトはそこからトークンを簡単に読み取り、リモートサーバーに送信できます。そこでは、リモートサーバーまたは攻撃者が被害者のユーザーになりすますことに問題はありません。
localstorageとsessionstorageは、サブドメイン間で共有されません。したがって、異なるサブドメインで2つのSPAを実行している場合、1つのアプリで保存されたトークンは組織内の他のアプリで使用できないため、SSO機能を取得できません
ただし、これらのブラウザストレージのいずれかにトークンがまだ保存されている場合は、適切な指紋を含める必要があります。指紋は、暗号的に強力なランダムなバイト列です。生の文字列のBase64文字列は、名前接頭辞__Secure-
を持つHttpOnly
、Secure
、SameSite
Cookieに保存されます。 Domain
およびPath
属性の適切な値。文字列のSHA256ハッシュもJWTのクレームで渡されます。したがって、XSS攻撃が攻撃者が制御するリモートサーバーにJWTアクセストークンを送信したとしても、Cookie内の元の文字列を送信できず、その結果、サーバーはCookieがないことに基づいてリクエストを拒否できます。また、XSSおよびスクリプトインジェクションは、適切なcontent-security-policy
応答ヘッダーを使用することでさらに軽減できます。
注意:
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に対する保護を提供します
「example.com」のサブドメインへのすべてのリクエストでCookieが渡されるようにするには、Cookieのドメイン属性を「example.com」として設定する必要があります
secure
およびhttpOnly
として設定できます。したがって、XSSが発生した場合、悪意のあるスクリプトはそれらを読み取り、リモートサーバーに送信できません。 XSSはユーザーのブラウザーからユーザーになりすますことができますが、ブラウザーが閉じている場合、スクリプトはそれ以上の損害を与えることはできません。 secure
フラグは、安全でない接続を介してトークンを送信できないようにします-SSL/TLSは必須ですdomain=example.com
として設定すると、Cookieがすべてのサブドメインでアクセス可能になります。したがって、組織内の異なるアプリとサーバーは同じトークンを使用できます。ログインは1回のみ必要ですトークンは通常JWTトークンです。通常、トークンの内容は秘密ではありません。したがって、通常は暗号化されません。暗号化が必要な場合(おそらく、一部の機密情報もトークン内で渡されるため)、別の仕様JWEがあります。暗号化が不要な場合でも、トークンの整合性を確保する必要があります。誰(ユーザーまたは攻撃者)がトークンを変更できるべきではありません。その場合、サーバーはそれを検出し、偽造トークンを使用したすべての要求を拒否できる必要があります。この整合性を確保するために、JWTトークンはHmacSHA256などのアルゴリズムを使用してデジタル署名されます。この署名を生成するには、秘密鍵が必要です。許可サーバーが秘密を所有して保護します。認可サーバーAPIが呼び出されてトークンを検証するたびに、認可サーバーは渡されたトークンのHMACを再計算します。入力HMACと一致しない場合、否定応答を返します。 JWTトークンは、Base64エンコード形式で返されるか、保存されます。
ただし、リソースサーバー上のすべてのAPI呼び出しでは、承認サーバーはトークンの検証に関与しません。リソースサーバーは、承認サーバーによって発行されたトークンをキャッシュできます。リソースサーバーは、メモリ内データグリッド(つまりRedis)を使用できます。または、すべてをRAMに保存できない場合は、LSMベースのDB(レベルDBを備えたRiak)を使用してトークンを保存できます。
すべてのAPI呼び出しについて、リソースサーバーはキャッシュをチェックします。
キャッシュにアクセストークンが存在しない場合、APIは適切な応答メッセージと401応答コードを返して、SPAがユーザーを再ログインするように要求される適切なページにリダイレクトできるようにする必要があります。
アクセストークンは有効だが期限が切れている場合(注、JWTトークンには通常、ユーザー名と有効期限が含まれます)、APIは適切な応答メッセージと401応答コードを返して、SPAが適切なリソースサーバーAPIを呼び出して更新トークン(適切なキャッシュヘッダーを使用)でアクセストークンを更新します。サーバーは、アクセストークン、更新トークン、およびクライアントシークレットを使用して承認サーバーを呼び出し、承認サーバーは新しいアクセストークンと更新トークンを返し、最終的に(適切なキャッシュヘッダーを使用して)SPAに流れます。その後、クライアントは元の要求を再試行する必要があります。これらはすべて、ユーザーの介入なしにシステムによって処理されます。アクセストークンと同様ですが、Path
属性に適切な値を持つリフレッシュトークンを格納するための個別のCookieを作成して、リフレッシュトークンがすべてのリクエストに付随するのではなく、更新リクエストでのみ使用できるようにすることができます
リフレッシュトークンが無効または期限切れの場合、APIは適切な応答メッセージと401応答コードを返して、SPAがユーザーを再ログインするように要求される適切なページにリダイレクトできるようにする必要があります。
通常、アクセストークンの有効期間は短く、たとえば30分です。リフレッシュトークンの有効期間は通常6か月など、より長いものです。アクセストークンが何らかの方法で侵害された場合、攻撃者はアクセストークンが有効である限り、被害者のユーザーになりすますことができます。攻撃者はクライアントシークレットを取得できないため、承認サーバーに新しいアクセストークンを要求することはできません。ただし、攻撃者はリソースサーバーにトークン更新を要求できます(上記の設定のように、更新要求はリソースサーバーを経由してブラウザにクライアントシークレットを保存しないようにします)。 IPアドレスに基づいて追加の保護対策を講じます。
アクセストークンのこの短い有効期間が必要な場合、承認サーバーがクライアントから発行されたトークンを取り消すのに役立ちます。許可サーバーは、発行されたトークンのキャッシュを保持することもできます。システムの管理者は、必要に応じて、特定のユーザーのトークンを失効としてマークできます。アクセストークンの有効期限が切れると、リソースサーバーが承認サーバーに移動すると、ユーザーは再度ログインするように強制されます。
ユーザーをCSRFから保護するために、Angular(Angular HttpClient documentation その特定のセッションの一意の予測不可能な値を含む非HttpOnly Cookie(つまり、読み取り可能なCookie)を送信します。これは、暗号的に強力なランダム値である必要があります。クライアントは、常にCookieを読み取り、カスタムHTTPヘッダー(状態を変更するロジックを持たないGET&HEADリクエストを除く。注:同じOriginポリシーのため、CSRFはターゲットWebアプリから何も読み取ることができません)ヘッダーとCookie:クロスドメインフォームはCookieを読み取ったり、カスタムヘッダーを設定できないため、CSRFリクエストの場合、カスタムヘッダーの値が失われ、サーバーは攻撃を検出できます。
ログインCSRFからアプリケーションを保護するには、referer
ヘッダーを常に確認し、referer
が信頼できるドメインの場合のみ要求を受け入れます。 referer
ヘッダーが存在しないか、ホワイトリストに登録されていないドメインの場合、リクエストを単に拒否します。 SSL/TLSを使用する場合、通常referrer
が存在します。ランディングページ(主に情報であり、ログインフォームまたは保護されたコンテンツを含まない)は、少し緩和される場合があり、referer
ヘッダーのないリクエストを許可します
TRACE
Cookieの読み取りに使用できるため、サーバーでhttpOnly
HTTPメソッドをブロックする必要があります
また、ヘッダーStrict-Transport-Security: max-age=<expire-time>; includeSubDomains
を設定して、セキュリティで保護された接続のみを許可し、中間者がサブドメインからのCSRF Cookieを上書きしないようにします
さらに、上記のSameSite
設定を使用する必要があります
最後に、SSL/TLSはすべての通信に必須です-今日のように、1.1より前のTLSバージョンはPCI/DSS準拠には受け入れられません。前方秘匿性と認証された暗号化を確保するには、適切な暗号スイートを使用する必要があります。また、アクセストークンとリフレッシュトークンは、ユーザーが明示的に「ログアウト」をクリックしてすぐにブラックリストに登録し、トークンの誤用の可能性を防止する必要があります。