├── README.md └── cors.conf /README.md: -------------------------------------------------------------------------------- 1 | nginx-cors 2 | ---------- 3 | Turn on the CORS by Feng Shui... maybe 🙃 4 | 5 | ### Featuring 6 | 7 | - [Easy and clear to use](#compare) 8 | - Does not use `add_header` into `if` (therefore, your headers will not be lost) 9 | - Configurable and flexibility 10 | 11 | --- 12 | 13 | ### Articles 14 | 15 | - [NGINX vs. CORS (ru)](https://medium.com/@ibnRubaXa/nginx-vs-cors-7a63029d9a34) 16 | 17 | --- 18 | 19 | ### Install 20 | 21 | ```sh 22 | cd /usr/local/etc/nginx/ 23 | git clone git@github.com:RubaXa/nginx-cors.git 24 | ``` 25 | 26 | --- 27 | 28 | ### Hot to use 29 | 30 | ```sh 31 | cd /usr/local/etc/nginx/ 32 | vim ./cors-enabled.conf 33 | ``` 34 | 35 | #### 1. Create `./cors.setup.conf` 36 | CORS confirugration 37 | 38 | ```nginx 39 | # If in your project setted larger, then this directive is not required 40 | map_hash_bucket_size 256; 41 | 42 | # Setup the $cors_path variable 43 | map $request_uri $cors_path { 44 | ~^(?[^?]+) $path; 45 | } 46 | 47 | # Convert Endpoints to CORS service 48 | map "$scheme://$host$cors_path" $cors_service { 49 | "https://api.project.com/user/info" "cors.service.user-info"; 50 | ~^https:\/\/api.auth.project.com\/(login|logout)$ "cors.service.auth"; 51 | default "<>"; 52 | } 53 | 54 | # Convert Origin to CORS client 55 | map "$http_origin" $cors_client { 56 | # "cors.client.foo" or "cors.client.bar"; 57 | ~^https:\/\/(foo|bar)\.client\.com$ "cors.client.$1"; 58 | default "<>"; 59 | } 60 | 61 | # Turn on CORS by client and service map 62 | map "$cors_client -> $cors_service" $cors_enabled { 63 | # Access for 'foo' client 64 | "cors.client.foo -> cors.service.auth" "true"; 65 | "cors.client.foo -> cors.service.user-info" "true"; 66 | 67 | # Access for 'bar' client 68 | "cors.client.bar -> cors.service.auth" "true"; 69 | } 70 | ``` 71 | 72 | #### 2. Enable CORS on any endpoint 73 | 74 | ```nginx 75 | http { 76 | # 1️⃣Setup CORS for all services (!) 77 | include 'cors.setup.conf'; 78 | 79 | server { 80 | listen 80; 81 | server_name api.project.com; 82 | 83 | location /user/info { 84 | # 2️⃣Enable CORS without credentials/cookies (!!) 85 | include 'nginx-cors/cors.conf'; 86 | } 87 | } 88 | 89 | server { 90 | listen 80; 91 | server_name api.auth.project.com; 92 | 93 | location ~/(login|logout) { 94 | # 3️⃣ Enable CORS with credentials/cookies (!!!) 95 | set $cors_allow_credentials "true"; 96 | include 'nginx-cors/cors.conf'; 97 | } 98 | } 99 | } 100 | ``` 101 | 102 | ### OR without cors service/client (not recommended, see [compare](#compare)) 103 | 104 | ```nginx 105 | location /api/user/exists { 106 | if ($http_origin = 'https://client.com') { 107 | set $cors_enabled 'true'; 108 | } 109 | include 'nginx-cors/cors.conf'; 110 | } 111 | ``` 112 | 113 | --- 114 | 115 | ### Variables 116 | 117 | See detail in source: [cors.conf](./cors.conf#L1-L9) 118 | 119 | --- 120 | 121 | ### Please send a PR if you know how to make it more beautiful and correctly. 122 | 123 | Thx. 124 | 125 | --- 126 | 127 | 128 | 129 | ### Compare 130 | 131 | #### Classical bugy way 💩 132 | 133 | - It's hard to read who exactly enabled CORS 134 | - It is easy to make a mistake in RegExp 135 | - If there is an add_header inside, it will not work 136 | 137 | ```nginx 138 | location ~ '/api/auth/(login|logout)' { 139 | if ($http_origin ~ '^https?://(about|company|account)\.(((alpha|beta|omega)\.))?test\.)?)?business\.com$') 140 | set $cors 'enabled'; 141 | } 142 | 143 | add_header X-My-Header "true"; # it will not work 🐞 144 | 145 | if ($request_method = 'OPTIONS') { 146 | set $cors "preflight-${cors}"; 147 | } 148 | 149 | if ($cors = 'preflight-enabled') { 150 | add_header Access-Control-Allow-Origin $http_origin; 151 | add_header Access-Control-Allow-Headers "..."; 152 | add_header Access-Control-Allow-Methods "..."; 153 | add_header Access-Control-Expose-Headers "X-My-Header"; 154 | return 204; 155 | } 156 | 157 | if ($cors = 'enabled') { 158 | add_header X-My-Header "true"; # but here it will work (1) 159 | add_header Access-Control-Allow-Origin $http_origin; 160 | } 161 | 162 | # bla-bla-bla 163 | } 164 | ``` 165 | 166 | 167 | ### True way 👍 168 | 169 | - Easy to read and maintain. 170 | - Flexible configuration 171 | - Operating not at the RegExp level, but client -> service 172 | 173 | ```nginx 174 | # Setup the $cors_path variable 175 | map $request_uri $cors_path { 176 | ~^(?[^?]+) $path; 177 | } 178 | 179 | # Convert endpoints to CORS service 180 | map "$scheme://$host$cors_path" $cors_service { 181 | ~^https:\/\/project.com/api/(login|logout)$ "cors.service.auth"; 182 | default "<>"; 183 | } 184 | 185 | # Convert Origin to CORS client 186 | map "$http_origin" $cors_client { 187 | # Yes, it would be possible to write in one regexp, but it is much more readable and more flexible. 188 | # In addition, you need to list subdomains! Why so? See next `map` ;] 189 | ~^https:\/\/([^\.]+)\.business\.com$" "cors.client.business.$1"; 190 | ~^https:\/\/([^\.]+)\.test\.business\.com$" "cors.client.business.$1"; 191 | ~^https:\/\/([^\.]+)\.(alpha|beta|omega)\.test\.business\.com$" "cors.client.business.$1"; 192 | default "<>"; 193 | } 194 | 195 | # Turn on CORS by client and service map 196 | map "$cors_client -> $cors_service" $cors_enabled { 197 | # And now we can clearly and elegantly include rights 198 | # based on the "$cors_client" and the "$cors_service" to which "client" invoke! 199 | "cors.client.business.about -> cors.service.auth" "false"; 200 | "cors.client.business.company -> cors.service.auth" "true"; 201 | "cors.client.business.account -> cors.service.auth" "true"; 202 | default "false"; 203 | } 204 | ``` 205 | 206 | and 207 | 208 | ```nginx 209 | location ~ '/api/auth/(login|logout)' { 210 | add_header X-My-Header "true"; # It's work! 🎉 211 | set $cors_allow_expose_headers "X-My-Header"; 212 | include "ngxin-cors/cors.conf"; 213 | } 214 | ``` 215 | -------------------------------------------------------------------------------- /cors.conf: -------------------------------------------------------------------------------- 1 | # $cors_enabled - "true" or "false" (required) 2 | # $cors_allow_methods — string separated by comma (optional, default: see $cors_allow_methods_default) 3 | # $cors_allow_headers — string separated by comma (optional, default: see $cors_allow_headers_default) 4 | # $cors_allow_headers_force — for 'allow header' without default, string separated by comma (optional) 5 | # $cors_allow_credentials — "true" or "false" (optional, default: false) 6 | # $cors_allow_expose_headers — string (optional, default: "Content-Disposition") 7 | # $cors_vary — string or "false" (optional, default: "Origin") 8 | # $cors_max_age — number (optional, default: 86400) 9 | # $cors_verbose - "true" or "false" (optional, default: false) 10 | # $cors_debug - "true" or "false" (optional, default: false) 11 | 12 | 13 | set $cors_preflight 'false'; 14 | set $cors_vary_default 'Origin'; 15 | set $cors_allow_origin $http_origin; 16 | set $cors_allow_methods_default 'OPTIONS, GET, POST'; 17 | set $cors_allow_headers_default 'DNT, Authorization, Origin, X-Requested-With, X-Host, X-Request-Id, Timing-Allow-Origin, Content-Type, Accept, Content-Range, Range, Keep-Alive, User-Agent, If-Modified-Since, Cache-Control, Content-Type'; 18 | set $cors_allow_expose_headers_default 'Content-Disposition'; 19 | 20 | uninitialized_variable_warn off; 21 | 22 | # Default: methods 23 | if ($cors_allow_methods = '') { 24 | set $cors_allow_methods $cors_allow_methods_default; 25 | } 26 | 27 | # Required: headers 28 | if ($cors_allow_headers ~ '.+') { 29 | set $cors_allow_headers "$cors_allow_headers_default, $cors_allow_headers"; 30 | } 31 | 32 | # Default: headers 33 | if ($cors_allow_headers = '') { 34 | set $cors_allow_headers $cors_allow_headers_default; 35 | } 36 | 37 | # Default: allow headers (force) 38 | if ($cors_allow_headers_force = '') { 39 | set $cors_allow_headers_force ''; 40 | } 41 | 42 | # Force: allow headers? 43 | if ($cors_allow_headers_force ~ '.+') { 44 | set $cors_allow_headers $cors_allow_headers_force; 45 | } 46 | 47 | # Default: credentials 48 | if ($cors_allow_credentials = '') { 49 | set $cors_allow_credentials 'false'; 50 | } 51 | 52 | # Default: expose headers 53 | if ($cors_allow_expose_headers = '') { 54 | set $cors_allow_expose_headers $cors_allow_expose_headers_default; 55 | } 56 | 57 | # Default: vary 58 | if ($cors_vary = '') { 59 | set $cors_vary $cors_vary_default; 60 | } 61 | 62 | # Default: max age 63 | if ($cors_max_age = '') { 64 | set $cors_max_age '86400'; 65 | } 66 | 67 | # Check: Vary 68 | if ($cors_vary = 'false') { 69 | set $cors_vary ''; 70 | } 71 | 72 | # Preflight? 73 | if ($request_method = 'OPTIONS') { 74 | set $cors_preflight 'true'; 75 | } 76 | 77 | # Enabled? 78 | if ($cors_enabled !~ '^true$') { 79 | set $cors_enabled 'disabled'; 80 | set $cors_preflight 'disabled'; 81 | set $cors_allow_credentials 'disabled'; 82 | } 83 | 84 | # Default: debug 85 | if ($cors_debug = '') { 86 | set $cors_debug 'false'; 87 | } 88 | 89 | # Default: $cors_path 90 | if ($cors_path = '') { 91 | set $cors_path '<>'; 92 | } 93 | 94 | # Default: $cors_client 95 | if ($cors_client = '') { 96 | set $cors_client '<>'; 97 | } 98 | 99 | # Default: $cors_service 100 | if ($cors_service = '') { 101 | set $cors_service '<>'; 102 | } 103 | 104 | # Allow requested 105 | if ($cors_enabled = 'true') { 106 | set $cors_allow_origin_value $cors_allow_origin; 107 | set $cors_vary_value $cors_vary; 108 | set $cors_allow_expose_headers_value $cors_allow_expose_headers; 109 | } 110 | 111 | # Preflight headers 112 | if ($cors_preflight = 'true') { 113 | set $cors_allow_methods_value $cors_allow_methods; 114 | set $cors_allow_headers_value $cors_allow_headers; 115 | set $cors_max_age_value $cors_max_age; 116 | } 117 | 118 | # With credentials? 119 | if ($cors_allow_credentials = 'true') { 120 | set $cors_allow_credentials_value 'true'; 121 | } 122 | 123 | # Verbose 124 | if ($cors_verbose = '') { 125 | set $cors_verbose 'false'; 126 | } 127 | 128 | if ($cors_verbose = 'true') { 129 | set $cors_enabled_verbose $cors_enabled; 130 | set $cors_service_vebose $cors_service; 131 | set $cors_client_vebose $cors_client; 132 | } 133 | 134 | add_header X-CORS-Verbose-Enabled $cors_enabled_verbose; 135 | add_header X-CORS-Verbose-Service $cors_service_vebose; 136 | add_header X-CORS-Verbose-Client $cors_client_vebose; 137 | 138 | # CORS 139 | add_header Access-Control-Allow-Origin $cors_allow_origin_value always; 140 | add_header Access-Control-Allow-Methods $cors_allow_methods_value; 141 | add_header Access-Control-Allow-Headers $cors_allow_headers_value; 142 | add_header Access-Control-Expose-Headers $cors_allow_expose_headers_value; 143 | add_header Access-Control-Allow-Credentials $cors_allow_credentials_value; 144 | add_header Access-Control-Max-Age $cors_max_age_value; 145 | add_header Vary $cors_vary_value always; 146 | 147 | # Debug 148 | if ($cors_debug = 'true') { 149 | set $cors_debug_enabled $cors_enabled; 150 | set $cors_debug_preflight $cors_preflight; 151 | set $cors_debug_service $cors_service; 152 | set $cors_debug_client $cors_client; 153 | set $cors_debug_path $cors_path; 154 | set $cors_debug_http_origin $http_origin; 155 | set $cors_debug_request "$scheme://$host$request_uri"; 156 | set $cors_debug_request_method $request_method; 157 | set $cors_debug_allow_origin $cors_allow_origin; 158 | set $cors_debug_allow_methods $cors_allow_methods; 159 | set $cors_debug_allow_headers $cors_allow_headers; 160 | set $cors_debug_allow_credentials $cors_allow_credentials; 161 | set $cors_debug_allow_expose_headers $cors_allow_expose_headers; 162 | set $cors_debug_max_age $cors_max_age; 163 | set $cors_debug_vary $cors_vary; 164 | 165 | set $cors_debug_allow_origin_value $cors_allow_origin_value; 166 | set $cors_debug_allow_methods_value $cors_allow_methods_value; 167 | set $cors_debug_allow_headers_value $cors_allow_headers_value; 168 | set $cors_debug_allow_credentials_value $cors_allow_credentials_value; 169 | set $cors_debug_allow_expose_headers_value $cors_allow_expose_headers_value; 170 | set $cors_debug_max_age_value $cors_max_age_value; 171 | } 172 | 173 | add_header X-CORS-Debug-Enabled $cors_debug_enabled always; 174 | add_header X-CORS-Debug-Preflight $cors_debug_preflight always; 175 | add_header X-CORS-Debug-Service $cors_debug_service always; 176 | add_header X-CORS-Debug-Client $cors_debug_client always; 177 | add_header X-CORS-Debug-Path $cors_debug_path always; 178 | add_header X-CORS-Debug-Http-Origin $cors_debug_http_origin always; 179 | add_header X-CORS-Debug-Request $cors_debug_request always; 180 | add_header X-CORS-Debug-Request-Method $cors_debug_request_method always; 181 | 182 | add_header X-CORS-Debug-Var-Origin $cors_debug_allow_origin always; 183 | add_header X-CORS-Debug-Var-Methods $cors_debug_allow_methods always; 184 | add_header X-CORS-Debug-Var-Headers $cors_debug_allow_headers always; 185 | add_header X-CORS-Debug-Var-Credentials $cors_debug_allow_credentials always; 186 | add_header X-CORS-Debug-Var-Headers $cors_debug_allow_expose_headers always; 187 | add_header X-CORS-Debug-Var-Max-Age $cors_debug_max_age always; 188 | add_header X-CORS-Debug-Var-Vary $cors_debug_vary always; 189 | 190 | add_header X-CORS-Debug-Access-Control-Allow-Origin $cors_debug_allow_origin_value always; 191 | add_header X-CORS-Debug-Access-Control-Allow-Methods $cors_debug_allow_methods_value always; 192 | add_header X-CORS-Debug-Access-Control-Allow-Headers $cors_debug_allow_headers_value always; 193 | add_header X-CORS-Debug-Access-Control-Allow-Credentials $cors_debug_allow_credentials_value always; 194 | add_header X-CORS-Debug-Access-Control-Expose-Headers $cors_debug_allow_expose_headers_value always; 195 | add_header X-CORS-Debug-Access-Control-Max-Age $cors_debug_max_age_value always; 196 | 197 | # Preflight 198 | if ($cors_preflight = 'true') { 199 | return 204; 200 | } 201 | --------------------------------------------------------------------------------