Files
docudjeex/content/en/3.serveex/3.security/3.tinyauth.md
T

11 KiB

title, description
title description
TinyAuth Install TinyAuth, a lightweight forward-auth proxy, and pair it with Pocket ID to add SSO login in front of your self-hosted apps. Protect your app behind Swag with forward-auth.

:ellipsis{left=0px width=40rem top=10rem blur=140px zIndex=60}

TinyAuth is a small forward-auth proxy: a single login page that Swag can insert in front of any app before letting a request through, checking whether a visitor is authenticated before forwarding them on.

tinyauth

It supports a simple local username/password login out of the box, which is what we'll set up here. It can also delegate login to an external OIDC provider like Pocket ID instead, so anyone visiting a protected app authenticates with a passkey via Pocket ID and then gets forwarded through: install Pocket ID afterwards and follow its guide to connect the two.

Installation

::file-tree

tree: /: - docker: - tinyauth: - compose.yaml - .env - data/

::

::steps{level="3"}

Create the data folder

sudo mkdir -p /docker/tinyauth/data

Generate a password hash

sudo docker run -i -t --rm ghcr.io/tinyauthapp/tinyauth:v5 user create --interactive

::note

Enable "Format for Docker" when prompted, so the generated hash is already escaped for use in a .env file. ::

Deploy the stack

Open Dockge, click compose, name the stack tinyauth, and add the following config:

---
services:
  tinyauth:
    image: ghcr.io/tinyauthapp/tinyauth:v5
    container_name: tinyauth
    restart: unless-stopped
    env_file:
      - .env
    volumes:
      - /docker/tinyauth/data:/data
    ports:
      - 3000:3000

::tip{icon=""} Add the Watchtower label to automate updates:

---
services:
  tinyauth:
    #...
    labels:
      - com.centurylinklabs.watchtower.enable=true

::

Set your environment variables

Fill in the .env file:

TINYAUTH_APPURL=https://tinyauth.mydomain.com
TINYAUTH_AUTH_USERS=
Variable Value Example
TINYAUTH_APPURL{lang=properties} The public URL you'll use to reach TinyAuth (see exposure below) https://tinyauth.mydomain.com
TINYAUTH_AUTH_USERS{lang=properties} The hash generated above user:$$2a$$10$$UdLYoJ5lgPsC0RKq...

Deploy the stack. The local interface is available at http://yourserverip:3000.

Done !

::

Enabling Two-Factor Authentication

TinyAuth can require a TOTP code from an authenticator app (Google Authenticator, Aegis...) alongside the local password, per user. This is a property of the user entry itself, not a toggle in the web UI.

::steps{level="3"}

Generate a TOTP secret

sudo docker run -i -t --rm ghcr.io/tinyauthapp/tinyauth:v5 totp generate --interactive

Enter the username:hash pair you generated during installation. TinyAuth prints a QR code to scan with your authenticator app, then outputs the updated login string as username:hash:secret.

::note

Both docker run and docker exec need the -it flags here: the command is interactive and renders the QR code in the terminal, which needs a TTY (and a wide enough window) to display correctly. ::

Update your environment variable

Replace that user's entry in TINYAUTH_AUTH_USERS with the new username:hash:secret string, then redeploy the stack.

::tip{icon=""} Tip: Verify the flow works before relying on it:

sudo docker run -i -t --rm ghcr.io/tinyauthapp/tinyauth:v5 user verify --interactive

It re-prompts for the username, password, and current 6-digit code. ::

Done !

::

From now on, that user needs both their password and a valid code from their authenticator app to log in.

Exposing TinyAuth with Swag

TinyAuth needs its own subdomain: it's the page users land on before being forwarded to the app they actually want.

::note

We assume you have the subdomain tinyauth.mydomain.com with a CNAME pointing to mydomain.com in your DNS zone. And of course, unless you use Cloudflare Zero Trust, your box's port 443 must be forwarded to your server's port 443 in NAT rules. ::

::steps{level="3"}

Add TinyAuth's network to SWAG

Go to Dockge and edit SWAG's compose file by adding TinyAuth's network:

---
services:
  swag:
     container_name: # ...
      # ... 
     networks:               # Attach container to custom network 
      # ...           
      - tinyauth             # Name of the declared network

networks:                    # Define the custom network
  # ...
  tinyauth:                  # Declared network name
    name: tinyauth_default   # Actual external network name
    external: true           # Marks it as externally defined

Redeploy the stack and wait for SWAG to be fully operational.

::note

Here we assume the TinyAuth network name is tinyauth_default. You can check the connection by visiting SWAG's dashboard at http://yourserverip:81. ::

Create the subdomain.conf file

In the Swag folders, create the file tinyauth.subdomain.conf:

::tip{icon="" to="/serveex/files/file-browser-quantum"} Tip: Use File Browser Quantum to navigate and edit files instead of using terminal commands. ::

sudo nano /docker/swag/config/nginx/proxy-confs/tinyauth.subdomain.conf

Paste the following configuration:

## Version 2023/12/19

server {
    listen 443 ssl;
    listen [::]:443 ssl;

    server_name tinyauth.*;

    include /config/nginx/ssl.conf;

    client_max_body_size 0;

    location / {
        include /config/nginx/proxy.conf;
        include /config/nginx/resolver.conf;
        set $upstream_app tinyauth;
        set $upstream_port 3000;
        set $upstream_proto http;
        proxy_pass $upstream_proto://$upstream_app:$upstream_port;
    }
}

Press :kbd{value="Ctrl+O"}, then :kbd{value="Enter"} to save, and :kbd{value="Ctrl+X"} to exit.

Visit your new subdomain

Wait a few minutes, then open https://tinyauth.mydomain.com in your browser and log in with the username/password you created above.

::caution

If it fails: check your firewall rules. ::

Done !

::

Protecting an app via reverse proxy

Swag doesn't ship a ready-made include file for TinyAuth, so we'll add the forward-auth check directly to the app's own *.subdomain.conf. We'll use Dockge as an example.

::steps{level="3"}

Open the app's subdomain.conf file

sudo nano /docker/swag/config/nginx/proxy-confs/dockge.subdomain.conf

Add the forward-auth check

Add an internal /tinyauth location, and reference it from the app's location / block with auth_request:

server {
    listen 443 ssl;
    listen [::]:443 ssl;

    server_name dockge.*;

    include /config/nginx/ssl.conf;

    client_max_body_size 0;

    location /tinyauth {
        internal;
        proxy_pass http://tinyauth:3000/api/auth/nginx;
        proxy_pass_request_body off;
        proxy_set_header Content-Length "";
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header X-Forwarded-Host $http_host;
        proxy_set_header X-Forwarded-Uri $request_uri;
    }

    location @tinyauth_login {
        return 302 https://tinyauth.mydomain.com/login?redirect_uri=$scheme://$http_host$request_uri;
    }

    location / {
        auth_request /tinyauth;
        error_page 401 = @tinyauth_login;

        include /config/nginx/proxy.conf;
        include /config/nginx/resolver.conf;
        set $upstream_app dockge;
        set $upstream_port 5001;
        set $upstream_proto http;
        proxy_pass $upstream_proto://$upstream_app:$upstream_port;
    }
}

::note{to="/serveex/security/tinyauth#exposing-tinyauth-with-swag"}

The location /tinyauth block runs inside SWAG's own container, so SWAG needs to be on TinyAuth's Docker network to reach it by name (tinyauth here). This should already be set up from exposing TinyAuth itself. If you run into an error, double-check SWAG's compose file still has that network attached. ::

Press :kbd{value="Ctrl+O"}, then :kbd{value="Enter"} to save, and :kbd{value="Ctrl+X"} to exit.

Done !

::

That's it! Visiting https://dockge.mydomain.com now redirects to TinyAuth first. Repeat this location /tinyauth / auth_request pattern in any other app's *.subdomain.conf to protect it the same way.

::note

Repeat this process for each app you want to protect (unless it has native OIDC support, in which case you can point it directly at Pocket ID instead). ::

Leaving specific paths public

Sometimes you want most of an app locked behind TinyAuth, but a handful of paths left open, for example a public status page, or the API endpoints a mobile app relies on. Unlike Authentik, TinyAuth has no built-in "authenticated paths" setting for this: it's a plain nginx problem, and it's solved with nginx's own location matching.

A regex location block always takes priority over the plain location / block, no matter which one appears first in the file. So any path matched by a regex location you define runs its own proxy_pass, without ever reaching the auth_request /tinyauth; line in location /.

For example, to leave Uptime-Kuma's public status page and its assets open while protecting everything else:

server {
    listen 443 ssl;
    listen [::]:443 ssl;

    server_name stats.*;

    include /config/nginx/ssl.conf;

    location ~ ^/(status|assets|icon\.svg|api|upload|metrics) {
        include /config/nginx/proxy.conf;
        include /config/nginx/resolver.conf;
        set $upstream_app uptime-kuma;
        set $upstream_port 3001;
        set $upstream_proto http;
        proxy_pass $upstream_proto://$upstream_app:$upstream_port;
    }

    location /tinyauth {
        internal;
        proxy_pass http://tinyauth:3000/api/auth/nginx;
        proxy_pass_request_body off;
        proxy_set_header Content-Length "";
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header X-Forwarded-Host $http_host;
        proxy_set_header X-Forwarded-Uri $request_uri;
    }

    location @tinyauth_login {
        return 302 https://tinyauth.mydomain.com/login?redirect_uri=$scheme://$http_host$request_uri;
    }

    location / {
        auth_request /tinyauth;
        error_page 401 = @tinyauth_login;

        include /config/nginx/proxy.conf;
        include /config/nginx/resolver.conf;
        set $upstream_app uptime-kuma;
        set $upstream_port 3001;
        set $upstream_proto http;
        proxy_pass $upstream_proto://$upstream_app:$upstream_port;
    }
}

::note

Adjust the list of excluded paths to what the app you're protecting actually needs public. Never leave an admin or settings path in that list, only what the app itself documents as safe to expose unauthenticated. ::