Passkeys
Passkeys provide phishing-resistant browser sign-in backed by a device, security key, or synced credential provider. Enable them after the final public hostname and trusted HTTPS boundary are in place: the relying-party ID is part of the credential and is not a cosmetic setting.
1. Choose the relying-party boundary
For https://documents.example.net, the safest default is the exact host:
RP ID: documents.example.net
Public origin: https://documents.example.net
An RP ID may instead be a DNS suffix such as example.net, but doing so deliberately makes the
credential eligible for compatible subdomains. Use a parent domain only when that shared scope is
part of the identity design and every affected origin is trusted. IP addresses and arbitrary HTTP
origins are not supported.
Changing the hostname, moving between a parent and child RP ID, or moving to an unrelated domain can make enrolled credentials unusable. Treat the public origin as durable infrastructure and keep a second sign-in method during any migration.
2. Configure production HTTPS and passkeys together
{
"schema_version": 1,
"server": {
"bind": "127.0.0.1:8077",
"ingress": {
"mode": "https_reverse_proxy",
"public_origin": "https://documents.example.net",
"trusted_proxy_cidrs": ["127.0.0.1/32", "::1/128"]
}
},
"auth": {
"passkeys": {
"rp_id": "documents.example.net",
"rp_name": "Jiandu household",
"public_origin": "https://documents.example.net"
}
}
}
The two public_origin values must match exactly, including a non-default port. rp_name is the
human-readable service name an authenticator may display. It does not affect credential matching.
If the proxy is in a container rather than the host namespace, use Jiandu's private container bind and the proxy's pinned direct address as described in reverse proxy and HTTPS.
Local development exception
WebAuthn permits exact localhost HTTP for development. Jiandu accepts it only on a loopback listener:
{
"schema_version": 1,
"server": {
"bind": "127.0.0.1:8077",
"ingress": {"mode": "loopback_http"}
},
"auth": {
"passkeys": {
"rp_id": "localhost",
"rp_name": "Jiandu development",
"public_origin": "http://localhost:8077"
}
}
}
http://127.0.0.1:8077, another hostname, a non-loopback bind, and ordinary remote HTTP do not
qualify for this exception.
3. Plan enrollment and recovery
Before treating passkeys as a primary method:
- retain the protected Owner token;
- keep a tested local password or linked external provider until enrollment is verified;
- enroll more than one authenticator when possible, avoiding one-device recovery dependence; and
- test sign-in from a clean browser profile on each supported device family.
Registration and authentication ceremonies are short-lived, one-use, and bound to the configured origin and RP ID. User verification is required. Sign-in is username-first; an unknown account gets a bounded decoy ceremony rather than a useful account-existence response.
Jiandu refuses to remove a member's last interactive credential. Revoking a passkey also revokes sessions issued from that exact credential. These checks protect against accidental lockout, but they do not replace the offline Owner recovery copy.
4. Validate before enrollment
jiandu --config /etc/jiandu/jiandu.json --check-config
curl --fail --silent https://documents.example.net/api/v1/auth/methods \
| jq '.localPasskey'
Expect true. Then verify the certificate in a normal browser and exercise registration plus a
fresh sign-in. passkeys_disabled means the effective file has no auth.passkeys object. Origin
errors almost always mean the browser URL, reverse-proxy origin, and passkey origin are not the same
exact value. Credential failures after a hostname migration require the old origin or another
sign-in/recovery method; weakening TLS checks will not repair RP-ID binding.