Contents

Pocket-ID and Passkeys for the Homelab

An OIDC provider with no password field, and the discipline that demands

Contents

I went looking for a small identity provider after doing the arithmetic on my last one. Authentik is genuinely excellent and it is four containers, Postgres, Redis, a Python application and about 2GB of RAM to authenticate the six people who use my rack — of whom four are me on different devices. That’s a lot of machinery to answer a question that gets asked eleven times a day.

Pocket-ID is the opposite bet, and it’s a 2024 project, so it arrives with no legacy to carry. It’s an OIDC provider in a single container with a SQLite database, and its distinguishing decision is that it has no password field anywhere. Not “passwords with passkeys available”. Not “passwords with MFA enforced”. There is no password. The only way to authenticate is a passkey, and the entire user-facing surface is one button.

That constraint is either the best feature in the category or a dealbreaker, and which one depends almost entirely on whether you’ve thought about recovery. Let me do the mechanics first.

Why removing the password is the whole point

Advertisement

A passkey is a WebAuthn credential: a keypair where the private half lives in a secure element on your device — a phone’s enclave, a laptop’s TPM, a hardware key — and never leaves it. Authentication is a signature over a challenge, bound to the origin that issued it. I’ve laid out the whole model in passkeys explained, and the property that matters here is the origin binding.

A passkey registered for id.example.com will refuse to sign a challenge from id.exampIe.com with a capital I. The browser enforces it, the user cannot override it, and no amount of urgency in a phishing email changes the arithmetic. That’s a genuinely different security posture from “a strong password plus TOTP”, where the user is one convincing page away from typing both into somebody else’s form.

The catch, and it’s the reason most deployments hedge: a system with passwords and passkeys is exactly as phishable as its password. The fallback is the attack surface. Every “log in with your password instead” link is a bypass for the whole scheme, and attackers know to click it. A system with no password has no bypass, which means the phishing-resistance is actually real rather than aspirational.

Everyone who has enabled WebAuthn as a second factor — the shape described in TOTP and WebAuthn — has improved things meaningfully while keeping the weak path available. Pocket-ID’s contribution is deleting the weak path, and it can only do that because it’s small enough to have no other constituencies to please.

Setting it up

One container, SQLite, and one environment variable that matters:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
services:
  pocket-id:
    image: ghcr.io/stonith404/pocket-id:v0.20.0
    restart: unless-stopped
    environment:
      # MUST match the URL users actually visit, exactly.
      # WebAuthn credentials are bound to this origin.
      PUBLIC_APP_URL: https://id.example.com
      TRUST_PROXY: "true"
    volumes:
      - ./data:/app/backend/data
    ports:
      - "3000:80"
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost/health"]
      interval: 90s
      timeout: 10s

PUBLIC_APP_URL is load-bearing in a way that will bite you if you skim it. WebAuthn credentials are cryptographically bound to the relying party ID derived from that origin. Set it to http://192.168.1.30:3000, register your passkeys, then later move to https://id.example.com, and every credential is silently void. The keys still exist. They will refuse to sign for the new origin, forever. Decide the hostname before anyone registers anything, and put it behind a real certificate on day one — Caddy makes that three lines. WebAuthn also requires a secure context, so plain HTTP works only on localhost and nowhere else.

SQLite is the right call here and worth defending, because people see it and assume “toy”. An identity provider for a household is a workload with a few dozen rows and a handful of writes a day. Postgres would be a network hop and a second failure domain to serve a table you could print on one page. It’s the same argument as SQLite is a production database, and it’s why the whole thing fits in about 100MB of RAM. Back up the data directory and you’ve backed up your entire identity infrastructure.

First run prints a one-time setup URL in the container log. Follow it, register your first passkey, and that’s the admin account. Then create OIDC clients from the admin UI — each gives you a client ID, a secret, and the standard discovery endpoint that any competent OIDC client will consume to configure itself:

1
https://id.example.com/.well-known/openid-configuration

Point Grafana, Immich, Nextcloud, Gitea, Proxmox or anything else that speaks OIDC at that, paste the client ID and secret, and it works. Pocket-ID supports user groups and will put them in the token’s claims, so applications that map groups to roles get that for free.

The recovery story, which is the actual product decision

Advertisement

Here’s the question that decides whether this is for you. Your phone is at the bottom of a canal. Your laptop is at work. How do you log in?

Pocket-ID’s answer is one-time access links: an admin generates a URL that grants a single login, during which the user registers a new passkey. Elegant, and it has an obvious problem — when the locked-out user is the admin, there’s nobody to generate it.

So the setup is only complete when you’ve handled that, and there are exactly three approaches worth taking:

Register at least three passkeys on the admin account. A phone, a laptop, and a hardware key that lives in a drawer in a different building. This is the answer for most people and it takes four minutes. A YubiKey in the paperweight drawer earns its keep the day you need it and costs nothing the other 364.

Use a syncing passkey provider for one of them. A passkey in a password manager or a platform account syncs across your devices and survives a dead phone. It’s a weaker credential — it exists in more places and its security reduces to that account’s — and as one of three it’s a reasonable hedge. If you already run Vaultwarden, it can hold passkeys, which is a pleasing loop right up until you notice Vaultwarden is behind Pocket-ID, at which point you have built a circle. Don’t do that. The syncing one must live somewhere outside the estate it protects.

Know how to get in from the filesystem. The database is a SQLite file. With shell access on the host you can inspect and modify it, and a one-time link can be manufactured. Document that path in your runbook now, while you’re calm, because you’ll want it while you’re not.

Skip all three and this is a system that will lock you out of your entire rack over a dropped phone. That’s the honest risk, and it’s the same class of mistake as generating an SSH key with no backup access — the argument in hardening SSH beyond the basics about always keeping a second way in applies with full force.

What you give up by being this small

Worth being explicit about the missing furniture, because the list is long and the omissions are deliberate.

There is no flow engine. Authentication is: prove you hold a passkey. You cannot express “challenge remote sessions but trust the LAN”, or “this group needs two factors”, or “show a consent screen for these scopes only”. For most homelabs that’s fine — the policy you’d have written is the policy Pocket-ID hardcodes, and hardcoding it means it can’t be misconfigured.

There is no SAML. Nothing in a normal rack needs SAML, and if something does, this is the wrong tool and you know it already.

There is no LDAP, in either direction. It won’t serve a directory to that old Java application, and it won’t consume your existing one, so users live in Pocket-ID’s own table and nowhere else. For a household that’s a feature; for anything with an existing directory it’s a non-starter.

There’s no user self-registration worth the name, no email flows, no branding beyond a logo. The admin creates accounts and sends a link. At six users that’s the correct amount of machinery, and the absence of an email dependency is quietly one of the best things about it — no SMTP credentials, no deliverability problem, no “the password reset email went to spam” support call, because there is no password to reset.

The pattern across all of these is the same: the features exist elsewhere and cost RAM, complexity and configuration surface. Pocket-ID’s argument is that a homelab identity provider that does one thing perfectly beats one that does nine things you’ll never enable. I find that argument persuasive right up to the OIDC boundary, and completely unpersuasive past it.

What actually goes wrong

Passkeys stopped working after a change. PUBLIC_APP_URL changed, or you added a proxy that rewrote the Host header. The relying party ID no longer matches what the credential was registered against. Every credential registered under the old origin is dead. Prevention is the only cure.

“This site can’t ask you to register” in the browser. Missing secure context. HTTPS, or localhost, or nothing. A self-signed certificate that the browser hasn’t been told to trust counts as nothing.

Login works from outside and fails from inside. Internal DNS resolves the hostname somewhere else, so the origin differs. This is the split-horizon DNS problem wearing a WebAuthn hat: both paths must land on the same origin string.

The OIDC client rejects the token. Redirect URI mismatch, character for character, as always. Pocket-ID’s audit log shows the URI it received; diff it against what you configured rather than squinting at both.

An application doesn’t speak OIDC. Pocket-ID has no proxy provider and no forward-auth endpoint. This is its hardest limit and there’s no clever way round it. You’d bolt oauth2-proxy in front as a separate component, which works and undoes some of the simplicity you came for.

Users don’t understand what they’re being asked. “Use your fingerprint to sign in to id.example.com” is a novel dialogue for most people and it reads like a scam the first time. Enrol people in person, once. It sticks immediately afterwards.

The honest verdict

Pocket-ID is the right choice for a small rack of applications that all speak OIDC, run by someone who will actually register three passkeys and mean it. It’s about 100MB of RAM, one container, one file to back up, and the resulting login experience is better than anything with a password in it — one button, no typing, no TOTP codes, no phishing surface. For a household of technical-adjacent people it removes the “what’s the password for the photo thing” conversation permanently.

It’s the wrong choice the moment something in your rack can’t do OIDC. No proxy provider means legacy applications stay outside the scheme entirely, and if that’s three of your twelve services, you’re running two identity systems and have made things worse. That’s precisely where Authentik earns its 2GB — the proxy and LDAP providers exist to drag the stubborn things in, and no amount of elegance elsewhere substitutes for that. The full shape of that trade-off is in Authelia vs Authentik, and Pocket-ID sits below both on the same axis.

It’s also young. September 2024 is not long to have existed, the release cadence is brisk, and the surface will move under you. For an identity provider — the component whose failure locks you out of everything — that’s a real cost, and it’s the honest reason to hesitate rather than any technical objection.

What I’d say is this: the passwordless constraint is the good idea here, and it works precisely because it’s absolute. Every system I’ve run that offered passkeys and passwords ended up authenticating me with a password most of the time, because the fallback was right there and I was in a hurry. Removing the option removed the drift. That’s a small piece of design doing more work than a lot of features.

Advertisement
Advertisement
Smarc
Written by Smarc

Founder and editor of vo.rs. A lifelong tinkerer who self-hosts far more than is sensible, hardens Linux boxes for fun, and prods the latest AI tools to see what they can really do. The how-to guides here are the notes Smarc wishes had existed the first time round.