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
curlandlogrotate. - 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:
| Purpose | Example |
|---|---|
| Existing Pocket ID | https://id.example.com |
| New Tinyauth portal | https://auth.example.com |
| Existing app | https://app.example.com |
| Tinyauth listener | 127.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:
uname -mss -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:
curl -fL \ https://github.com/tinyauthapp/tinyauth/releases/download/v5.2.0/tinyauth-amd64 \ -o /tmp/tinyauth-amd64install -o root -g root -m 755 \ /tmp/tinyauth-amd64 /usr/local/bin/tinyauth/usr/local/bin/tinyauth versionExpect 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:
| Setting | Value |
|---|---|
| Name | Tinyauth |
| Callback URL | https://auth.example.com/api/oauth/callback/pocketid |
| Client type | Confidential, with a client secret |
| Allowed User Groups | tinyauth_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:
id tinyauth >/dev/null 2>&1 || \ useradd --system --user-group --home-dir /var/lib/tinyauth \ --shell /usr/sbin/nologin tinyauthinstall -d -o root -g tinyauth -m 710 /etc/tinyauthinstall -d -o tinyauth -g tinyauth -m 700 /var/lib/tinyauthinstall -d -o tinyauth -g tinyauth -m 750 /var/log/tinyauthThe 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:
read -r -s -p 'Pocket ID client secret: ' TINYAUTH_CLIENT_SECRETecho(umask 077; printf '%s' "$TINYAUTH_CLIENT_SECRET" > /etc/tinyauth/pocketid-secret)unset TINYAUTH_CLIENT_SECRETchown tinyauth:tinyauth /etc/tinyauth/pocketid-secretchmod 600 /etc/tinyauth/pocketid-secretCreate /etc/tinyauth/tinyauth.env in your editor. Replace the example domains and REPLACE_WITH_CLIENT_ID. The secret stays in its separate file.
TINYAUTH_APPURL=https://auth.example.comTINYAUTH_SERVER_ADDRESS=127.0.0.1TINYAUTH_SERVER_PORT=8085TINYAUTH_DATABASE_DRIVER=sqliteTINYAUTH_DATABASE_PATH=/var/lib/tinyauth/tinyauth.dbTINYAUTH_AUTH_SECURECOOKIE=trueTINYAUTH_AUTH_TRUSTEDPROXIES=127.0.0.1/32TINYAUTH_OIDC_PRIVATEKEYPATH=/var/lib/tinyauth/tinyauth_oidc_keyTINYAUTH_OIDC_PUBLICKEYPATH=/var/lib/tinyauth/tinyauth_oidc_key.pubTINYAUTH_RESOURCES_PATH=/var/lib/tinyauth/resourcesTINYAUTH_LOG_LEVEL=infoTINYAUTH_OAUTH_PROVIDERS_POCKETID_CLIENTID=REPLACE_WITH_CLIENT_IDTINYAUTH_OAUTH_PROVIDERS_POCKETID_CLIENTSECRETFILE=/etc/tinyauth/pocketid-secretTINYAUTH_OAUTH_PROVIDERS_POCKETID_AUTHURL=https://id.example.com/authorizeTINYAUTH_OAUTH_PROVIDERS_POCKETID_TOKENURL=https://id.example.com/api/oidc/tokenTINYAUTH_OAUTH_PROVIDERS_POCKETID_USERINFOURL=https://id.example.com/api/oidc/userinfoTINYAUTH_OAUTH_PROVIDERS_POCKETID_REDIRECTURL=https://auth.example.com/api/oauth/callback/pocketidTINYAUTH_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:
chown root:root /etc/tinyauth/tinyauth.envchmod 600 /etc/tinyauth/tinyauth.envrunuser -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:
[Unit]Description=Tinyauth authentication serviceWants=network-online.targetAfter=network-online.target
[Service]Type=simpleUser=tinyauthGroup=tinyauthWorkingDirectory=/var/lib/tinyauthEnvironmentFile=/etc/tinyauth/tinyauth.envExecStart=/usr/local/bin/tinyauthRestart=on-failureRestartSec=5UMask=0077NoNewPrivileges=truePrivateTmp=trueStandardOutput=append:/var/log/tinyauth/tinyauth.logStandardError=append:/var/log/tinyauth/tinyauth.log
[Install]WantedBy=multi-user.targetThe 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:
/var/log/tinyauth/*.log { daily rotate 7 missingok notifempty copytruncate compress dateext}Check and start the service:
systemd-analyze verify /etc/systemd/system/tinyauth.servicesystemctl daemon-reloadsystemctl enable --now tinyauthsystemctl is-active tinyauthcurl -I http://127.0.0.1:8085Expect 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.
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:
nginx -tsystemctl is-active npmsystemctl is-active tinyauthMy 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:
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:
- Open the app and confirm it sends you to Tinyauth.
- Sign in through Pocket ID and confirm you return to the app.
- Try the app’s normal action and a bookmarked inner page.
- 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:
tail -n 50 /var/log/tinyauth/tinyauth.logTo 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