Cors problem with nginx/django from react app on docker - reactjs

I have a question about cors implementation in django.
Having a problem with setting the correct cors values.
My deployment is on docker.
I have a deployed 3 containers:
backend: Django + DRF as backend (expose 8000 port)
Nginx to server my backend (use exposed 8000 port and set it to 1338)
frontend React app used with nginx (uses port 1337)
Everything is on localhost.
I use axios from frontend to call get/post requests. (I call to 1338 port then I think it is redirected to internal service on 8000 port)
For backend I had to install django-cors-headers package to work with CORS.
I think I set up it correctly. But there are scenarios where it does not work.
In settings.py
INSTALLED_APPS = [
...
"corsheaders",
]
...
MIDDLEWARE = [
...
"corsheaders.middleware.CorsMiddleware",
"django.middleware.common.CommonMiddleware",
...
]
Nginx.conf for nginx image:
upstream backend {
server backend:8000;
}
server {
listen 80;
add_header Access-Control-Allow-Origin *;
location / {
proxy_pass http://backend;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $host:1337;
proxy_redirect off;
}
location /static/ {
alias /home/app/web/staticfiles/;
}
}
First scenario
In settings.py
CORS_ALLOW_ALL_ORIGINS = True
No get/post requests work. Get message:
CORS Multiple Origin Not Allowed
Second scenario
In settings.py
CORS_ALLOWED_ORIGINS = ["http://localhost:1337"]
Works with get requests, but does not work with post requests.
For post requests:
options with error: CORS Missing Allow Header
post with error: NS_ERROR_DOM_BAD_URI
It works if I am not using nginx for backend.
Adding request headers as requested in the comment.
I am not sure what else could I add here. So my deployed project is here (it also is easy to launch if you have docker on your machine:
https://gitlab.com/k.impolevicius/app-001

I have come across this issue a while back, and I think the issue is with the headers.
In the MDN docs, it is stated here that other than for the simple requests, we'll get preflighted requests with OPTIONS method. There are 3 main headers that we need to send in response in your case
Access-Control-Allow-Origin: http://localhost:1337
Access-Control-Allow-Methods: POST, GET, OPTIONS
Access-Control-Allow-Headers: Content-Type
From the looks of it you have configured the first header and you should be seeing it in the network tab too, and since the error is about missing allow headers, you need to add Access-Control-Allow-Methods header
to your nginx file
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
Seeing your network tab on the requested headers will give more context here, generally you should be seeing Access-Control-Request-Method and Access-Control-Request-Headers headers in the OPTIONS request. If there are some headers that you aren't allowing, please write an nginx rule for the same. You can look into this solution for more reference

Good day, seems that problem is in axios.post not in Django or nginx.
Because if you do curl post or post from drf browsable api, it works fine:
curl -X POST http://localhost:1337/Country/ \
-H "Content-Type: application/json" \
-d '{"name": "other"}'
{"id":6,"name":"other"}%
But react app generates this OPTIONS request:
app-001-backend-1 | [13/May/2022 05:56:20] "OPTIONS /Country/ HTTP/1.0" 200 0
app-001-nginx-1 | 192.168.240.1 - - [13/May/2022:05:56:20 +0000] "OPTIONS /Country/ HTTP/1.1" 200 0 "http://localhost:1338/" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.4 Safari/605.1.15" "-"

Related

How to get status response from fetch URL [duplicate]

Mod note: This question is about why XMLHttpRequest/fetch/etc. on the browser are subject to the Same Access Policy restrictions (you get errors mentioning CORB or CORS) while Postman is not. This question is not about how to fix a "No 'Access-Control-Allow-Origin'..." error. It's about why they happen.
Please stop posting:
CORS configurations for every language/framework under the sun. Instead find your relevant language/framework's question.
3rd party services that allow a request to circumvent CORS
Command line options for turning off CORS for various browsers
I am trying to do authorization using JavaScript by connecting to the RESTful API built-in Flask. However, when I make the request, I get the following error:
XMLHttpRequest cannot load http://myApiUrl/login.
No 'Access-Control-Allow-Origin' header is present on the requested resource.
Origin 'null' is therefore not allowed access.
I know that the API or remote resource must set the header, but why did it work when I made the request via the Chrome extension Postman?
This is the request code:
$.ajax({
type: 'POST',
dataType: 'text',
url: api,
username: 'user',
password: 'pass',
crossDomain: true,
xhrFields: {
withCredentials: true,
},
})
.done(function (data) {
console.log('done');
})
.fail(function (xhr, textStatus, errorThrown) {
alert(xhr.responseText);
alert(textStatus);
});
If I understood it right you are doing an XMLHttpRequest to a different domain than your page is on. So the browser is blocking it as it usually allows a request in the same origin for security reasons. You need to do something different when you want to do a cross-domain request.
When you are using Postman they are not restricted by this policy. Quoted from Cross-Origin XMLHttpRequest:
Regular web pages can use the XMLHttpRequest object to send and receive data from remote servers, but they're limited by the same origin policy. Extensions aren't so limited. An extension can talk to remote servers outside of its origin, as long as it first requests cross-origin permissions.
WARNING: Using Access-Control-Allow-Origin: * can make your API/website vulnerable to cross-site request forgery (CSRF) attacks. Make certain you understand the risks before using this code.
It's very simple to solve if you are using PHP. Just add the following script in the beginning of your PHP page which handles the request:
<?php header('Access-Control-Allow-Origin: *'); ?>
If you are using Node-red you have to allow CORS in the node-red/settings.js file by un-commenting the following lines:
// The following property can be used to configure cross-origin resource sharing
// in the HTTP nodes.
// See https://github.com/troygoode/node-cors#configuration-options for
// details on its contents. The following is a basic permissive set of options:
httpNodeCors: {
origin: "*",
methods: "GET,PUT,POST,DELETE"
},
If you are using Flask same as the question; you have first to install flask-cors
pip install -U flask-cors
Then include the Flask cors package in your application.
from flask_cors import CORS
A simple application will look like:
from flask import Flask
from flask_cors import CORS
app = Flask(__name__)
CORS(app)
#app.route("/")
def helloWorld():
return "Hello, cross-origin-world!"
For more details, you can check the Flask documentation.
Because
$.ajax({type: "POST" - calls OPTIONS
$.post( - calls POST
Both are different. Postman calls "POST" properly, but when we call it, it will be "OPTIONS".
For C# web services - Web API
Please add the following code in your web.config file under the <system.webServer> tag. This will work:
<httpProtocol>
<customHeaders>
<add name="Access-Control-Allow-Origin" value="*" />
</customHeaders>
</httpProtocol>
Please make sure you are not doing any mistake in the Ajax call.
jQuery
$.ajax({
url: 'http://mysite.microsoft.sample.xyz.com/api/mycall',
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
type: "POST", /* or type:"GET" or type:"PUT" */
dataType: "json",
data: {
},
success: function (result) {
console.log(result);
},
error: function () {
console.log("error");
}
});
Note: If you are looking for downloading content from a third-party website then this will not help you. You can try the following code, but not JavaScript.
System.Net.WebClient wc = new System.Net.WebClient();
string str = wc.DownloadString("http://mysite.microsoft.sample.xyz.com/api/mycall");
Deep
In the below investigation as API, I use http://example.com instead of http://myApiUrl/login from your question, because this first one working. I assume that your page is on http://my-site.local:8088.
NOTE: The API and your page have different domains!
The reason why you see different results is that Postman:
set header Host=example.com (your API)
NOT set header Origin
Postman actually not use your website url at all (you only type your API address into Postman) - he only send request to API, so he assume that website has same address as API (browser not assume this)
This is similar to browsers' way of sending requests when the site and API has the same domain (browsers also set the header item Referer=http://my-site.local:8088, however I don't see it in Postman). When Origin header is not set, usually servers allow such requests by default.
This is the standard way how Postman sends requests. But a browser sends requests differently when your site and API have different domains, and then CORS occurs and the browser automatically:
sets header Host=example.com (yours as API)
sets header Origin=http://my-site.local:8088 (your site)
(The header Referer has the same value as Origin). And now in Chrome's Console & Networks tab you will see:
When you have Host != Origin this is CORS, and when the server detects such a request, it usually blocks it by default.
Origin=null is set when you open HTML content from a local directory, and it sends a request. The same situation is when you send a request inside an <iframe>, like in the below snippet (but here the Host header is not set at all) - in general, everywhere the HTML specification says opaque origin, you can translate that to Origin=null. More information about this you can find here.
fetch('http://example.com/api', {method: 'POST'});
Look on chrome-console > network tab
If you do not use a simple CORS request, usually the browser automatically also sends an OPTIONS request before sending the main request - more information is here. The snippet below shows it:
fetch('http://example.com/api', {
method: 'POST',
headers: { 'Content-Type': 'application/json'}
});
Look in chrome-console -> network tab to 'api' request.
This is the OPTIONS request (the server does not allow sending a POST request)
You can change the configuration of your server to allow CORS requests.
Here is an example configuration which turns on CORS on nginx (nginx.conf file) - be very careful with setting always/"$http_origin" for nginx and "*" for Apache - this will unblock CORS from any domain (in production instead of stars use your concrete page adres which consume your api)
location ~ ^/index\.php(/|$) {
...
add_header 'Access-Control-Allow-Origin' "$http_origin" always;
add_header 'Access-Control-Allow-Credentials' 'true' always;
if ($request_method = OPTIONS) {
add_header 'Access-Control-Allow-Origin' "$http_origin"; # DO NOT remove THIS LINES (doubled with outside 'if' above)
add_header 'Access-Control-Allow-Credentials' 'true';
add_header 'Access-Control-Max-Age' 1728000; # cache preflight value for 20 days
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
add_header 'Access-Control-Allow-Headers' 'My-First-Header,My-Second-Header,Authorization,Content-Type,Accept,Origin';
add_header 'Content-Length' 0;
add_header 'Content-Type' 'text/plain charset=UTF-8';
return 204;
}
}
Here is an example configuration which turns on CORS on Apache (.htaccess file)
# ------------------------------------------------------------------------------
# | Cross-domain Ajax requests |
# ------------------------------------------------------------------------------
# Enable cross-origin Ajax requests.
# http://code.google.com/p/html5security/wiki/CrossOriginRequestSecurity
# http://enable-cors.org/
# <IfModule mod_headers.c>
# Header set Access-Control-Allow-Origin "*"
# </IfModule>
# Header set Header set Access-Control-Allow-Origin "*"
# Header always set Access-Control-Allow-Credentials "true"
Access-Control-Allow-Origin "http://your-page.com:80"
Header always set Access-Control-Allow-Methods "POST, GET, OPTIONS, DELETE, PUT"
Header always set Access-Control-Allow-Headers "My-First-Header,My-Second-Header,Authorization, content-type, csrf-token"
Applying a CORS restriction is a security feature defined by a server and implemented by a browser.
The browser looks at the CORS policy of the server and respects it.
However, the Postman tool does not bother about the CORS policy of the server.
That is why the CORS error appears in the browser, but not in Postman.
The error you get is due to the CORS standard, which sets some restrictions on how JavaScript can perform ajax requests.
The CORS standard is a client-side standard, implemented in the browser. So it is the browser which prevent the call from completing and generates the error message - not the server.
Postman does not implement the CORS restrictions, which is why you don't see the same error when making the same call from Postman.
Why doesn't Postman implement CORS? CORS defines the restrictions relative to the origin (URL domain) of the page which initiates the request. But in Postman the requests doesn't originate from a page with an URL so CORS does not apply.
Solution & Issue Origins
You are making a XMLHttpRequest to different domains, example:
Domain one: some-domain.com
Domain Two: some-different-domain.com
This difference in domain names triggers CORS (Cross-Origin Resource Sharing) policy called SOP (Same-Origin Policy) that enforces the use of same domains (hence Origin) in Ajax, XMLHttpRequest and other HTTP requests.
Why did it work when I made the request via the Chrome extension
Postman?
A client (most Browsers and Development Tools) has a choice to enforce the Same-Origin Policy.
Most browsers enforce the policy of Same-Origin Policy to prevent issues related to CSRF (Cross-Site Request Forgery) attack.
Postman as a development tool chooses not to enforce SOP while some browsers enforce, this is why you can send requests via Postman that you cannot send with XMLHttpRequest via JS using the browser.
For browser testing purposes:
Windows - Run:
chrome.exe --user-data-dir="C://Chrome dev session" --disable-web-security
The command above will disable chrome web security. So for example if you work on a local project and encounter CORS policy issue when trying to make a request, you can skip this type of error with the above command. Basically it will open a new chrome session.
You might also get this error if your gateway timeout is too short and the resource you are accessing takes longer to process than the timeout. This may be the case for complex database queries etc. Thus, the above error code can be disguishing this problem. Just check if the error code is 504 instead of 404 as in Kamil's answer or something else. If it is 504, then increasing the gateway timeout might fix the problem.
In my case the CORS error could be removed by disabling the same origin policy (CORS) in the Internet Explorer browser, see How to disable same origin policy Internet Explorer. After doing this, it was a pure 504 error in the log.
To resolve this issue, write this line of code in your doGet() or doPost() function whichever you are using in backend
response.setHeader("Access-Control-Allow-Origin", "*");
Instead of "*" you can type in the website or API URL endpoint which is accessing the website else it will be public.
Your IP address is not whitelisted, so you are getting this error.
Ask the backend staff to whitelist your IP address for the service you are accessing.
Access-Control-Allow-Headers
For me I got this issue for different reason, the remote domain was added to origins the deployed app works perfectly except one end point I got this issue:
Origin https://mai-frontend.vercel.app is not allowed by Access-Control-Allow-Origin. Status code: 500
and
Fetch API cannot load https://sciigo.herokuapp.com/recommendations/recommendationsByUser/8f1bb29e-8ce6-4df2-b138-ffe53650dbab due to access control checks.
I discovered that my Heroku database table does not contains all the columns of my local table after updating Heroku database table everything worked well.
It works for me by applying this middleware in globally:
<?php
namespace App\Http\Middleware;
use Closure;
class Cors {
public function handle($request, Closure $next) {
return $next($request)
->header('Access-Control-Allow-Origin', '*')
->header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS')
->header('Access-Control-Allow-Headers', "Accept,authorization,Authorization, Content-Type");
}
}

cors: strict-origin-when-cross-origin: react + nginx + elasticsearch

This afternoon I was make some small adaptations to my react app. However, when trying to fetch information from my elasticsearch server, I receive a strict-origin-when-cross-origin error. I have received CORS errors in the past and was always able to deal with them in a certain way, but this time I am quit stuck.
My set up:
I have the react app, using axios, making the following get request:
const authorization = process.env.REACT_APP_ELASTICSEARCH_AUTHORIZATION;
const header = {
'Authorization': authorization,
}
axios.get(`${path}/${searchIndex}/_search`,
{headers: header}
The request is then send to my proxy server running nginx.
The setting there are the following:
location /somelocation/ {
proxy_pass https://someip:someport/;
proxy_redirect off;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $http_host;
proxy_pass_header Access-Control-Allow-Origin;
proxy_pass_header Access-Control-Allow-Methods;
proxy_hide_header Access-Control-Allow-Headers;
add_header Access-Control-Allow-Headers 'X-Requested-With, Content-Type';
add_header Access-Control-Allow-Credentials true;
proxy_pass_header Authorization;
}
On the elasticsearch server I have the following CORS settings:
http.cors.enabled: true
http.cors.allow-credentials: true
http.cors.allow-origin: '*'
http.cors.allow-headers: X-Requested-With, X-Auth-Token, Content-Type, Content-Length, Authorization, Access-Control-Allow-Headers, Accept
http.cors.allow-methods: OPTIONS, HEAD, GET, POST, PUT, DELETE
Making requests, using the path that the react app is using, from postman works perfectly fine. I can also perform the request from my browser, after passing the necessary username + password. Only the react app (in development running on localhost) does not seem to work.
From https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS/Errors I understand:
strict-origin-when-cross-origin (default)
Send the origin, path, and querystring when performing a same-origin request. For cross-origin requests send the origin (only) when the protocol security level stays same (HTTPS→HTTPS). Don't send the Referer header to less secure destinations (HTTPS→HTTP).
I thought that maybe the problem was situated with the fact that the app runs on HTTP and not HTTPS, but running the app via 'HTTPS=true npm start' did not solve the problem. I have tried different tweaks, both on the request side (axios) as in nginx or in the es yml file, but nothing seems to work. Thank you and kind regards for your help.
EDIT:
included screenshots now as well:
Ok I was able to solve the error in the course of this afternoon. First of all, not all the error messages were showing in my console, which made it fo course more difficult to detect the error. As pointed out correctly above, the error message I was seeing, was just a generic message. In my console I needed to update the settings to the following:
After that the complete CORS errors were visible, which required me to make some changes, both to the NGINX proxy server and the elasticsearch server.
The first error was: "header field authorization is not allowed by Access-Control-Allow-Headers in preflight response"
Although the elasticsearch server allowed the Authorization header, this was -correct me if I am wrong- not properly passed on by the NGINX proxy server, since the settings there were: "proxy_hide_header Access-Control-Allow-Headers;"
I changed the NGINX settings to:
proxy_pass https://******:9200/;
proxy_redirect off;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $http_host;
proxy_pass_header Access-Control-Allow-Origin;
proxy_pass_header Access-Control-Allow-Methods;
# proxy_hide_header Access-Control-Allow-Headers;
proxy_pass_header Access-Control-Allow-Headers;
# add_header Access-Control-Allow-Headers 'X-Requested-With, Content-Type';
# add_header Access-Control-Allow-Credentials true;
proxy_pass_header Authorization;
The lines that are commented out are the old configurations.
Attempting a several new requests, other CORS policies were shown, such as access-control-allow-methods is not allowed by Access-control-Allow-Headers, access-control-allow-origin is not allowed by Access-Control-Allow-Headers,... At that point the errors were clearly indicating what to do, namely adding these fields to the Access-Control-Allow-Headers section in the elasticsearch configuration file.
After completing everything, the elasticsearch config yml file looks like this:
http.cors.enabled: true
http.cors.allow-credentials: true
http.cors.allow-origin: '*'
http.cors.allow-headers: X-Requested-With, X-Auth-Token, Content-Type, Content-Length, Authorization, Access-Control-Allow-Headers, Accept, Access-Control-Allow-Methods, Access-Control-Allow-Origin, Access-Control-Allow-Credentials
http.cors.allow-methods: OPTIONS, HEAD, GET, POST, PUT, DELETE
So I added: Access-Control-Allow-Methods, Access-Control-Allow-Origin, Access-Control-Allow-Credentials.
Thanks everyone for helping me out, I post this answer so it might be used for future purposes. Feel free to further correct me.
Add both lines to the below file
/etc/nginx/sites-available/yours_conf_file
add_header 'Access-Control-Allow-Origin' '*';
add_header 'Access-Control-Allow-Headers' 'Content-Type';
and restart nginx server
sudo systemctl restart nginx

React Application within NGINX Docker cannot call API

I am currently running into a problem with my react application being served by an NGINX docker container. Here are the details:
My NGINX proxy to my API is working correctly, as I can call it using postman from an external machine. The problem is that I cannot call it from within my frontend. Whenever my frontend makes any request (POST, GET, OPTIONS, etc) into my API, NGINX makes it call 127.0.0.1:8000, which in turn makes the request fail because I am connecting from an external machine which isn't running anything on 127.0.0.1. Even when I set my react application to call the external IP that maps to the proxy, it ends up requesting 127.0.0.1 for some reason.
I don't know if this is an NGINX or a react problem, but I would appreciate any help. I have been trying to solve this issue for quite some time, and even made a previous post that helped me identify the problem correctly, but not the root cause of it.
Here are what my config files look like:
NGINX: (nginx-proxy.conf)
upstream api {
server backend:8000;
}
server {
listen 8080;
server_name 192.168.100.6;
add_header Access-Control-Allow-Origin *;
add_header Access-Control-Allow-Methods "GET, POST, OPTIONS, PUT,";
add_header Access-Control-Allow-Headers "DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range";
add_header Access-Control-Expose-Headers "Content-Length,Content-Range";
location /api/{
resolver 127.0.0.1;
proxy_set_header Host $host;
proxy_pass http://api;
}
# ignore cache frontend
location ~* (service-worker\.js)$ {
add_header 'Cache-Control' 'no-store, no-cache, must-revalidate, proxy-revalidate, max-age=0';
expires off;
proxy_no_cache 1;
}
location / {
root /var/www/react-frontend;
try_files $uri $uri/ /index.html;
}
}
Screenshot to firefox networking tab
The image in the link above shows all of my website resources being loaded from 192.168.100.6, but the moment I call my API, the request address changes to 127.0.0.1:8000, despite having the react application call 192.168.100.6/api/token (which does work on postman).
So after a lot of troubleshooting, I have found the problem to my issue.
What actually happened is that all of my codebase was correct, and the proxy was indeed working as intended, but for some reason
docker-compose build
or even:
docker-compose build --no-cache
was not updating my code changes (it was still sending requests to the ip I was using in development).
The answer that I arrived to was to do:
docker volume prune "my-nginx-volume"
and then rebuilding through docker-compose.

S3 hosted React app gets 405 Method Not Allowed

I have a react app hosted in S3. It is behind a Cloudfront CDN, which is reachable via a custom domain:
console.example.com -> CDN -> S3 hosted app
I also have a serverless application acting as my backend to handle API calls from the React app. This is also behind a Cloudfront CDN, which is reachable via a custom domain:
api.example.com -> CDN -> API Gateway
The front end is correctly configured to point to api.example.com. For the API Gateway, CORS is enabled.
When I navigate to the home page and attempt to login, console.example.com/login, I get the 405 Method Not Allowed error. Login, obviously using POST
I verified that the following curl is successful, when hitting the backend via API Gateway endpoint, Cloudfront Domain and the custom domain, effectively ruling out any API Gateway issue.
curl:
curl --header "Content-Type: application/json" \
--request POST \
--data '{"email":"name#test123.com","password":"xyz"}' \
https://api.example.com/login
In my research, I have found that S3 does not support POST. I have also found similar questions such as this and this, which were not helpful in my case unfortunately.
It is also worth noting that running my frontend and backend locally, works just fine, leaving me to think the S3 issue is my blocker here. But I'm not sure why. My POST endpoints are not trying to POST an object to the S3 bucket, they should be using the bundle.js file to hit the api endpoint.
So what am I missing? While I am not a frontend specialist, I assume others host react apps on S3 and can hit their api's just fine no?
I have intentionally not included the code as there would be a lot to digest there but am happy to include any that would be helpful, such as serverless.yml files or cloudformation templates etc. Any help would be awesome.
**** UPDATE - Added Cloudformation template for frontend Cloudfront CDN ****
Distribution:
Type: AWS::CloudFront::Distribution
Properties:
DistributionConfig:
Origins:
-
# Use the Website as the origin
DomainName: !GetAtt 'Website.DomainName'
Id: !Ref Website
CustomOriginConfig:
HTTPPort: 80
HTTPSPort: 443
OriginProtocolPolicy: https-only
Enabled: true
HttpVersion: http2
DefaultRootObject: index.html
CustomErrorResponses:
- ErrorCode: 404
ResponseCode: 200
ResponsePagePath: /index.html
- ErrorCode: 403
ResponseCode: 200
ResponsePagePath: /index.html
DefaultCacheBehavior:
AllowedMethods:
- DELETE
- GET
- HEAD
- OPTIONS
- PATCH
- POST
- PUT
DefaultTTL: 60
ForwardedValues:
QueryString: true
Cookies:
Forward: none
# The origin id defined above
TargetOriginId: !Ref Website
ViewerProtocolPolicy: "redirect-to-https" # we want to force https
# The certificate to use when using https
Aliases:
- console.example.com
ViewerCertificate:
AcmCertificateArn: arn:aws:acm:us-east-1:11111111:certificate/11111111-fa9b-4705-b9d2-11111111
MinimumProtocolVersion: TLSv1
SslSupportMethod: sni-only
I updated CustomErrorResponses to include 405 and I no longer get "405 Method Not Allowed" error:
CustomErrorResponses:
- ErrorCode: 405
ResponseCode: 200
ResponsePagePath: /index.html
- ErrorCode: 404
ResponseCode: 200
ResponsePagePath: /index.html
- ErrorCode: 403
ResponseCode: 200
ResponsePagePath: /index.html

Serving Spring REST API from Nginx, issues with CORS and CSRF

I've been struggling with this for 8 hours already and I realized that this is not something that I can come up with on my own.
I'm trying to serve a REST API implemented with Spring Boot using Nginx on a DigitalOcean Ubuntu. I have implemented a simple JWT authentication along with passing a CSRF token on every request. I have a frontend implemented with ReactJS. The whole system worked fine when I deployed it to Heroku, but back then backend served the frontend as well, so CORS was not much of an issue really. I'm supposed to do a deployment for a "client", who's webhotel does not support Spring Boot, therefore I need to serve frontend and backend in different locations.
Basically I'm running into multiple issues:
My frontend code does not find the CSRF token when the app is opened
If I (for testing purposes) don't even try to pass the CSRF token on logon, I run into CORS error: "Access to XMLHttpRequest at 'https://ip.of.my.droplet.on.digitalocean/api/auth/login' from origin 'https://url.of.my.frontend.on.heroku.com' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: Redirect is not allowed for a preflight request."
I've been Googling and trying different kinds of configurations for so long that I don't even have an idea if my fixes actually are causing more problems or just not doing anything.
My Spring Security configuration
http
.csrf()
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
.and()
.requiresChannel()
.requestMatchers(r -> r.getHeader("X-Forwarded-Proto") != null)
.requiresSecure()
.and()
.exceptionHandling()
.authenticationEntryPoint(unauthorizedHandler)
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.antMatchers(HttpMethod.OPTIONS, "/api/**").permitAll()
.antMatchers("/api/auth/**").permitAll()
.antMatchers("/static/**").permitAll()
.antMatchers("/**").permitAll()
.anyRequest().authenticated();
Spring CORS configuration
registry.addMapping("/**")
.allowedOrigins([URL to the frontend for testing purposes])
.allowedMethods("GET", "POST", "OPTIONS", "DELETE", "PUT", "PATCH")
.allowedHeaders("authorization, content-type, content-length, xsrf-token, credentials")
.allowCredentials(true)
.exposedHeaders("xsrf-token")
.maxAge(3600);
Nginx service configuration
server {
listen 443 ssl;
listen [::]:443 ssl;
include snippets/self-signed.conf;
include snippets/ssl-params.conf;
server_name [IP of my DigitalOcean droplet here];
location /api {
proxy_pass http://localhost:8080/api;
proxy_set_header Origin [URL to the frontend deployed on Heroku for testing purposes];
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Port $server_port;
proxy_pass_header X-XSRF-TOKEN;
}
}
server {
listen 80;
listen [::]:80;
server_name [IP of my DigitalOcean droplet here];
return 302 https://$server_name$request_uri;
}
Axios configuration on frontend
const CSRF_TOKEN = document.cookie.match(new RegExp('XSRF-TOKEN=([^;]+)'))[1];
instance = axios.create({
withCredentials: true,
baseURL: '[ip-address of my DigitalOcean droplet]/api',
headers: {
'X-XSRF-TOKEN': CSRF_TOKEN,
'Access-Control-Allow-Methods': 'PATCH, DELETE, POST, GET, OPTIONS, PUT'
}
});
I'm very beginner here, so I'm not even sure if any of this is right at all. I just know that it worked when it all was deployed to Heroku, so atleast some of it should be right.

Resources