Integration and securization under subpath / subfolder

Just to let you know I’ve manage on how to install n8n under a subpath integrating securely under my existing app.
Let’s have an existing app such as:

frontend.mydomain.com: your frontend app
api.mydomain.com: your api
webhooks.mydomain.com: I have other webhooks for other services

Your case might be simpler, having all in one or just frontend an api, just adapt to your needs.

Authentication on API is done by an Authentication header token.

In this scenario I’d like to run n8n under frontend.mydomain.com/workflows with the same auth token, but just the UI, I want to get Access to webhooks from webhooks.mydomain.com/workflows

How?
I use nginx on api, frontend and webhooks.
Let’s suppose you already have n8n up and running.

-in frontend.mydomain.com

server {
        ...bla bla bla....
        #block access to webhooks from frontend
        location ^~ /workflows/webhook {
            return 404;
        }
       #proxy pass /workflows to n8n deployment
        location /workflows {
          sub_filter_once off;
          sub_filter_types *;
          sub_filter 'VUE_APP_URL_BASE_API:"/"' 'VUE_APP_URL_BASE_API:"/workflows/"';
          sub_filter 'base:"/"' 'base:"/workflows/"';
          sub_filter "src=/" "src=/workflows/";
          sub_filter "href=/"    "href=/workflows/";
          sub_filter "BASE_URL:\"/\"" "BASE_URL:\"/workflows/\"";
          rewrite ^/workflows/(.*)$ /$1 break;
          proxy_set_header Accept-Encoding "";
          proxy_pass http://n8n;
          proxy_set_header Connection '';
          proxy_http_version 1.1;
          chunked_transfer_encoding off;
          proxy_buffering off;
          proxy_cache off;
        }
}

So, web have two ways rewriting. With sub_filters we replace in all responses (in the code, html, js…) the different patterns we have to change to allow the VUE app to work. So, instead of the hardcoded /js/…. links we get /workflows/js/…
Then, inside the appXXX.js we change the hardcoded URLs from / to /workflows too.
If everything goes right, your installation is working under /workflows/.

-Under webhooks.mydomain.com nginx:

server{
        ...bla bla bla...
        location /workflows/webhook {
          rewrite ^/workflows/(.*)$ /$1 break;
          proxy_set_header Accept-Encoding "";
          proxy_pass http://n8n;
          proxy_set_header Connection '';
          proxy_http_version 1.1;
          chunked_transfer_encoding off;
          proxy_buffering off;
          proxy_cache off;
        }
        location ^~ /workflows {
            return 404;
        }
}

We proxy pass everything on webhooks to n8n, removing /workflows/ and deny access to ui.

This way we should have everything working without securing UI. How to do that?
Nginx again :slight_smile:

-Add to your frontend nginx:

server{
        ...bla bla bla....
        location = /auth {
            internal;
            proxy_pass http://api/v1/users/auth;
            proxy_pass_request_body     off;
            proxy_set_header Content-Length "";
            proxy_set_header X-Original-URI $request_uri;
            proxy_set_header Host $http_host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto "https";
            if ($http_cookie ~* "mycookiesession=([^;]+)(?:;|$)") {
                set $token "Bearer $1";
            }
            proxy_set_header Authorization $token;
        }
        location /workflows {
          #add this
          auth_request /auth;
          #the same as before
        }
}

This is how it works:
-When a user logs into my frontend app the browser gets an auth token and generates a cookie with this token.
-When the user goes to frontend.mydomain.com/workflows the cookie goes too.
-With auth_request /auth; nginx authenticates each request to /auth internally, and this proxies the request to my api, transforming the cookie into the Authorization header and authenticates against http:///api/v1/users/auth. If it returns http200, user is allowed, otherwhise, rejected. You can improve the rejection with:

error_page 401 = @error401;
location @error401 {
    return 302 https://frontend.mydomain.com/login;
}

This way any unauthenticated acces is redirected to login.

Full nginx config for frontend.

server {
    listen       80;
    server_name  localhost;

    if ($http_x_forwarded_proto = "http") {
        return 301 https://$host$request_uri;
    }        
    
    error_page 401 = @error401;
    location @error401 {
        return 302 https://frontend.mydomain.com/login;
    }
    location ^~ /workflows/webhook {
        return 404;
    }
    location = /auth {
        internal;

        proxy_pass http://api/v1/users/auth;
        proxy_pass_request_body     off;
        proxy_set_header Content-Length "";
        proxy_set_header X-Original-URI $request_uri;
        proxy_set_header Host $http_host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto "https";

        if ($http_cookie ~* "mycookiesession=([^;]+)(?:;|$)") {
            set $token "Bearer $1";
        }
        proxy_set_header Authorization $token;
    }
    location /workflows {
      auth_request /auth;
      sub_filter_once off;
      sub_filter_types *;
      sub_filter 'VUE_APP_URL_BASE_API:"/"' 'VUE_APP_URL_BASE_API:"/workflows/"';
      sub_filter 'base:"/"' 'base:"/workflows/"';
      sub_filter "src=/" "src=/workflows/";
      sub_filter "href=/"    "href=/workflows/";
      sub_filter "BASE_URL:\"/\"" "BASE_URL:\"/workflows/\"";

      rewrite ^/workflows/(.*)$ /$1 break;

      proxy_set_header Accept-Encoding "";
      proxy_pass http://n8n;
      proxy_set_header Connection '';
      proxy_http_version 1.1;
      chunked_transfer_encoding off;
      proxy_buffering off;
      proxy_cache off;
    }
    location / {
        root   /usr/share/nginx/html;
        index  index.html index.htm;
        try_files $uri $uri/ /index.html;
    }
    location ~ /\.ht {
        deny  all;
    }
}

Hope you find it usefull :slight_smile:

3 Likes

Welcome to the community @osus and thanks a lot for sharing this!

It is now properly possible to run n8n in a subfolder with [email protected]

Here an example:

1 Like

Hi, Jesus, @osus. I hope you are doing well, I am trying to do a similar setup using Docker Compose, the cotainer name for the n8n service is “etl”, I also specified the env variables as follow.

N8N_HOST=etl
N8N_PORT=5678
N8N_PROTOCOL=http
WEBHOOK_TUNNEL_URL=http://etl:5678/
VUE_APP_URL_BASE_API=http://etl:5678/

The nginx.conf is like you suggest.

    location /workflows {
        sub_filter_once off;
        sub_filter_types *;
        #sub_filter 'VUE_APP_URL_BASE_API:"/"' 'VUE_APP_URL_BASE_API:"/workflows/"';
        sub_filter 'http://etl:5678/:"/"' 'http://etl:5678/:"/workflows/"';            
        sub_filter 'base:"/"' 'base:"/workflows/"';
        sub_filter "src=/" "src=/workflows/";
        sub_filter "href=/"    "href=/workflows/";
        sub_filter "http://etl:5678/:\"/\"" "http://etl:5678/:\"/workflows/\"";

        rewrite ^/workflows/(.*)$ /$1 break;

        proxy_set_header Accept-Encoding "";
        proxy_pass http://etl:5678;
        proxy_set_header Connection '';
        proxy_http_version 1.1;
        chunked_transfer_encoding off;
        proxy_buffering off;
        proxy_cache off;
    }

however when trying to reach http://localhost/workflows, despite of the fact that the index and the assets (css and js) load, the app does not work properly, do you have any ideas about what could be the issue here? thanks for your time.

Welcome to the community @Tsjuan!

No sorry sadly no idea. Took me quite a while to get that setup I documented in my previous post up and running. It worked for me if it got used to 100% like that. So you should make sure that you use that setup. If you need anything else you have probably more luck in an nginx forum as my knowledge in this area is more than limited. Sorry!

@jan, thanks for your response, if I get my setup to work, I will be back to contribute a bit with the documentation, schönes Wochenende.

Thanks a lot. That would be very appreciated. I am sure it would be helpful for many people.

Auch schönes Wochenende!

Hi guys,

I’ve been checking my last config because I remember an update last summer broke the setup and I guess it was just that problem, some things were loading but not VUE app. These are the changes I see:

Env vars:
- name: BASE_URL
value: https://frontend.mydomain.com/workflows/
- name: N8N_PATH1
value: /workflows/
- name: SUBFOLDER1
value: workflows

Nginx config:
Add these:

  sub_filter "BASE_URL:\"/\"" "BASE_URL:\"/workflows/\"";
  sub_filter "window.BASE_PATH = \"/\"" "window.BASE_PATH = \"/workflows/\"";

Now VUE app should be loading.
My setup is runing N8N 0.94.1 with this config, new ones might break :slight_smile:

Jesus

1 Like

@Tsjuan,

Looking at your nginx config I see this

sub_filter 'http://etl:5678/:"/"' 'http://etl:5678/:"/workflows/"';  
sub_filter "http://etl:5678/:\"/\"" "http://etl:5678/:\"/workflows/\"";

These filters are some king weird (I might be wrong)

I’t suposed to replace any string like: http://etl:5678/:"/"
with: http://etl:5678/:"/workflows/"
In any of the loaded files…

Does it have sense for you?
I can’t see anything similar in my original post…

Think about the filters as replacements in your HTML (or css or js) files.
In my original post, something like
sub_filter "href=/" "href=/workflows/";
will replace href=/ with href=/workflows/ in N8N html.

Yours will look for something like

<div class="myclass" http://etl:5678/:"/" ></div>

See the problem?

Regards,
Jesus

1 Like