今回達成すること
前回作成したNuxtのFunctionsリクエストボタンの通信を行えるように、FunctionsにCORS設定を行います。
実装の中で
- オリジンとは?
- CORSとは?
- プリフライトリクエストとは?
について解説します。
CORSエラーを確認する
Functionsのサーバーを起動して、
% yarn functions:dev
トップページのボタンをクリックしてみましょう。
ネットワークエラーが発生しました。
これは、異なるオリジン間の通信を拒否するブラウザの仕様です。
オリジンとは?
オリジンとは、
- プロトコル ...
http
- ドメイン ...
localhost
- ポート ...
3000
を組み合わせたものを言います。
Document: Origin (オリジン) | MDN
クロスオリジンリソースシェアリング(CORS)とは?
通常、同じオリジン間の通信は、自分のアプリから自分のアプリへのリクエストなので許容されます。
今回は http:localhost:3000
=> http:localhost:8888
への異なるオリジン間の通信となります。
このような通信を何でも許容すると、外部からの悪意のあるリクエストにより、サーバーのデータが抜き取られてしまいます。
そこでブラウザには、異なるオリジン間の通信にアクセス権を与えるようにブラウザに指示する仕組みが用意されています。
その仕組みを「クロスオリジンリソースシェアリング(CORS)」と呼びます。
- クロスオリジンリソースシェアリング => 異なるオリジン間でリソースを共有する仕組み
Document: オリジン間リソース共有 (CORS) - HTTP | MDN
異なるオリジンのリクエストを許可するには?
特定のオリジンのリクエストを許可するには、レスポンスヘッダにAccess-Control-Allow-Origin
を追加し、許容するオリジンを指定します。
netlify/functions/form-validator/form-validator.ts
import { Handler, HandlerEvent, HandlerContext } from '@netlify/functions'
/* eslint require-await: 'off' */
// eslint Doc: https://eslint.org/docs/user-guide/configuring/rules
export const handler: Handler = async (_event: HandlerEvent, _context: HandlerContext) => {
const name: string = 'World'
// 追加
const headers: { [key: string]: string } = {
'Access-Control-Allow-Origin': 'http://localhost:3000'
}
return {
statusCode: 200,
// 追加
headers,
body: JSON.stringify({
message: `Hello, ${name}!`
})
}
}
もう一度ボタンをクリックしてみましょう。
FunctionsからHello World
が返されます。
これで特定のオリジンからのリクエストを許可する設定ができました。
プリフライトリクエストに対応する
上記Nuxtからのリクエストは、シンプルリクエストという種類のリクエストに該当します。
シンプルリクエストの要件: Simple requests - HTTP | MDN
Nuxtからのリクエストにパラメーターを持たせた場合、
pages/index.vue
@Component
export default class IndexPage extends Vue {
async requestFunctions (): Promise<void> {
// $postに書き換え
await this.$axios.$post(
'/.netlify/functions/form-validator',
// パラメーターの追加
{ data: 'Nuxt request parameter' }
)
.then((response: { message: string }) => {
console.log(response)
})
}
}
リクエストヘッダには、Content-Type: application/json
が付与されます。
リクエストヘッダにContent-Type: application/json
が付与された場合、シンプルリクエストの要件から外れます。
この時ブラウザは、プリフライトリクエストを実行します。
プリフライトリクエストとは?
リクエスト送信前に「該当のリクエストは、安全に送信できるリクエストであるか?」を判断するためのHTTPリクエストです。
シンプルなリクエストに該当しない場合、ブラウザが自動で送信します。
リクエストメソッドは、OPTIONS
となります。
httpMethodを確認する
event.httpMethod
のログをとって確認しましょう。
netlify/functions/form-validator/form-validator.ts
...
export const handler: Handler = async (event: HandlerEvent, _context: HandlerContext) => {
// 追加
console.log(event.httpMethod)
...
}
FunctionsサーバーのログにはPOST
リクエストに到達する前のOPTIONS
が表示されます。
localhost:8888サーバーログ
Request from ::1: OPTIONS /.netlify/functions/form-validator
OPTIONS
これがプリフライトリクエストの実態です。
プリフライトリクエストに対応するには?
OPTIONS
のリクエストに対して、通信が安全であることを伝えます。
リクエストヘッダにContent-Type: application/json
が付与された場合、
レスポンスヘッダは'Access-Control-Allow-Headers'
を返します。
また異なるオリジンなので、Access-Control-Allow-Origin
のヘッダも返す必要があります。
リクエストヘッダ | 対応するレスポンスヘッダ | レスポンスの値 |
---|---|---|
Content-Type: application/json | Access-Control-Allow-Headers | Content-Type |
Origin: 異なるオリジン | Access-Control-Allow-Origin | 許容するオリジン |
プリフライトリクエストまとめ
axios
の$post()
メソッドでパラメーターを持たせた場合、リクエストヘッダにはContent-Type: application/json
が付与される。Content-Type: application/json
が付与されたリクエストは、プリフライトリクエストが実行される。- プリフライトリクエストとは、安全に送信できるリクエストであるか?を判断するリクエスト。ブラウザによって自動送信される。
- つまり、シンプルリクエストでは無いリクエストには
OPTIONS
と該当のリクエスト、2つのリクエストが送信される。 OPTIONS
のリクエストに対して「安全なリクエストである」事を通知するには、'Access-Control-Allow-Headers': 'Content-Type'
と'Access-Control-Allow-Origin': '許容するオリジン'
を返す。
- 上記の「安全なリクエストである事を通知する設定」をCORS設定という。
FunctionsプロジェクトにCORS設定を行う
ここまでの知識を踏まえた
netlify/functions/form-validator/form-validator.ts
import { Handler, HandlerEvent, HandlerContext } from '@netlify/functions'
/* eslint require-await: 'off' */
// eslint Doc: https://eslint.org/docs/user-guide/configuring/rules
export const handler: Handler = async (event: HandlerEvent, _context: HandlerContext) => {
// 許容するオリジンリスト
const allowedOrigins: string[] = [
'http://localhost:3000'
/* 本番環境のカスタムドメインはここに追加する */
]
// リクエストオリジン
const requestOrigin: string | undefined = event.headers.origin
// レスポンスヘッダ
let headers: { [key: string]: string } | undefined
// リクエストオリジンが存在し、許容オリジンリストに含まれている場合
if (requestOrigin && allowedOrigins.includes(requestOrigin)) {
// レスポンスヘッダの値を用意
headers = {
'Access-Control-Allow-Headers': 'Content-Type',
'Access-Control-Allow-Origin': requestOrigin
}
}
// プリフライトリクエストの場合
if (event.httpMethod === 'OPTIONS') {
// 安全なリクエストであることを伝える
if (headers) {
return {
statusCode: 200,
headers
}
}
// headersが存在しない場合のレスポンス
return {
statusCode: 500
}
// リクエストがPOSTではない場合
} else if (event.httpMethod !== 'POST') {
// 405エラーの発生
return {
statusCode: 405,
body: 'Method Not Allowed'
}
}
/*
プリフライトリクエスト通過 && POSTリクエストの場合
*/
const name: string = 'World'
return {
statusCode: 200,
headers,
body: JSON.stringify({
message: `Hello, ${name}!`
})
}
}
-
const allowedOrigins
... 許容するオリジンを配列で指定。 -
allowedOrigins.includes(requestOrigin)
... リクエストオリジンが許容オリジン配列に含まれていればtrue
を返す。 -
if (event.httpMethod === 'OPTIONS')
... プリフライトリクエストの場合は、headers
の値を確認し、存在すればCORS設定を行なったheaders
を返す。これにより、安全な通信であることを保証している。 -
else if (event.httpMethod !== 'POST')
... リクエストがPOST
ではない場合はエラーを返し、処理を終了している。
CORS設定を確認しよう
Functionsサーバーを起動して、ブラウザで確認してみましょう。
yarn functions:dev
ボタンをクリックすると、message
が返ってきました。
Functionsサーバーのログを確認すると、OPTIONS
とPOST
双方に200
のステータスコードが返ってきています。
許容しないオリジンからのリクエストを行う
許容しないオリジンからのリクエストにエラーを返しているか確認します。
Nuxtサーバーを停止し、ポート8080
で起動します。
% yarn dev --port 8080
ポート8080
の場合では、ネットワークエラーが返ってきており、ログを見るとCORSエラーが発生しています。
FunctionsサーバーはOPTIONS
の時点で500
が返ってきていますね。
OK!!これで想定通りの挙動となりました。
確認が取れたので、Nuxtサーバーは3000
番で再起動しておいてください。
% yarn dev
今回の作業は以上です。
% git commit -am "Add Netlify Functions cors settings"
まとめと次回
今回は、Nuxtからのリクエストに応えられるようFunctionsにCORS設定を行いました。
この知識を理解していれば、モジュール無しで異なるオリジン間の通信設定を行うことができますね。
次回は、Functions内で使用する自作ライブラリを作成し、CORS設定の処理メソッドを作成します。
そしてフォームのバリデーションを行うFunctionsプロジェクトを完成させましょう。