Serving static content using Nginx after successful NodeJS authentication
I figured out the solution that involves below steps:
- Serve static login page using Nginx
- Forward the login credentials to the NodeJS server where authentication is handled and auth-token is stored in the response cookie (http-only). Send this response with the cookie set to the client
- Once the client receives authentication message, on success request the secure webapp page. This request will carry the auth-cookie along with itself.
- Again forward the request to the NodeJS server from Nginx, validate the token, set X-Accel-Redirect header with the path where to look for the secure file on NodeJS server.
While using X-Accel-Redirect, special care needs to be taken for Mime-Type of the response.
- CSS needs to be served with text/css
- images needs to be served with images/jpeg
- HTML content should be served with text/html
Nginx fails to load css files
I found an workaround on the web. I added to /etc/nginx/conf.d/default.conf the following:
location ~ \.css {
add_header Content-Type text/css;
}
location ~ \.js {
add_header Content-Type application/x-javascript;
}
The problem now is that a request to my css file isn't redirected well, as if root is not correctly set.
In error.log I see
2012/04/11 14:01:23 [error] 7260#0: *2 open() "/etc/nginx//html/style.css"
So as a second workaround I added the root to each defined location.
Now it works, but seems a little redundant. Isn't root inherited from / location ?
How to serve static files in Flask
In production, configure the HTTP server (Nginx, Apache, etc.) in front of your application to serve requests to /static
from the static folder. A dedicated web server is very good at serving static files efficiently, although you probably won't notice a difference compared to Flask at low volumes.
Flask automatically creates a /static/<path:filename>
route that will serve any filename
under the static
folder next to the Python module that defines your Flask app. Use url_for
to link to static files: url_for('static', filename='js/analytics.js')
You can also use send_from_directory
to serve files from a directory in your own route. This takes a base directory and a path, and ensures that the path is contained in the directory, which makes it safe to accept user-provided paths. This can be useful in cases where you want to check something before serving the file, such as if the logged in user has permission.
from flask import send_from_directory
@app.route('/reports/<path:path>')
def send_report(path):
return send_from_directory('reports', path)
Do not use send_file
or send_static_file
with a user-supplied path. This will expose you to directory traversal attacks. send_from_directory
was designed to safely handle user-supplied paths under a known directory, and will raise an error if the path attempts to escape the directory.
If you are generating a file in memory without writing it to the filesystem, you can pass a BytesIO
object to send_file
to serve it like a file. You'll need to pass other arguments to send_file
in this case since it can't infer things like the file name or content type.
Ingress Nginx - how to serve assets to application
TL;DR
To diagnose the reason why you get error 404 you can check in nginx-ingress
controller pod logs. You can do it with below command:
kubectl logs -n ingress-nginx INGRESS_NGINX_CONTROLLER_POD_NAME
You should get output similar to this (depending on your use case):
CLIENT_IP - - [12/May/2020:11:06:56 +0000] "GET / HTTP/1.1" 200 238 "-" "REDACTED" 430 0.003 [default-ubuntu-service-ubuntu-port] [] 10.48.0.13:8080 276 0.003 200
CLIENT_IP - - [12/May/2020:11:06:56 +0000] "GET /assets/styles/style.css HTTP/1.1" 200 22 "http://SERVER_IP/" "REDACTED" 348 0.002 [default-ubuntu-service-ubuntu-port] [] 10.48.0.13:8080 22 0.002 200
With above logs you can check if the requests are handled properly by nginx-ingress
controller and where they are sent.
Also you can check the Kubernetes.github.io: ingress-nginx: Ingress-path-matching. It's a document describing how Ingress
matches paths with regular expressions.
You can experiment with Ingress
, by following below example:
- Deploy
nginx-ingress
controller - Create a
pod
and aservice
- Run example application
- Create an
Ingress
resource - Test
- Rewrite example
Deploy nginx-ingress
controller
You can deploy your nginx-ingress
controller by following official documentation:
Kubernetes.github.io: Ingress-nginx
Create a pod
and a service
Below is an example definition of a pod and a service attached to it which will be used for testing purposes:
apiVersion: apps/v1
kind: Deployment
metadata:
name: ubuntu-deployment
spec:
selector:
matchLabels:
app: ubuntu
replicas: 1
template:
metadata:
labels:
app: ubuntu
spec:
containers:
- name: ubuntu
image: ubuntu
command:
- sleep
- "infinity"
---
apiVersion: v1
kind: Service
metadata:
name: ubuntu-service
spec:
selector:
app: ubuntu
ports:
- name: ubuntu-port
port: 8080
targetPort: 8080
nodePort: 30080
type: NodePort
Example page
I created a basic index.html
with one css
to simulate the request process. You need to create this files inside of a pod (manually or copy them to pod).
The file tree looks like this:
- index.html
- assets/styles/style.css
index.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="assets/styles/style.css">
<title>Document</title>
</head>
<body>
<h1>Hi</h1>
</body>
Please take a specific look on a line:
<link rel="stylesheet" href="assets/styles/style.css">
style.css:
h1 {
color: red;
}
You can run above page with python
:
$ apt update && apt install -y python3
$ python3 -m http.server 8080
where theindex.html
andassets
folder is stored.
Create an Ingress
resource
Below is an example Ingress
resource configured to use nginx-ingress
controller:
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: nginx-ingress-example
annotations:
kubernetes.io/ingress.class: nginx
spec:
rules:
- host:
http:
paths:
- path: /
backend:
serviceName: ubuntu-service
servicePort: ubuntu-port
After applying above resource you can start to test.
Test
You can go to your browser and enter the external IP address associated with your Ingress
resource.
As I said above you can check the logs of nginx-ingress
controller pod to check how your controller is handling request.
If you run command mentioned earlier python3 -m http.server 8080
you will get logs too:
$ python3 -m http.server 8080
Serving HTTP on 0.0.0.0 port 8080 (http://0.0.0.0:8080/) ...
10.48.0.16 - - [12/May/2020 11:06:56] "GET / HTTP/1.1" 200 -
10.48.0.16 - - [12/May/2020 11:06:56] "GET /assets/styles/style.css HTTP/1.1" 200 -
Rewrite example
I've edited the Ingress
resource to show you an example of a path rewrite:
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: nginx-ingress-example
annotations:
kubernetes.io/ingress.class: nginx
nginx.ingress.kubernetes.io/rewrite-target: /$1
spec:
rules:
- host:
http:
paths:
- path: /product/(.*)
backend:
serviceName: ubuntu-service
servicePort: ubuntu-port
Changes were made to lines:
nginx.ingress.kubernetes.io/rewrite-target: /$1
and:
- path: /product/(.*)
Steps:
- The browser sent:
/product/
- Controller got
/product/
and had it rewritten to/
- Pod got
/
from a controller.
Logs from thenginx-ingress
controller:
CLIENT_IP - - [12/May/2020:11:33:23 +0000] "GET /product/ HTTP/1.1" 200 228 "-" "REDACTED" 438 0.002 [default-ubuntu-service-ubuntu-port] [] 10.48.0.13:8080 276 0.001 200 fb0d95e7253335fc82cc84f70348683a
CLIENT_IP - - [12/May/2020:11:33:23 +0000] "GET /product/assets/styles/style.css HTTP/1.1" 200 22 "http://SERVER_IP/product/" "REDACTED" 364 0.002 [default-ubuntu-service-ubuntu-port] [] 10.48.0.13:8080 22 0.002 200
Logs from the pod:
10.48.0.16 - - [12/May/2020 11:33:23] "GET / HTTP/1.1" 200 -
10.48.0.16 - - [12/May/2020 11:33:23] "GET /assets/styles/style.css HTTP/1.1" 200 -
Please let me know if you have any questions in that.
Nginx reverse-proxy route returns its index.html for static asset requests (ex. .css files)
For anyone running into the same issue later on. This was the fix:
nginx-server (root) nginx.config
http {
server {
...
location /dashboard/ {
proxy_pass http://dashboard:80/;
...
}
}
}
You have to add the /
at the end for your proxy_pass
value ( http://dashboard:80/ ). Then it worked :)
Using express.js to serve html file along with scripts, css, and images
You should do the following things:
- Serve your static files
- Create an API server that will listen for the requests coming from your frontend app
1. Serve your static files
To serve static files with Express, read this link.
You'll basically add it to your express app:
app.use( express.static( __dirname + '/client' ));
Where '/client'
will be the name of the folder with your frontend app files.
2. Create an API server
You can see how to create an API server here.
For the entry point of your application, you should send/render a file.
This could be accomplished with the following code:
app
.get( '/', function( req, res ) {
res.sendFile( path.join( __dirname, 'client', 'index.html' ));
});
This will send a static file every time that a user request a file at the root path of your application.
You can use the asterisk *
(wildcard) instead of /
too. That symbol meaning that for whatever route requested, you will respond with the same file/action.
More about the responses here.
Sum up
Those are the things that you should seek to build your app.
You can see a simple app with those things implemented here.
Nginx: how to let rewrite rules ignore files or folders
Put rewrite
into one location
and use other location
s for assests/dynamic urls/etc.
server {
listen 80 default;
server_name my.domain.com;
root /path/to/app/root;
location / {
rewrite ^ /index.html break;
}
location /assets/ {
# Do nothing. nginx will serve files as usual.
}
}
Related Topics
How to Combine Cursor: Not-Allowed and Pointer-Events: None;
How to Center Variable Width Divs in CSS
CSS - Circle with Margin on Border
CSS Transform to Skew the Shape to a Trapezium
Emojis Won't Scale Beyond 16Px Font-Size on iOS 7
Border-Radius CSS Property Curve Outside
Override Jquery UI Datepicker Div Visible Strangely on First Page Load
What's the :Any-Link Pseudo-Class For
Responsive Images Positioned Over Image
How to Use Bootstrap 4 Flexbox to Fill Available Content
Remove New Firefox Autofill Color
Using '-Webkit-Overflow-Scrolling: Touch' Hides Content While Scrolling/Dragging
Css3 Animation Is Not Working in Ie11 But Works in Other Browsers
Bootstrap 4 Flex Grid System Only