ServerAuth

Server Authentication will allow you to secure any/all location blocks at your web server/proxy level, only allowing authenticated Organizr users or administrators access.

Note that this method will only provide an Authorization layer but will not actually pass any Authentication information / credentials to the underlying back-end services, since this would require some cooperation from these services to understand it. So these services will still be accessed as a guest / unauthenticated user, but this is often good enough for many services.

Methodology

Method 1: Using the Organizr authorization API

To utilize the block add auth_request /auth-$; within your location block, where $=Organizr group_id.

For this to work, a URL rewrite directive needs to be added so that the static /auth-$ locations can be understood by Organizr's authentication API, i.e. use the /api/?v1/auth&group=$1 format.

Organizr Groups

Default group-numbers and its numbers

0=Admin
1=Co-Admin
2=Super User
3=Power User
4=User

Hardcoded useful "groups"

998=Logged In Users
999=Guests

Setting up the rewriting directive

Native NGINX install
location ~ /auth-(.*) {
	internal;
	rewrite ^/auth-(.*) /api/?v1/auth&group=$1;
}
Docker

If you use a docker container, the location block needs to be altered, to look something like this:

location ~ /auth-(.*) {
        internal;
        proxy_pass http://[docker/hostIP]:[port]/api/?v1/auth&group=$1;
        proxy_set_header Content-Length "";
}

Note: If you are using a reverse proxy, this should be added on the reverse proxy layer

Subdomain

For subdomains, you need to call back to the domain organizr is on, this can be done differently depending on your installation method.

Native, with local DNS setup (This can also apply for containers): http://web.home.lab/api/?v1/auth&group=$1

Docker, using ip and port (This is assuming the container is running in bridge): http://192.168.9.5:8080/api/?v1/auth&group=$1

location ~ ^/auth-(.*) {
    ## Has to be local ip or local DNS name
    proxy_pass https://web.home.lab/api/?v1/auth&group=$1;
    proxy_pass_request_body off;
    proxy_set_header Content-Length "";
    
}

How to include the authorization block in a reverse proxy

All you need to do is include one line per reverse proxy block as the very first line: auth_request /auth-0; Where /auth-0 is the access level for admin.

Here is a sample of a reverse proxy with admin access:

location /[SERVICE] {
    auth_request /auth-0;
    proxy_pass http://[IP]:[PORT];
    add_header X-Frame-Options "SAMEORIGIN";
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
Excluding a location from authentication

Most of our examples already has this, but here is an explanation, using one of our examples(with headers removed).

location /sonarr {
    proxy_pass http://127.0.0.1:8989/sonarr;
    auth_request /auth-0;
    location /sonarr/api { # We know that sonarr's api-endpoint is /api, so we are gonna open that up.
        auth_request off; # The line that actually opens it up
        proxy_pass http://127.0.0.1:8989/sonarr/api; # We need to tell nginx where to send the request
        }
}
Caddy

Using Caddy and the reauth plugin you can accomplish the same using the following block:

  reauth {
    path /sonarr   # location that requires reauth
    # path /glances   # other directories can be listed
    #
    # if someone is not authorized for a page, send them here instead
    failure redirect target=https://<your_domain>/

    upstream url=https://<your_domain>/api/?v1/auth&group=<group_id>,cookies=true
  }
Træfik v1

You can use Traefik's auth-forward feature to do the same.

Example docker-compose.yml block for Organizr:

services:
  organizr:
    image: organizrtools/organizr-v2
    environment:
      - TZ
      - PUID=${USER_UID}
      - PGID=${USER_GID}
    labels:
      - "traefik.enable=true"
      - "traefik.organizr.frontend.rule=Host: www.your_domain.com"
      - "traefik.organizr.port=80"
    depends_on:
      - traefik

Example service that depends on user being authenticated to Organizr:

services:
  nzbget:
    image: linuxserver/nzbget
    environment:
      - TZ
      - PUID=${USER_UID}
      - PGID=${USER_GID}
    labels:
      - "traefik.enable=true"
      - "traefik.frontend.rule=Host: nzbget.your_domain.com"
      - "traefik.frontend.auth.forward.address=http://organizr/api/?v1/auth&group=1"
      - "traefik.port=6789"
    depends_on:
      - traefik
      - organizr
Træfik v2

Træfik changed how the tags work in v2.

Example docker-compose.yml block for Organizr:

services:
  organizr:
    image: organizrtools/organizr-v2
    environment:
      - TZ
      - PUID=${USER_UID}
      - PGID=${USER_GID}
    labels:
      - "traefik.http.routers.organizr.rule=Host(`www.your_domain.com`)"
      - "traefik.http.services.organizr.loadbalancer.server.port=80"
      - "traefik.http.services.organizr.loadbalancer.server.scheme=http"
    depends_on:
      - traefik

Example service that depends on user being authenticated to Organizr:

services:
  nzbget:
    image: linuxserver/nzbget
    environment:
      - TZ
      - PUID=${USER_UID}
      - PGID=${USER_GID}
    labels:
      - "traefik.http.routers.nzbget.service"
      - "traefik.http.routers.nzbget.rule=Host(`nzbget.your_domain.com`)'
      - "traefik.http.services.nzbget.loadbalancer.server.port=6789"
      - "traefik.http.routers.nzbget.middlewares=auth"
      - "traefik.http.middlewares.auth.forwardauth.address=http://organizr/api/?v1/auth&group=1"
    depends_on:
      - traefik
      - organizr

Method 2: Using OAuth / JWT tokens

The first method has the drawback of making calls to Organizr's authorization API for each and every HTTP request made against your protected location blocks, therefore impacting performance.

This method allows you to securely trust the Organizr authentication simply based on the JWT token passed in your authenticated requests cookies. It does not require any extra roundtrip to the Organizr API, nor the rewrite directives.

The flow is as follows:

  1. An unauthenticated user accesses Organizr UI and logs in, using any of the supported login methods.
  2. After successful login, the browser will keep an authentication cookie generated by Organizr using the standard JSON Web Token (JWT) format. This signed token includes a number of claims (such as user id, group memberships etc) that the user has. For more info on this standard, visit https://jwt.io/
  3. This cookie is then passed to any subsequent visit to your Organizr domain and subdomains.
  4. Capable web servers can easily read the JWT token from this cookie and validate it to trust the user's identity and make an allow/deny decision based on the claims mentioned in the token.

Organizr JWT token structure

You can easily check the contents of Organizr-generated JWT tokens by inspecting your browser cookies and pasting the contents of the cookie named organizr_token_<uuid> to a JWT inspector such as https://jwt.io/

Once decoded, an Organizr token includes a JWT payload similar to this:

{
  "iss": "Organizr",
  "aud": "Organizr",
  "jti": "4f1g23a12aa",
  "iat": 1555553579,
  "exp": 1556158379,
  "username": "myusername",
  "group": "Admin",
  "groupID": 0,
  "email": "mail@spam.com",
  "image": "https://www.gravatar.com/avatar/901d703edb7a7f21a92ae87f29484d01?s=100&d=mm",
  "userID": 1
}

It includes various claims such as your user name, group name, user id, group id, which can all be used by your JWT-aware web server to make an authorization decision.

Validating the token

Of course, your web server should not blindly trust the content of the JWT token since it cold have been forged. In order to trust the token content, your web server will need to validate its signature. This requires the server to know a secret specific to your Organizr instance.

Since Organizr uses HS256 signature algorithm, which is a symmetric algorithm, the same secret is used to both by Organizr to sign the token, and by your web server to validate it. It is therefore important that you keep this secret safe, otherwise it may be used to forge tokens and authenticate to your server!

This requires the following 2 pieces of information:

  • The name of the cookie to use to extract the token from. This value is dynamic, of the form organizr_token_<uuid> where <uuid> is your Organizr instance's $GLOBALS['uuid'] value.
  • The secret to use to validate the token signature, which is your Organizr instance's $GLOBALS['organizrHash'] value.

Both these values can be taken from your Organizr server's www/Dashboard/api/config/config.php after initial setup.

On Linux, you may use the following commands to parse them:

cat /config/www/Dashboard/api/config/config.php  | grep organizrHash | sed -r 's/.*=>[^[:alnum:]]*([[:alnum:]]*).*/\1/'
cat /config/www/Dashboard/api/config/config.php  | grep uuid | sed -r 's/.*=>[^[:alnum:]]*([[:alnum:]-]*).*/\1/'

Implementation in Caddy

Here is a sample Caddy directive to protect a path using the Organizr token:

jwt {
    # Name of the path to protect
    path /protected
    
    # Allow / deny based on JWT claims
    allow group Admin
    allow group User
    
    # Where to redirect in case the token is invalid or the claims are denied	
    redirect /
    
    # Where to read the token from
    token_source cookie organizr_token_62d9e46e-cdad-4726-9db7-e25b85397f57
    
    # Path the the secret to validate the token
    secret /etc/myprecious.txt
}

The secret to use to validate the token needs to be passed to Caddy either as an environment variable named JWT_SECRET or in a file, specified with the secret configuration option.

Note that the http.jwt plugin is not installed in default Caddy builds. See https://caddyserver.com/docs/http.jwt for instructions on how to install it.

See https://github.com/BTBurke/caddy-jwt for more information on the jwt plugin and its configuration options.

You should not protect the / Organizr root path. Organizr handles it on its own.

 


Revision #8
Created Sun, Mar 17, 2019 1:46 PM by Roxedus
Updated Tue, Oct 8, 2019 4:22 PM by HalianElf