トークンを保護するためにHTTPOnlyCookieでCORSを有効にする方法は?

公開: 2021-12-29

この記事では、HTTPOnly Cookieを使用してCORS(クロスオリジンリソースシェアリング)を有効にして、アクセストークンを保護する方法について説明します。

現在、バックエンドサーバーとフロントエンドクライアントは異なるドメインに展開されています。 したがって、サーバーはCORSを有効にして、クライアントがブラウザーでサーバーと通信できるようにする必要があります。

また、サーバーはスケーラビリティを向上させるためにステートレス認証を実装しています。 トークンはクライアント側で保存および維持されますが、セッションのようにサーバー側では維持されません。 セキュリティ上の理由から、トークンはHTTPOnlyCookieに保存することをお勧めします。

クロスオリジンリクエストがブロックされるのはなぜですか?

フロントエンドアプリケーションがhttps://app.geekflare.comデプロイされていると仮定しましょう。 https://app.geekflare.com読み込まれたスクリプトは、同一生成元のリソースのみをリクエストできhttps://app.geekflare.com

クロスオリジンリクエストを別のドメインhttps://api.geekflare.comまたは別のポートhttps://app.geekflare.com:3000または別のスキームhttp://app.geekflare.comに送信しようとすると、クロスオリジンリクエストはブラウザによってブロックされます。

しかし、ブラウザによってブロックされた同じリクエストが、curlリクエストを使用してバックエンドサーバーから送信されたり、CORSの問題なしにpostmanなどのツールを使用して送信されたりするのはなぜですか。 実際には、CSRF(クロスサイトリクエストフォージェリ)などの攻撃からユーザーを保護するためのセキュリティがあります。

例を見てみましょう。ブラウザで自分のPayPalアカウントにログインしているユーザーがいるとします。 同じオリジンリクエストを送信するように、CORSエラー/ブロッキングなしで、別のドメインのmalicious.com paypal.comロードされたスクリプトからpaypal.comクロスオリジンリクエストを送信できる場合。

攻撃者は、悪意のあるページhttps://malicious.com/transfer-money-to-attacker-account-from-user-paypal-accountをshort-URLに変換して実際のURLを隠すことにより、簡単に送信できます。 ユーザーが悪意のあるリンクをクリックすると、ドメインmalicious.comロードされたスクリプトがPayPalにクロスオリジンリクエストを送信して、ユーザーの金額を攻撃者のPayPalアカウントに転送します。 PayPalアカウントにログインし、この悪意のあるリンクをクリックしたすべてのユーザーは、お金を失います。 PayPalアカウントのユーザーの知識がなくても、誰でも簡単にお金を盗むことができます。

上記の理由により、ブラウザはすべてのクロスオリジンリクエストをブロックします。

CORS(クロスオリジンリソースシェアリング)とは何ですか?

CORSは、信頼できるドメインからクロスオリジンリクエストを送信するようにブラウザに指示するためにサーバーが使用するヘッダーベースのセキュリティメカニズムです。
サーバーは、ブラウザーによってブロックされるクロスオリジンリクエストを回避するために使用されるCORSヘッダーで有効になっています。

CORSはどのように機能しますか?

サーバーはすでにCORS構成で信頼できるドメインを定義しているため。 サーバーにリクエストを送信すると、リクエストされたドメインが信頼されているか、ヘッダーに含まれていないかがブラウザに通知されます。

2種類のCORSリクエストがあります。

  • 簡単なリクエスト
  • プリフライトリクエスト

簡単なリクエスト:

CORS-シンプルなリクエストフローは、クロスオリジンリクエストを送信しますが、レスポンスを受信したことを示します。ヘッダーをチェックします。

  • ブラウザは、 origin(https://app.geekflare.com)を持つクロスオリジンドメインにリクエストを送信します。
  • サーバーは、許可されたメソッド許可されたオリジンを含む対応する応答を送り返します
  • リクエストを受信した後、ブラウザは送信されたオリジンヘッダー値( https://app.geekflare.com )と受信されたaccess-control-allow-origin値( https://app.geekflare.com )が同じであるか、ワイルドカード(*)。 そうしないと、CORSエラーがスローされます。

プリフライトリクエスト:

CORS-Preflight Request Image which show the flow of cross-origin request with OPTIONS preflight request before sending actual request for verifying headers.

  • メソッド(PUT、DELETE)、カスタムヘッダー、さまざまなコンテンツタイプなど、クロスオリジンリクエストのカスタムリクエストパラメータに応じて、ブラウザはプリフライトOPTIONSリクエストを送信して、実際のリクエストが安全に送信できるかどうかを確認します。か否か。
  • 応答(ステータスコード:204、コンテンツがないことを意味します)を受信した後、ブラウザーは実際の要求のaccess-control-allowパラメーターを確認します。 リクエストパラメータがサーバーによって許可されている場合。 送受信された実際のクロスオリジンリクエスト

access-control-allow-origin: *場合、応答はすべてのオリジンに対して許可されます。 しかし、あなたがそれを必要としない限り、それは安全ではありません。

CORSを有効にする方法は?

任意のドメインでCORSを有効にするには、CORSヘッダーを有効にして、オリジン、メソッド、カスタムヘッダー、クレデンシャルなどを許可します。

ブラウザはサーバーからCORSヘッダーを読み取り、リクエストパラメータを確認した後にのみクライアントからの実際のリクエストを許可します。

  • Access-Control-Allow-Origin:正確なドメイン(https ://app.geekflate.com、https: //lab.geekflare.com)またはワイルドカード(*)を指定します
  • Access-Control-Allow-Methods:必要なHTTPメソッド(GET、POST、PUT、DELETE、PATCH、HEAD、OPTIONS)を許可します。
  • Access-Control-Allow-Headers:特定のヘッダーのみを許可する(承認、csrf-token)
  • Access-Control-Allow-Credentials:クロスオリジンクレデンシャル(Cookie、認証ヘッダー)を許可するために使用されるブール値。
  • Access-Control-Max-Age:プリフライト応答をしばらくの間キャッシュするようにブラウザに指示します。
  • Access-Control-Expose-Headers:クライアント側のスクリプトからアクセスできるヘッダーを指定します。

ApacheおよびNginxWebサーバーでCORSを有効にするには、このチュートリアルに従ってください。

ExpressJSでCORSを有効にする

CORSを使用しないExpressJSアプリの例を見てみましょう。

 const express = require('express'); const app = express() app.get('/users', function (req, res, next) { res.json({msg: 'user get'}) }); app.post('/users', function (req, res, next) { res.json({msg: 'user create'}) }); app.put('/users', function (req, res, next) { res.json({msg: 'User update'}) }); app.listen(80, function () { console.log('CORS-enabled web server listening on port 80') })

上記の例では、POST、PUT、GETメソッドのユーザーAPIエンドポイントを有効にしていますが、DELETEメソッドは有効にしていません。

簡単ExpressJSアプリでCORSを有効にするためには、CORSをインストールすることができます

npm install cors

Access-Control-Allow-Origin

すべてのドメインでCORSを有効にする

app.use(cors({ origin: '*' }));

単一ドメインのCORSの有効化

app.use(cors({ origin: 'https://app.geekflare.com' }));

オリジンhttps://app.geekflare.comおよびhttps://lab.geekflare.comのCORSを許可する場合

app.use(cors({ origin: [ 'https://app.geekflare.com', 'https://lab.geekflare.com' ] }));

Access-Control-Allow-Methods

すべてのメソッドでCORSを有効にするには、ExpressJSのCORSモジュールでこのオプションを省略します。 ただし、特定のメソッド(GET、POST、PUT)を有効にする場合。

 app.use(cors({ origin: [ 'https://app.geekflare.com', 'https://lab.geekflare.com' ], methods: ['GET', 'PUT', 'POST'] }));

Access-Control-Allow-Headers

デフォルト以外のヘッダーが実際のリクエストで送信できるようにするために使用されます。

 app.use(cors({ origin: [ 'https://app.geekflare.com', 'https://lab.geekflare.com' ], methods: ['GET', 'PUT', 'POST'], allowedHeaders: ['Content-Type', 'Authorization', 'x-csrf-token'] }));

Access-Control-Allow-Credentials

withCredentialsがtrueに設定されている場合でも、要求に応じてクレデンシャルを許可するようにブラウザに指示したくない場合は、これを省略してください

 app.use(cors({ origin: [ 'https://app.geekflare.com', 'https://lab.geekflare.com' ], methods: ['GET', 'PUT', 'POST'], allowedHeaders: ['Content-Type', 'Authorization', 'x-csrf-token'], credentials: true }));

アクセス制御-最大年齢

ブラウザーを親密にして、指定された秒数の間、飛行前の応答情報をキャッシュにキャッシュします。 応答をキャッシュしたくない場合は、これを省略してください。

 app.use(cors({ origin: [ 'https://app.geekflare.com', 'https://lab.geekflare.com' ], methods: ['GET', 'PUT', 'POST'], allowedHeaders: ['Content-Type', 'Authorization', 'x-csrf-token'], credentials: true, maxAge: 600 }));

キャッシュされたプリフライト応答は、ブラウザで10分間利用できます。

Access-Control-Expose-Headers

 app.use(cors({ origin: [ 'https://app.geekflare.com', 'https://lab.geekflare.com' ], methods: ['GET', 'PUT', 'POST'], allowedHeaders: ['Content-Type', 'Authorization', 'x-csrf-token'], credentials: true, maxAge: 600, exposedHeaders: ['Content-Range', 'X-Content-Range'] }));

ワイルドカード(*)をexposedHeadersに入れると、Authorizationヘッダーは公開されません。 したがって、以下のように明示的に公開する必要があります

app.use(cors({ origin: [ 'https://app.geekflare.com', 'https://lab.geekflare.com' ], methods: ['GET', 'PUT', 'POST'], allowedHeaders: ['Content-Type', 'Authorization', 'x-csrf-token'], credentials: true, maxAge: 600, exposedHeaders: ['*', 'Authorization', ] }));

上記は、すべてのヘッダーとAuthorizationヘッダーも公開します。

HTTPクッキーとは何ですか?

Cookieは、サーバーがクライアントブラウザに送信する小さなデータです。 それ以降のリクエストでは、ブラウザはすべてのリクエストで同じドメインに関連するすべてのCookieを送信します。

Cookieにはその属性があり、必要に応じてCookieの動作を変えるために定義できます。

  • 名前Cookieの名前。
  • 値: cookie-nameに対応するcookieのデータ
  • ドメイン: Cookieは定義されたドメインにのみ送信されます
  • パス:定義されたURLプレフィックスパスの後にのみ送信されるCookie。 path = 'admin /'のようにCookieパスを定義したとします。 URL https://geekflare.com/expire/に対して送信されないが、URLプレフィックスhttps://geekflare.com/admin/で送信されるCookie
  • Max-Age / Expires(秒単位の数値): Cookieの有効期限が切れるのはいつですか。 Cookieの有効期間により、指定された時間が経過するとCookieは無効になります。
  • HTTPOnly(ブール値):バックエンドサーバーはそのHTTPOnly Cookieにアクセスできますが、trueの場合はクライアント側スクリプトにアクセスできません。
  • Secure(Boolean): Cookieは、trueの場合にのみSSL / TLSドメインを介して送信されます。
  • sameSite(string [Strict、Lax、None]):クロスサイトリクエストで送信されるCookieを有効化/制限するために使用されます。 クッキーsameSite詳細については、MDNを参照してください。 Strict、Lax、Noneの3つのオプションを受け入れます。 Cookie構成sameSite = NoneのCookieセキュア値をtrueに設定します。

なぜトークンにHTTPOnlyCookieを使用するのですか?

サーバーから送信されたアクセストークンをローカルストレージインデックス付きDB、 Cookie (HTTPOnlyがtrueに設定されていない)などのクライアント側ストレージに保存すると、XSS攻撃に対してより脆弱になります。 あなたのページのいずれかがXSS攻撃に弱いと仮定します。 攻撃者は、ブラウザに保存されているユーザートークンを悪用する可能性があります。

HTTPOnly Cookieは、サーバー/バックエンドによってのみ設定/取得され、クライアント側では設定されません。

そのHTTPonlyCookieへのアクセスに制限されたクライアント側スクリプト。 したがって、HTTPOnly CookieはXSS攻撃に対して脆弱ではなく、より安全です。 サーバーからのみアクセスできるためです。

CORS対応のバックエンドでHTTPOnlyCookieを有効にする

CORSでCookieを有効にするには、アプリケーション/サーバーで以下の構成が必要です。

  • Access-Control-Allow-Credentialsヘッダーをtrueに設定します。
  • Access-Control-Allow-OriginおよびAccess-Control-Allow-Headersはワイルドカード(*)であってはなりません。
  • CookieのsameSite属性はNoneである必要があります。
  • sameSite値をnoneに有効にするには、secure値をtrueに設定します。SSL/ TLS証明書を使用してバックエンドを有効にしてドメイン名で機能するようにします。

ログインクレデンシャルを確認した後、HTTPOnlyCookieにアクセストークンを設定するサンプルコードを見てみましょう。

 const express = require('express'); const app = express(); const cors = require('cors'); app.use(cors({ origin: [ 'https://app.geekflare.com', 'https://lab.geekflare.com' ], methods: ['GET', 'PUT', 'POST'], allowedHeaders: ['Content-Type', 'Authorization', 'x-csrf-token'], credentials: true, maxAge: 600, exposedHeaders: ['*', 'Authorization' ] })); app.post('/login', function (req, res, next) { res.cookie('access_token', access_token, { expires: new Date(Date.now() + (3600 * 1000 * 24 * 180 * 1)), //second min hour days year secure: true, // set to true if your using https or samesite is none httpOnly: true, // backend only sameSite: 'none' // set to none for cross-request }); res.json({ msg: 'Login Successfully', access_token }); }); app.listen(80, function () { console.log('CORS-enabled web server listening on port 80') });

バックエンド言語とWebサーバーで上記の4つの手順を実装することにより、CORSおよびHTTPOnlyCookieを構成できます。

上記の手順に従うことで、CORSを有効にするためのApacheとNginxのこのチュートリアルに従うことができます。

クロスオリジンリクエストのwithCredentials

デフォルトで同一生成元リクエストで送信される資格情報(Cookie、承認)。 クロスオリジンの場合、withCredentialsをtrueに指定する必要があります。

XMLHttpRequest API:

 var xhr = new XMLHttpRequest(); xhr.open('GET', 'http://api.geekflare.com/user', true); xhr.withCredentials = true; xhr.send(null);

フェッチAPI:

 fetch('http://api.geekflare.com/user', { credentials: 'include' });

JQuery Ajax:

 $.ajax({ url: 'http://api.geekflare.com/user', xhrFields: { withCredentials: true } });

アクシオス:

 axios.defaults.withCredentials = true

結論

上記の記事が、CORSがどのように機能するかを理解し、サーバーでのクロスオリジンリクエストに対してCORSを有効にするのに役立つことを願っています。 HTTPOnlyにCookieを保存することが安全である理由と、クロスオリジンリクエストのクライアントでwithCredentialsを使用する方法。