Skip to content

Instantly share code, notes, and snippets.

@williscool
Last active August 29, 2015 14:16
Show Gist options
  • Save williscool/46c377faf26011aa505a to your computer and use it in GitHub Desktop.
Save williscool/46c377faf26011aa505a to your computer and use it in GitHub Desktop.
Not as simple as I thought it was at first. (you want to skip out fast if its an options request. and you want to make sure allow creds and methods and origin is set all the time)
server {
listen 80;
server_name yourserver.com;
#
# A CORS (Cross-Origin Resouce Sharing) config for nginx
#
# == Purpose
#
# This nginx configuration enables CORS requests in the following way:
# - enables CORS just for origins on a whitelist specified by a regular expression
# - CORS preflight request (OPTIONS) are responded immediately
# - Access-Control-Allow-Credentials=true for GET and POST requests
# - Access-Control-Max-Age=20days, to minimize repetitive OPTIONS requests
# - various superluous settings to accommodate nonconformant browsers
#
# == Comment on echoing Access-Control-Allow-Origin
#
# How do you allow CORS requests only from certain domains? The last
# published W3C candidate recommendation states that the
# Access-Control-Allow-Origin header can include a list of origins.
# (See: http://www.w3.org/TR/2013/CR-cors-20130129/#access-control-allow-origin-response-header )
# However, browsers do not support this well and it likely will be
# dropped from the spec (see, http://www.rfc-editor.org/errata_search.php?rfc=6454&eid=3249 ).
#
# The usual workaround is for the server to keep a whitelist of
# acceptable origins (as a regular expression), match the request's
# Origin header against the list, and echo back the matched value.
#
# (Yes you can use '*' to accept all origins but this is too open and
# prevents using 'Access-Control-Allow-Credentials: true', which is
# needed for HTTP Basic Access authentication.)
#
# == Comment on spec
#
# Comments below are all based on my reading of the CORS spec as of
# 2013-Jan-29 ( http://www.w3.org/TR/2013/CR-cors-20130129/ ), the
# XMLHttpRequest spec (
# http://www.w3.org/TR/2012/WD-XMLHttpRequest-20121206/ ), and
# experimentation with latest versions of Firefox, Chrome, Safari at
# that point in time.
#
# == Changelog
#
# shared at: https://gist.github.com/algal/5480916
# based on: https://gist.github.com/alexjs/4165271
#
location ^~ /api_endpoint {
# if the request included an Origin: header with an origin on the whitelist,
# then it is some kind of CORS request.
# specifically, this example allow CORS requests from
# scheme : http or https
# authority : any authority ending in ".example.com"
# port : nothing, or :
if ($http_origin ~* (https?://[^/]*\.example\.com(:[0-9]+)?)$) {
set $cors "true";
}
# Nginx doesn't support nested If statements, so we use string
# concatenation to create a flag for compound conditions
# OPTIONS indicates a CORS pre-flight request
if ($request_method = 'OPTIONS') {
set $cors "${cors}options";
}
# if it's OPTIONS, then it's a CORS preflight request so respond immediately with no response body
if ($cors = "trueoptions") {
# Tells the browser this origin may make cross-origin requests
# (Here, we echo the requesting origin, which matched the whitelist.)
add_header 'Access-Control-Allow-Origin' "$http_origin";
# in a preflight response, tells browser the subsequent actual request can include user credentials (e.g., cookies)
add_header 'Access-Control-Allow-Credentials' 'true';
#
# Return special preflight info
#
# Tell browser to cache this pre-flight info for 20 days
add_header 'Access-Control-Max-Age' 1728000;
# Tell browser we respond to GET,POST,OPTIONS in normal CORS requests.
#
# Not officially needed but still included to help non-conforming browsers.
#
# OPTIONS should not be needed here, since the field is used
# to indicate methods allowed for "actual request" not the
# preflight request.
#
# GET,POST also should not be needed, since the "simple
# methods" GET,POST,HEAD are included by default.
#
# We should only need this header for non-simple requests
# methods (e.g., DELETE), or custom request methods (e.g., XMODIFY)
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS, PUT, DELETE';
# Tell browser we accept these headers in the actual request
#
# A dynamic, wide-open config would just echo back all the headers
# listed in the preflight request's
# Access-Control-Request-Headers.
#
# A dynamic, restrictive config, would just echo back the
# subset of Access-Control-Request-Headers headers which are
# allowed for this resource.
#
# This static, fairly open config just returns a hardcoded set of
# headers that covers many cases, including some headers that
# are officially unnecessary but actually needed to support
# non-conforming browsers
#
# Comment on some particular headers below:
#
# Authorization -- practically and officially needed to support
# requests using HTTP Basic Access authentication. Browser JS
# can use HTTP BA authentication with an XmlHttpRequest object
# req by calling
#
# req.withCredentials=true, and
# req.setRequestHeader('Authorization','Basic ' + window.btoa(theusername + ':' + thepassword))
#
# Counterintuitively, the username and password fields on
# XmlHttpRequest#open cannot be used to set the authorization
# field automatically for CORS requests.
#
# Content-Type -- this is a "simple header" only when it's
# value is either application/x-www-form-urlencoded,
# multipart/form-data, or text/plain; and in that case it does
# not officially need to be included. But, if your browser
# code sets the content type as application/json, for example,
# then that makes the header non-simple, and then your server
# must declare that it allows the Content-Type header.
#
# Accept,Accept-Language,Content-Language -- these are the
# "simple headers" and they are officially never
# required. Practically, possibly required.
#
# Origin -- logically, should not need to be explicitly
# required, since it's implicitly required by all of
# CORS. officially, it is unclear if it is required or
# forbidden! practically, probably required by existing
# browsers (Gecko does not request it but WebKit does, so
# WebKit might choke if it's not returned back).
#
# User-Agent,DNT -- officially, should not be required, as
# they cannot be set as "author request headers". practically,
# may be required.
#
# My Comment:
#
# The specs are contradictory, or else just confusing to me,
# in how they describe certain headers as required by CORS but
# forbidden by XmlHttpRequest. The CORS spec says the browser
# is supposed to set Access-Control-Request-Headers to include
# only "author request headers" (section 7.1.5). And then the
# server is supposed to use Access-Control-Allow-Headers to
# echo back the subset of those which is allowed, telling the
# browser that it should not continue and perform the actual
# request if it includes additional headers (section 7.1.5,
# step 8). So this implies the browser client code must take
# care to include all necessary headers as author request
# headers.
#
# However, the spec for XmlHttpRequest#setRequestHeader
# (section 4.6.2) provides a long list of headers which the
# the browser client code is forbidden to set, including for
# instance Origin, DNT (do not track), User-Agent, etc.. This
# is understandable: these are all headers that we want the
# browser itself to control, so that malicious browser client
# code cannot spoof them and for instance pretend to be from a
# different origin, etc..
#
# But if XmlHttpRequest forbids the browser client code from
# setting these (as per the XmlHttpRequest spec), then they
# are not author request headers. And if they are not author
# request headers, then the browser should not include them in
# the preflight request's Access-Control-Request-Headers. And
# if they are not included in Access-Control-Request-Headers,
# then they should not be echoed by
# Access-Control-Allow-Headers. And if they are not echoed by
# Access-Control-Allow-Headers, then the browser should not
# continue and execute actual request. So this seems to imply
# that the CORS and XmlHttpRequest specs forbid certain
# widely-used fields in CORS requests, including the Origin
# field, which they also require for CORS requests.
#
# The bottom line: it seems there are headers needed for the
# web and CORS to work, which at the moment you should
# hard-code into Access-Control-Allow-Headers, although
# official specs imply this should not be necessary.
#
add_header 'Access-Control-Allow-Headers' 'Authorization,Content-Type,Accept,Origin,User-Agent,DNT,Cache-Control,X-Mx-ReqToken,Keep-Alive,X-Requested-With,If-Modified-Since';
# build entire response to the preflight request
# no body in this response
add_header 'Content-Length' 0;
# (should not be necessary, but included for non-conforming browsers)
add_header 'Content-Type' 'text/plain charset=UTF-8';
# indicate successful return with no content
return 204;
}
# --PUT YOUR REGULAR NGINX CODE HERE--
## so we can see what url is being sent to be rewritten
log_format upstreamlog '[$time_local] to: $upstream_addr: $request upstream_response_time $upstream_response_time msec $msec request_time $request_time';
access_log /var/log/nginx/access.log upstreamlog;
add_header 'Access-Control-Allow-Credentials' 'true';
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS, PUT, DELETE';
add_header 'Access-Control-Allow-Origin' "$http_origin";
proxy_hide_header 'Access-Control-Allow-Origin'; # remove extra allow origin because you are only allowed to declare this once
rewrite /whateverapi/(.*) /$1 break;
proxy_pass https://api.whatever.com; # pass reqs for api to whatever
}
location / {
proxy_pass http://127.0.0.1:3000; # pass requests for most things to your application server
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment