概要
No 'Access-Control-Allow-Origin' header is present on the requested resource.
というエラーが出た際に外部API(サーバ側)でどう対応すべきかをまとめました。
CORSでググると幸せになれます。
環境
- go 1.7.4
- gin 1.1.4
CORSが必要になるのはどんな時か
シンプルに言うと以下の条件のときです。
ref: オリジン間リソース共有 (CORS) - HTTP | MDN
CORSのリクエストは2種類
以下の2つがあります。
preflightリクエストを送るかどうかはブラウザが決めており、以下の条件全てを満たすリクエスト(単純リクエストと呼ばれる)以外は自動で送られます。
- HTTPメソッドがGET, POST, HEADのいずれか
- HTTPヘッダにAccept, Accept-Language, Content-Language, Content-Type以外のフィールドが含まれない
- Content-Typeの値はapplication/x-www-form-urlencoded, multipart/form-data, text/plainのいずれか
a. そのままAPIへリクエスト
この場合は外部APIからのレスポンスヘッダに
レスポンスヘッダ | 説明 | 具体例 |
---|---|---|
Access-Control-Allow-Origin | 許可するリクエスト元のURL | "*", "http://www.example.com"など |
が設定されていればOKです。
b. 先にpreflightリクエストを送ってから、その後でAPIへリクエスト
Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource
というエラーが出てる場合はこちらです。
この場合はまずはpreflightリクエストにレスポンスを返す口を用意する必要があります。
またそのレスポンスでは
レスポンスヘッダ | 説明 | 具体例 |
---|---|---|
Access-Control-Allow-Origin | 許可するリクエスト元のURL | "*", "http://www.example.com"など |
Access-Control-Allow-Methods | 次に送る実際のAPIコールで使用するメソッドを含ませる | "GET,POST,PUT,DELETE"など |
Access-Control-Allow-Headers | 次に送る実際のAPIコールで使用するヘッダを含ませる | "content-type,X-User-Id"など |
という各種ヘッダをつけて返す必要があります。
Access-Control-Allow-Headers
についてはpreflightリクエストが自動で次に使うヘッダの一覧をAccess-Control-Request-Headers
というリクエストヘッダに追加するので、サーバ側はこれをみて返せば大丈夫です。
Access-Control-Allow-Headersのセーフリスト
Access-Control-Allow-Headers
にはCORS セーフリストリクエストヘッダーというものがあり、
- Accept
- Accept-Language
- Content-Language
- Content-Type
は常に許可されており、このヘッダーに含める必要はありません。
しかしこれらのヘッダーを含めることで追加の制約の適用を回避することができます。
Content-Type: application/json
を使っていたり、値の長さが非常に長かったりするとこの制約にひっかかるのでその場合は明示的に書くようにしましょう。
Authorization ヘッダーはワイルドカードの対象にならない
Access-Control-Allow-Headers
はワイルドカード値を使うこともできますが、
Access-Control-Allow-Headers: *
Authorization ヘッダーはワイルドカードの対象にならないので明示的に列挙する必要があります。
Access-Control-Allow-Headers: *, Authorization
具体的な実装方針
ミドルウェアとして挟むのが疎に設計できて良いと思います。
こちらを参考に作ります。
前提として
- OPTIONSは全てpreflightリクエストとみなす
xxx.example.com
のサービスからならアクセス可能- ↑であれば"Access-Control-Allow-Origin"をリクエストの
Origin
ヘッダの値にする
とします。
ginというgolangのwebフレームワークのミドルウェアとして用意すると以下のようになります。
var reg = regexp.MustCompile("https?:\\/\\/(.+)\\.example\\.com:?.*") func CORS() gin.HandlerFunc { return func(c *gin.Context) { origin := c.Request.Header.Get("Origin") // for preflight if c.Request.Method == "OPTIONS" { r := reg.Copy() if r.MatchString(origin) { headers := c.Request.Header.Get("Access-Control-Request-Headers") c.Writer.Header().Set("Access-Control-Allow-Origin", origin) c.Writer.Header().Set("Access-Control-Allow-Methods", "GET,HEAD,PUT,PATCH,POST,DELETE") c.Writer.Header().Set("Access-Control-Allow-Headers", headers) c.Data(200, "text/plain", []byte{}) c.Abort() } else { c.Data(403, "text/plain", []byte{}) c.Abort() } } else { // for actual response c.Writer.Header().Set("Access-Control-Allow-Origin", origin) //c.Writer.Header().Set("Access-Control-Expose-Headers", "") c.Next() } return } }
その他
よく見るレスポンスヘッダ
レスポンスヘッダ | 説明 | 具体例 |
---|---|---|
Access-Control-Allow-Credentials | cookieの送信をOKするか。 ただし "Access-Control-Allow-Origin", "*" の場合は使えない |
true |
Access-Control-Expose-Headers | APIから返ってくるレスポンスヘッダの内、Javascriptでアクセスしてよいとするヘッダ | "X-User-Id" |
Access-Control-Expose-Headers
セーフリスト
以下のレスポンスヘッダに関してはAccess-Control-Expose-Headers
で指定せずとも、クライアントはアクセスすることができます。
- Cache-Control
- Content-Language
- Content-Length
- Content-Type
- Expires
- Last-Modified
- Pragma
Authorization ヘッダーはワイルドカードの対象にならない
Access-Control-Expose-Headers
はワイルドカード値を使うこともできますが、
Access-Control-Expose-Headers: *
Authorization ヘッダーはワイルドカードの対象にならないので明示的に列挙する必要があります。
Access-Control-Expose-Headers: *, Authorization
CORS とキャッシング
Access-Control-Allow-Origin
の値が"*"
ではなく、具体的なOriginである場合、レスポンスにVary
レスポンスヘッダーに Origin という値を設定して、 Origin リクエストヘッダーの値によって値が変わることをCDNやブラウザに示してください。
Access-Control-Allow-Origin: https://xxx.example.com Vary: Origin
ref: オリジン間リソース共有 (CORS) - HTTP | MDN