Nginx reverse proxy with react service - cannot load styles - reactjs

I have docker swarm app with multiple services and have problems with one service which is a react app. My config looks like this:
location /web/ {
include /etc/nginx/proxy-options/proxy.conf;
set $webapp webapp;
proxy_cache_bypass $http_pragma;
proxy_pass http://$webapp$uri$is_args$args;
sub_filter 'action="/' 'action="/web/';
sub_filter 'href="/' 'href="/web/';
sub_filter 'src="/' 'src="/web/';
sub_filter_once off;
}
location /app-info/ {
include /etc/nginx/proxy-options/proxy.conf;
proxy_ssl_session_reuse off;
proxy_redirect off;
set $webapp webapp;
proxy_pass http://$webapp$uri/$is_args$args;
sub_filter 'action="/' 'action="/web/';
sub_filter 'href="/' 'href="/web/';
sub_filter 'src="/' 'src="/web/';
sub_filter_once off;
}
Webapp is the react application hosted in docker also served with nginx. One of it's pages is webapp/app-info.
When setup through reverse proxy when I try to access anything on webapp I just get white screen and in the console I can see these errors. At this moment I'm out of ideas what I'm doing wrong.
Refused to apply style from 'https://example.com/web/static/css/main.23d98db9.chunk.css' because its MIME type ('text/html') is not a supported stylesheet MIME type, and strict MIME checking is enabled.
Now what I'm trying to achieve (beside just making /web work) is making only /app-info/ accessible from outside. I would like to (temporary) block any access to webapp, beside the requests to /app-info. Is that possible?
Edit: Forgot to mention - everything works perfectly fine with
location / {
include /etc/nginx/proxy-options/proxy.conf;
set $webapp webapp;
proxy_pass http://$webapp$uri$is_args$args;
proxy_redirect http://$webapp/ $scheme://$http_host/;
}

Related

proxy_pass from wildcard subdomain via NGINX

I'm trying to show users visiting a wildcard subdomain a subfolder:
abc.example.com -> example.com/xyz
This NGINX server block code is working:
server {
# server name with regexp
server_name ~^(?<sub>[^.]+)\.example\.com$;
# this server catches all requests to xxxx.example.com
# and put "xxxx" to $sub variable
location / {
# finally we want to request different URI from remote server
proxy_pass http://localhost:8000;
# proxy_redirect will rewrite Location: header from backend
# or you can leave proxy_redirect off;
proxy_redirect http://localhost:8000 http://$sub.localhost:8000;
}
[certbot code]
}
(found in question 5249883).
But when replacing proxy_pass value "https://localhost:8000" with "https:localhost:8000/xyz", I get these errors and a blank page:
Uncaught SyntaxError: Unexpected token '<'
in both socket.io.js and commons.js.
The app I'm running on example.com is built with React/Gatsby. example.com/demo is working.
EDIT: I put the wrong error messages, those errors appeared when I tried something different.
The problem was (as I understand it now), that Gatsby hosts scripts at example.com/[script-address] and using NGINX proxy_pass, the script address is also changed to example.com/[subfolder]/[script-address].
The solution to this is to set the "path-prefix" value in gatsby.config as explained here: Gatsby documentation.
With doing that, I set a prefix for my complete application, which is not really what I want to do, as the main application is still hosted on example.com, I only want the subdomains to get passed to some subpages. (The subdomains are user created and served dynamically by the main application).
Surprisingly, both (main application and subdomains) work after changing the path prefix.
This seems only to work for the production build (you have to pass a flag when building), so I'm currently still not sure what to do while developing.
If you have an idea how I could solve that better, please message me :)
Thanks to Ivan and Richard for putting me on the right track!
EDIT: Asset prefixed would be the better way: https://www.gatsbyjs.org/docs/asset-prefix/
It's still ugly and I think there's a way to solve this via NGINX.
I still can't use the development build this way.
EDIT 2:
After I've been messing with this for 3 days now, I've again tried to find a similar question & got lucky: https://serverfault.com/questions/840654/nginx-map-subdomain-to-a-subdirectory-on-proxied-server
I've changed my code to:
location / {
proxy_pass http://localhost:8000/xyz$uri/;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $http_host;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_redirect off;
}
and it finally works :)

Can a ReactJS app with a router be hosted on S3 and fronted by an nginx proxy?

I may be twisting things about horribly, but... I was given a ReactJS application that has to be served out to multiple sub-domains, so
a.foo.bar
b.foo.bar
c.foo.bar
...
Each of these should point to a different instance of the application, but I don't want to run npm start for each one - that would be a crazy amount of server resources.
So I went to host these on S3. I have a bucket foo.bar and then directories under that for a b c... and set that bucket up to serve static web sites. So far so good - if I go to https://s3.amazonaws.com/foo.bar/a/ I will get the index page. However most things tend to break from there as there are non-relative links to things like /css/ or /somepath - those break because they aren't smart enough to realize they're being served from /foo.bar/a/. Plus we want a domain slapped on this anyway.
So now I need to map a.foo.bar -> https://s3.amazonaws.com/foo.bar/a/. We aren't hosting our domain with AWS, so I'm not sure if it's possible to front this with CloudFront or similar. Open to a solution along those lines, but I couldn't find it.
Instead, I stood up a simple nginx proxy. I also added in forcing to https and some other things while I had the proxy, something of the form:
server {
listen 443;
server_name foo.bar;
ssl on;
ssl_certificate /etc/pki/tls/certs/server.crt;
ssl_certificate_key /etc/pki/tls/certs/server.key;
ssl_session_timeout 5m;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_prefer_server_ciphers on;
# Redirect (*).foo.bar to (s3bucket)/(*)
location / {
index index.html index.htm;
set $legit "0";
set $index "";
# First off, we lose the index document functionality of S3 when we
# proxy requests. So we need to add that back on to our rewrites if
# needed. This is a little dangerous, probably should find a better
# way if one exists.
if ($uri ~* "\.foo\.bar$") {
set $index "/index.html";
}
if ($uri ~* "\/$") {
set $index "index.html";
}
# If we're making a request to foo.bar (not a sub-host),
# make the request directly to "production"
if ($host ~* "^foo\.bar") {
set $legit "1";
rewrite /(.*) /foo.bar/production/$1$index break;
}
# Otherwise, take the sub-host from the request and use that for the
# redirect path
if ($host ~* "^(.*?)\.foo\.bar") {
set $legit "1";
set $subhost $1;
rewrite /(.*) /foo.bar/$subhost/$1$index break;
}
# Anything else, give them foo.bar
if ($legit = "0") {
return 302 https://foo.bar;
}
# Peform the actual proxy forward
proxy_pass https://s3.amazonaws.com/;
proxy_set_header Host s3.amazonaws.com;
proxy_set_header Referer https://s3.amazonaws.com;
proxy_set_header User-Agent $http_user_agent;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header Accept-Encoding "";
proxy_set_header Accept-Language $http_accept_language;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
sub_filter google.com example.com;
sub_filter_once off;
}
}
This works - I go to a.foo.bar, and I get the index page I expect, and clicking around works. However, part of the application also does an OAuth style login, and expects the browser to be redirected back to the page at /reentry?token=foo... The problem is that path only exists as a route in the React app, and that app isn't loaded by a static web server like S3, so you just get a 404 (or 403 because I don't have an error page defined or forwarded yet).
So.... All that for the question...
Can I serve a ReactJS application from a dumb/static server like S3, and have it understand callbacks to it's routes? Keep in mind that the index/error directives in S3 seem to be discarded when fronted with a proxy the way I have above.
OK, there was a lot in my original question, but the core of it really came down to: as a non-UI person, how do I make an OAuth workflow work with a React app? The callback URL in this case is a route, which doesn't exist if you unload the index.html page. If you're going directly against S3, this is solved by directing all errors to index.html, which reloads the routes and the callback works.
When fronted by nginx however, we lose this error->index.html routing. Fortunately, it's a pretty simple thing to add back:
location / {
proxy_intercept_errors on;
error_page 400 403 404 500 =200 /index.html;
Probably don't need all of those status codes - for S3, the big thing is the 403. When you request a page that doesn't exist, it will treat it as though you're trying to browse the bucket, and give you back a 403 forbidden rather than a 404 not found or something like that. So in this case a response from S3 that results in a 403 will get redirected to /index.html, which will recall the routes loaded there and the callback to /callback?token=... will work.
You can use Route53 to buy domain names and then point them toward your S3 bucket and you can do this with as many domains as you like.
You don't strictly speaking need to touch CloudFront but it's recommended as it is a CDN solution which is better for the user experience.
When deploying applications to S3, all you need to keep in mind is that the code you deploy to it is going to run 100% on your user's browser. So no server stuff.

How To Avoid Mixed Content with Docker Apps

I am running a Django based web application inside a set of Docker containers and I'm trying to include both a REST API (using django-REST-framework) as well as the ReactJS app that consumes it. All my other apps are served over HTTPS but I am running into Mixed Active Content when it comes to the React app hitting the REST API inside the Docker network. The React App is being hosted within my NGINX container and served up as a static site.
Here's the relevant config for my Nginx container:
# SSL Website
server {
listen 443 http2 ssl;
listen [::]:443 http2 ssl;
server_name *.domain.com;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_ciphers EECDH+CHACHA20:EECDH+AES128:RSA+AES128:EECDH+AES256:RSA+AES256:EECDH+3DES:RSA+3DES:!MD5;
ssl_prefer_server_ciphers on;
ssl_certificate /etc/nginx/ssl/my_cert.crt;
ssl_certificate_key /etc/nginx/ssl/my_key.key;
ssl_stapling on;
ssl_stapling_verify on;
access_log /home/logs/error.log;
error_log /home/logs/access.log;
upstream django {
server web:9000;
}
location /
{
include uwsgi_params;
# Proxy settings
proxy_pass http://django;
proxy_http_version 1.1;
proxy_buffering off;
proxy_set_header Host $http_host;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
# REACT APPLICATION
location /faqs {
autoindex on;
sendfile on;
alias /usr/share/nginx/html/faqs;
}
}
The during development the React app was hitting my REST API from outside the network so resources calls used https like so:
axios.get(https://myapp.domain.com/api/)
and everything went relatively smoothly, barring the occasional CORS error.
However, now that both the React and the API are running inside the Docker network NGINX is not involved in the communication between containers and the routes are like so:
axios.get(http://web:9000/api)
This gives me the aggravating Mixed Active Content Error.
I've seen multiple questions similar to this but most are either not using Docker containers or use some NGINX directives I've already got in my config file. Given the popularity of Docker for these kind of loosely coupled applications I would imagine solutions abound for this kind of problem. Sadly I have not managed to come across any and as such, any suggestions would be greatly appreciated.
Since your application includes both an API and a web client from the same end point, you have a "gateway" in nginx that routes all requests to either end point. So far, common practice (although you are missing a load balancer, but that's a different discussion)
All requests to your API should be to https. You should also be serving your static site over https with the same certificate from the same domain. If this isn't the case - there is your problem.
Furthermore, all routes and urls inside your react application should be relative. That means that the react app doesn't need to know what your domain is. Neither should your API ideally although that is sometimes harder to do.
your axios call, given that the react app is served from the same domain over https, should be
axios.get(/api)

Nginx conf for prerender + reverse proxy to django + serving angular in html5 mode

The mouthful of a title says it all:
We've got an Angular frontend with a Django backend providing a REST API that is exposed independently at endpoints example.com/api/v1/*
The Angular app runs in HTML5 mode, and we want hard-links to example.com/foo/bar to bring users into the app at the foo.bar state as if it were a static page rather than an app state (where foo is anything but api).
We're running behind nginx,and our basic strategy in the conf was to define locations at ^~ /scripts, /images etc. for serving static content directly, as well as a ^~ /api/* location that gets routed to django. Below that, we have a location ~ ^/.+$ that matches any path not matched by any of the above and "sends it to Angular" - i.e. serves our index page to it and appends the path to the base url, allowing our angular router to handle it from there.
This is our conf in full:
upstream django {
server 127.0.0.1:8000 fail_timeout=0;
}
server {
listen 80;
server_name example.com;
return 301 https://example.com$request_uri;
}
server {
listen 443;
server_name example.com;
client_max_body_size 10M;
ssl on;
ssl_certificate /etc/ssl/thawte/example_com.crt;
ssl_certificate_key /etc/ssl/thawte/example_com.key;
ssl_verify_depth 2;
gzip on;
gzip_types text/plain text/html application/javascript application/json;
gzip_proxied any;
index index.html;
location ^~ /index.html {
gzip_static on;
root /www/dist;
}
location ^~ /images/ {
expires max;
root /www/dist;
}
location ^~ /scripts/ {
expires max;
gzip_static on;
root /www/dist;
}
location ^~ /favicon.ico {
expires max;
root /www/dist;
}
location ^~ /api {
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $http_host;
proxy_set_header X-Forwarded-Proto https;
proxy_redirect off;
proxy_pass http://django;
}
//Send anything else to angular
location ~ ^/.+$ {
rewrite .* /index.html last;
}
}
This has worked perfectly for us, but we now need to set it up to work with prerender.io. We've tried doing this several ways, making modifications on the official prerender nginx example, but none have worked - crawlers are getting the same code users are rather than cached pages.
How can we get this working?
(note: this is new territory for everyone involved here, so if the best way to handle this involves making different choices a few steps back, please suggest them)
So it turns out the config posted above was working the whole time.
I realized this when it finally occurred to me to try putting https://example.com/anything through the crawler debugger (instead of https://example.com, which is all I had been testing previously), and it worked - the crawler was served the cached page as expected.
This was simply because the greedy quantifier in:
location ~ ^/.+$ {
did not match the empty string. With an additional
location = / {
try_files $uri #prerender;
}
, my conf is working as expected.
Hopefully the handprint on my forehead d only been putting https://example.com through the crawler debugger - which was not working.
On the upside, I'm thinking I can turn this handprint on my forehead into a nice Halloween costume next weekend....
Still not sure I've gone about this the best way, and welcome alternative suggestions.

Serve single file Angular app from S3 with nginx

I have an Angular app that consists of a single index.html file. The only others files are main.css and some image assets. I've already rediscovered there is no way to use S3 web hosting to serve it so I'm trying to set up nginx as a proxy. I have done this before but it was years ago and it wasn't with an Angular app and HTML5 push state. Here is the current nginx config server block I have.
server {
server_name foo.com;
set $s3_bucket 'foo.com.s3.amazonaws.com';
proxy_http_version 1.1;
proxy_set_header Host $s3_bucket;
proxy_set_header Authorization '';
proxy_hide_header x-amz-id-2;
proxy_hide_header x-amz-request-id;
proxy_hide_header Set-Cookie;
proxy_ignore_headers "Set-Cookie";
proxy_buffering off;
proxy_intercept_errors on;
resolver 172.16.0.23 valid=300s;
resolver_timeout 10s;
location ~* ^/(assets|styles)/(.*) {
set $url_full '$1/$2';
proxy_pass http://$s3_bucket/live/$url_full;
}
location / {
rewrite ^ /live/index.html break;
proxy_pass http://$s3_bucket;
}
}
I don't assume anything with this config. It could all be completely wrong.
It does "work". I can go to foo.com and the site serves and I can navigate and it all work wonders. But it won't load any URL that is not /. All other redirect to / and that is a problem.
What am I doing wrong? All help appreciated.

Resources