Add Pocket ID Login to Homelab Apps with Tinyauth and Nginx Proxy Manager

Put an existing web app behind Pocket ID login using native Tinyauth and Nginx Proxy Manager, with a reusable setup for your other homelab apps.

On this page

I already had Pocket ID handling my Proxmox login. Then I wanted the same login for Wastebin, my little paste-sharing app.

Wastebin doesn’t have native user authentication. I wanted to keep using it, so I added Tinyauth alongside my existing Nginx Proxy Manager installation. Pocket ID handles the login. NPM checks with Tinyauth before letting a request through to the app.

This guide gets one whole app behind login first. Allow roughly 20 to 30 minutes, assuming Pocket ID, DNS, HTTPS and shell access already work.

Allow more time if you need to sort out DNS or certificates first.

TL;DR

  • Run Tinyauth as its own systemd service beside a native NPM installation.
  • Register one Tinyauth client in Pocket ID and restrict it to your chosen group.
  • Give Tinyauth an HTTPS hostname, then add forward authentication to an existing NPM app host.
  • Test with an unauthenticated browser before protecting more apps.

For Wastebin’s extra requirement, I wrote a separate guide for authenticated creation and public paste links.

What you need

My working setup uses Debian 13 on an x86_64 Proxmox LXC, native NPM and Tinyauth v5.2.0.

NPM and Tinyauth run as independent services in the same LXC. Because, I am just lazy and I did not wanted to manage another LXC.

There is no Docker installation involved.

You need:

  • A working Pocket ID installation and a passkey you can use.
  • Native NPM already proxying your chosen app successfully.
  • Root shell access to the NPM machine, plus curl and logrotate.
  • A snapshot or backup of that machine and a copy of the app’s existing NPM Advanced (⚙️) configuration.

If your NPM runs in Docker, use the upstream NPM deployment guide (opens in a new tab) instead of copying the localhost addresses below. Inside a container, localhost refers to that container.

Use these example names consistently, replacing them with yours:

PurposeExample
Existing Pocket IDhttps://id.example.com
New Tinyauth portalhttps://auth.example.com
Existing apphttps://app.example.com
Tinyauth listener127.0.0.1:8085 on the NPM machine

Keep the portal and app under the same parent domain for this guide. Both hostnames must resolve to NPM from the browsers using them, and NPM needs valid HTTPS certificates. Pocket ID must also be reachable over trusted HTTPS from Tinyauth.

What Tinyauth adds

flowchart TD
browser["🌐 Browser"] --> npm["🚦 NPM"]
npm -->|Check session| auth["🔐 Tinyauth"]
auth -->|Login| pocket["🗝️ Pocket ID"]
npm -->|Approved requests| app["🏠 Web app"]

This is called forward authentication. NPM still proxies the app traffic. Tinyauth answers the authentication check.

The app doesn’t gain Pocket ID accounts or roles from this configuration. If it has its own login, that login still exists. If the app already supports OIDC properly, use its native integration, as I did with Proxmox.

Restrict the app’s backend port to NPM and any intended management network. Direct backend requests bypass the login check.

1. Install the native binary

Run the shell commands in this guide as root, in Bash, on the NPM machine. These instructions are for a fresh installation. Preserve any existing Tinyauth configuration.

Check the architecture and chosen port:

Terminal window
uname -m
ss -tlnp '( sport = :8085 )'

For the download below, the architecture should be x86_64 and port 8085 should have no listener. My NPM backend already used port 3000, so I chose 8085 for Tinyauth.

Download the v5.2.0 release (opens in a new tab) into a temporary file, then install it:

Terminal window
curl -fL \
https://github.com/tinyauthapp/tinyauth/releases/download/v5.2.0/tinyauth-amd64 \
-o /tmp/tinyauth-amd64
install -o root -g root -m 755 \
/tmp/tinyauth-amd64 /usr/local/bin/tinyauth
/usr/local/bin/tinyauth version

Expect Version: v5.2.0. This guide uses that tested release. Check upstream release notes and security updates before deploying it later.

2. Create the Pocket ID client

In Pocket ID, create a user group named tinyauth_users and add your account. Then open Administration → OIDC Clients and create a client:

SettingValue
NameTinyauth
Callback URLhttps://auth.example.com/api/oauth/callback/pocketid
Client typeConfidential, with a client secret
Allowed User Groupstinyauth_users

Save the client ID and secret somewhere private. Select the group on the client’s Access tab too, so its members can use this client. Pocket ID documents this under allowed user groups (opens in a new tab).

My tested setup left Pocket ID’s PKCE requirement disabled. That isn’t a Tinyauth limitation: v5.2.0 sends an S256 challenge and verifier (opens in a new tab). Requiring PKCE in Pocket ID is a separate setting to validate if you enable it.

3. Configure Tinyauth

Create the service account and directories:

Terminal window
id tinyauth >/dev/null 2>&1 || \
useradd --system --user-group --home-dir /var/lib/tinyauth \
--shell /usr/sbin/nologin tinyauth
install -d -o root -g tinyauth -m 710 /etc/tinyauth
install -d -o tinyauth -g tinyauth -m 700 /var/lib/tinyauth
install -d -o tinyauth -g tinyauth -m 750 /var/log/tinyauth

The 710 permission matters. Tinyauth needs to traverse /etc/tinyauth to read its secret file. Making that directory root-only prevents it from reaching the file, even when the file itself has the right owner.

Store the client secret without putting it in shell history:

Terminal window
read -r -s -p 'Pocket ID client secret: ' TINYAUTH_CLIENT_SECRET
echo
(umask 077; printf '%s' "$TINYAUTH_CLIENT_SECRET" > /etc/tinyauth/pocketid-secret)
unset TINYAUTH_CLIENT_SECRET
chown tinyauth:tinyauth /etc/tinyauth/pocketid-secret
chmod 600 /etc/tinyauth/pocketid-secret

Create /etc/tinyauth/tinyauth.env in your editor. Replace the example domains and REPLACE_WITH_CLIENT_ID. The secret stays in its separate file.

/etc/tinyauth/tinyauth.env
TINYAUTH_APPURL=https://auth.example.com
TINYAUTH_SERVER_ADDRESS=127.0.0.1
TINYAUTH_SERVER_PORT=8085
TINYAUTH_DATABASE_DRIVER=sqlite
TINYAUTH_DATABASE_PATH=/var/lib/tinyauth/tinyauth.db
TINYAUTH_AUTH_SECURECOOKIE=true
TINYAUTH_AUTH_TRUSTEDPROXIES=127.0.0.1/32
TINYAUTH_OIDC_PRIVATEKEYPATH=/var/lib/tinyauth/tinyauth_oidc_key
TINYAUTH_OIDC_PUBLICKEYPATH=/var/lib/tinyauth/tinyauth_oidc_key.pub
TINYAUTH_RESOURCES_PATH=/var/lib/tinyauth/resources
TINYAUTH_LOG_LEVEL=info
TINYAUTH_OAUTH_PROVIDERS_POCKETID_CLIENTID=REPLACE_WITH_CLIENT_ID
TINYAUTH_OAUTH_PROVIDERS_POCKETID_CLIENTSECRETFILE=/etc/tinyauth/pocketid-secret
TINYAUTH_OAUTH_PROVIDERS_POCKETID_AUTHURL=https://id.example.com/authorize
TINYAUTH_OAUTH_PROVIDERS_POCKETID_TOKENURL=https://id.example.com/api/oidc/token
TINYAUTH_OAUTH_PROVIDERS_POCKETID_USERINFOURL=https://id.example.com/api/oidc/userinfo
TINYAUTH_OAUTH_PROVIDERS_POCKETID_REDIRECTURL=https://auth.example.com/api/oauth/callback/pocketid
TINYAUTH_OAUTH_PROVIDERS_POCKETID_SCOPES="openid email profile groups"
TINYAUTH_OAUTH_PROVIDERS_POCKETID_NAME="Pocket ID"

These variables combine the v5.2.0 configuration fields (opens in a new tab) with the Pocket ID provider settings (opens in a new tab). There is no local password user in this setup.

Restrict the environment file and confirm the service account can read the secret:

Terminal window
chown root:root /etc/tinyauth/tinyauth.env
chmod 600 /etc/tinyauth/tinyauth.env
runuser -u tinyauth -- test -s /etc/tinyauth/pocketid-secret && \
runuser -u tinyauth -- test -r /etc/tinyauth/pocketid-secret && \
echo 'Secret exists and is readable'

Create /etc/systemd/system/tinyauth.service:

/etc/systemd/system/tinyauth.service
[Unit]
Description=Tinyauth authentication service
Wants=network-online.target
After=network-online.target
[Service]
Type=simple
User=tinyauth
Group=tinyauth
WorkingDirectory=/var/lib/tinyauth
EnvironmentFile=/etc/tinyauth/tinyauth.env
ExecStart=/usr/local/bin/tinyauth
Restart=on-failure
RestartSec=5
UMask=0077
NoNewPrivileges=true
PrivateTmp=true
StandardOutput=append:/var/log/tinyauth/tinyauth.log
StandardError=append:/var/log/tinyauth/tinyauth.log
[Install]
WantedBy=multi-user.target

The system service manager reads the root-owned environment file before starting the process as tinyauth. The database, keys and resources stay under /var/lib/tinyauth.

Create /etc/logrotate.d/tinyauth so file logs rotate daily, keeping seven rotations:

/etc/logrotate.d/tinyauth
/var/log/tinyauth/*.log {
daily
rotate 7
missingok
notifempty
copytruncate
compress
dateext
}

Check and start the service:

Terminal window
systemd-analyze verify /etc/systemd/system/tinyauth.service
systemctl daemon-reload
systemctl enable --now tinyauth
systemctl is-active tinyauth
curl -I http://127.0.0.1:8085

Expect active and an HTTP response from Tinyauth. This checks startup and connectivity. Browser login comes next.

4. Open the authentication portal

In NPM, create the proxy host auth.example.com with:

  • Scheme: http
  • Forward hostname: 127.0.0.1
  • Forward port: 8085
  • A valid certificate and Force SSL enabled

Disable Block Common Exploits on this portal host, as required by the Tinyauth NPM instructions (opens in a new tab). Its redirect parameters can otherwise be blocked. Don’t add forward-auth rules to the portal itself.

Open https://auth.example.com, choose Pocket ID and sign in. After returning, Tinyauth should show your authenticated account. Get this working before changing the app host.

5. Protect your app in NPM

Keep the app host’s existing upstream and SSL settings. On a standard host without custom locations, merge this into its Advanced (Gear ⚙️) tab. Preserve unrelated settings and avoid duplicate directives or /tinyauth locations.

NPM → app.example.com → Advanced ⚙️
auth_request /tinyauth;
auth_request_set $redirection_url $upstream_http_x_tinyauth_location;
error_page 401 403 =302 $redirection_url;
location = /tinyauth {
internal;
proxy_pass http://127.0.0.1:8085/api/auth/nginx;
proxy_pass_request_body off;
proxy_set_header Content-Length "";
proxy_set_header X-Original-URL $scheme://$http_host$request_uri;
proxy_set_header X-Original-Method $request_method;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Real-IP $remote_addr;
}

This follows the server-level placement used in my native NPM setup. The upstream example (opens in a new tab) puts the authentication directives inside an explicit app location instead. Don’t combine both layouts. Hosts with custom locations need a check of which locations inherit authentication. Preserve their app-specific proxy settings.

NPM asks the internal endpoint for permission, then sends approved requests to the existing backend. Point that check at localhost so it uses the local Tinyauth listener.

Save, then run on the NPM machine:

Terminal window
nginx -t
systemctl is-active npm
systemctl is-active tinyauth

My native installation uses npm.service. Use your installation’s service name if it differs. If NPM rejects the host or nginx -t fails, restore the previous Advanced (⚙️ icon) configuration before continuing.

6. Test without borrowing your own login

From your workstation, request the app without browser cookies:

Terminal window
curl -sS -o /dev/null -D - https://app.example.com/

Expect a 302 with a Location pointing to https://auth.example.com/login?.... Leave out -L so you can inspect the first response.

Then use a different browser where you haven’t logged in:

  1. Open the app and confirm it sends you to Tinyauth.
  2. Sign in through Pocket ID and confirm you return to the app.
  3. Try the app’s normal action and a bookmarked inner page.
  4. Open the same inner page in another unauthenticated browser and confirm it also requires login.

I realize, if i use two private windows in the same browser. It can share a PocketID session. I initially mistook that for public access while testing Wastebin. Firefox and Zen gave me independent sessions, and I verified the final workflow across browsers.

For another app, repeat the NPM configuration and tests. Keep the same Tinyauth instance and Pocket ID client. This policy can help designated apps can reach the endpoint. Different access groups per app need additional Tinyauth access controls (opens in a new tab).

If login fails

invalid_client after Pocket ID consent: check the client ID, then replace an incorrectly copied secret and restart Tinyauth. That fixed my failure. Use the hidden-input command from step 3, then systemctl restart tinyauth.

Secret-file access errors: repeat the service-account readability check. Check both the file ownership and the directory’s traverse permission.

Redirect loops or a rejected callback: compare TINYAUTH_APPURL, the provider’s redirect URL and the Pocket ID callback character for character. Check HTTPS, the shared parent domain, and the portal’s Block Common Exploits setting.

Read the app log with:

Terminal window
tail -n 50 /var/log/tinyauth/tinyauth.log

To undo app protection, restore its previous NPM Advanced (⚙️ icon) configuration and run nginx -t. That restores the app’s earlier access policy, which may be public. Leave Tinyauth running if other hosts use it. Back up /etc/tinyauth and /var/lib/tinyauth privately with the NPM machine. Verify startup after a planned reboot before relying on unattended recovery.

For an app you want entirely private, you’re done. For my pastebin, I also wanted people to read shared links without an account. That change is in Wastebin with Pocket ID: login to create, public links to read.

Comments

← All posts

Type to search the published collection.