mirror of
https://github.com/pocket-id/pocket-id.git
synced 2025-12-06 09:13:19 +03:00
🚀 Feature: Multi-factor Passkey Authentication with a PIN #55
Reference in New Issue
Block a user
Delete Branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Originally created by @lordraiden on GitHub.
Feature description
This feature introduces an optional second factor of authentication to the Pocket-ID passkey flow. While passkeys themselves offer a robust, phish-resistant form of authentication, their security model assumes the physical security of the device (e.g., a laptop, phone). This feature adds a user-defined PIN as an additional requirement for passkey authentication, creating a two-factor authentication (2FA) experience even with a single device.
This approach addresses scenarios where:
The user's device might be temporarily accessible to others.
The user wants to protect high-value accounts (e.g., financial, healthcare) with an extra layer of security.
The device itself is a shared resource, or the user wants to add friction to prevent a casual or accidental login.
The PIN would be stored securely and would be a required step before the passkey is used to authenticate.
Pitch
Adding a PIN feature to Pocket-ID is a critical enhancement that addresses the real-world security gaps not covered by device-centric passkey authentication. While passkeys are secure against phishing, they operate under the assumption of device integrity and physical security. This feature introduces a low-friction, high-impact layer of multi-factor authentication (MFA) that secures accounts even if the user's device is compromised or temporarily accessible to others.
This is a security best practice already adopted by leading hardware security keys like YubiKeys, which allow a PIN to unlock the key's functionality. By making this feature optional and configurable—with options for 'always require' or 'expire after a period of time'—Pocket-ID can offer a more robust security solution that caters to the diverse needs and risk profiles of its users.
Implementing this feature is not just a 'nice to have' but a fundamental step toward making Pocket-ID a more comprehensive and resilient authentication platform. It directly addresses the security concerns that arise when the physical security of a device cannot be guarantee.
Use cases
Scenario: You have a shared family tablet or a desktop PC that is always logged in to a common user account.
Problem: Your Pocket-ID admin dashboard passkey is stored on that device. If a child or house guest picks up the tablet, they could potentially navigate to the Pocket-ID URL and gain administrator access to all your self-hosted services, whether intentionally or accidentally.
Solution: By requiring a PIN specifically for the admin dashboard, you add an extra layer of protection. Even if the device's passkey is recognized, the login will fail without the PIN, preventing unauthorized configuration changes to your entire homelab authentication system.
Scenario: You host a private Gitea instance with your code repositories, a Nextcloud instance with personal documents, or a Vaultwarden instance with your secrets. You've configured Pocket-ID to handle authentication for all of these.
Problem: While a passkey is secure, if your laptop is open and unlocked, it's trivial to access these sensitive services. Passkeys are designed for frictionless login, which can be a downside in this context.
Solution: The PIN feature could be configured on a per-client basis. You could have a rule that requires a PIN to log in to gitea.homelab.local, but not for a non-critical service like your internal Wiki or monitoring dashboard. This provides granular control and high security where it's most needed.
Scenario: You've purchased a hardware security key (like a YubiKey or Titan Key) to use with Pocket-ID for the highest level of security.
Problem: If you lose that key, anyone who finds it could theoretically plug it in and use it to authenticate to all your services, as long as they know which device to plug it into.
Solution: A PIN for the passkey is a standard feature for many hardware keys precisely for this reason. By integrating this into Pocket-ID's flow, you can enforce the use of that PIN for your entire homelab. This means even a stolen or lost key is useless without the accompanying PIN.
Scenario: You have a friend or family member who wants to use your Plex or Jellyfin server, which is authenticated via Pocket-ID. You give them a passkey to use on their phone.
Problem: What happens if they lose their phone? Or what if you want to provide them with a one-time use passkey without them having full control over their account?
Solution: A PIN feature can be configured with an expiration. You could issue a passkey to a guest that is only valid for a 24-hour period or requires a PIN that you've told them only once. This adds a layer of control and revokability that isn't built into the base passkey standard
@stonith404 commented on GitHub:
Thanks for your feature request, but we are not planning to implement MFA, see #446. Depending on the implementation most of the passkeys require you to either use Face ID, Touch ID, a PIN or a password. For example if I store my passkey in my iCloud keychain I can only use it if I use Touch ID or my password to confirm it. Yubikeys allow you to set a PIN on the key itself as well.
@savely-krasovsky commented on GitHub:
@lordraiden I still don't see anything new in what you have said. I understand it perfectly, but in my opinion, it's better to improve the security of authenticators rather than Pocket-ID itself. As an industry, we are moving toward passkeys to achieve a big goal: a passwordless future. Instead, you are suggesting we reject their main advantage and keep using passkeys as a simple autofill substitute with the extra 2FA-like security they provide. No, that's not how they were intended to work. Authentication security should be centered around authenticators, not Relying Parties.
I could potentially work on implementing an AAGUID check with attestation validation. This would allow you to enforce the use of specific authenticators on specific OIDC clients. For example, you would be able to restrict authentication to only FIDO2-certified authenticators on specific OIDC client. Would this help in your case?
@savely-krasovsky commented on GitHub:
@lordraiden I am definitely not against
userVerification: required. This is what we can and probably should implement. But I was under impression that Pocket-ID already does it by default. It's not the case though? (To clarify, I am not Pocket-ID developer, but rather occasional contributor).@lordraiden commented on GitHub:
@savely-krasovsky
Ok I understand your posture and might reach to a solution if I understood it well.
I believe that while attestation validation is a necessary security measure, it is not entirely sufficient.
Attestation validation is about trusting the authenticator. It confirms that the passkey was created by a genuine device (e.g., a real YubiKey or an Apple Secure Enclave) and not a piece of malware. It's a powerful check that happens once, at registration.
User verification (UV) is about trusting the user. It confirms that the person currently using the authenticator is the legitimate owner by requiring a PIN, biometric, or password.
The security gap is this: An attacker with physical access to a device with an already-registered, attested passkey could still use it to authenticate if the user verification step is not strictly enforced. Some passkeys allow a simple "tap to log in" without a UV check.
By combining your proposed attestation validation with a userVerification: 'required' policy option, Pocket-ID would solve both problems:
Problem A (Attestation): An administrator can ensure only genuine, trusted authenticators are registered for a service.
Problem B (User Verification): The administrator can force the user to perform a biometric or PIN check on every login to a sensitive service, even if the authenticator or device would otherwise allow a frictionless "tap to log in."
https://openid.net/specs/fapi-attacker-model-2_0-final.html?hl=es-ES#:~:text=Other%20endpoints%20not%20controlled%20by,of%20scope%20for%20this%20specification.
@lordraiden commented on GitHub:
The core of the issue isn't about whether a PIN is used, but who controls the requirement for that PIN.
It is a way to enforce a consistent, high-security policy at the application level, regardless of what the user's OS or browser decides is "convenient." It's about providing the homelab administrator with the ability to say, "For this specific service, a PIN is always required, even if you just logged in a minute ago on the same device and your Face ID worked."
Use the existing FIDO2 userVerification capability to its full potential by allowing the relying party (Pocket-ID) to override the client's (browser/OS) default behavior.
Examples in a homelab context:
Securing a shared admin panel: A single admin user has access to a dashboard for Proxmox or a router. To ensure a family member can't accidentally get in, the administrator requires an additional PIN for that specific login, even if their device is already unlocked.
Controlling access to critical data: A user has a Nextcloud instance with private family photos. They can configure Pocket-ID to always require an explicit PIN before authenticating to the nextcloud.homelab.local address, adding a deliberate security step for high-value data.
Forcing multi-factor on a new device: A user logs in to a homelab service from a friend's laptop. While the OS would normally handle the passkey, the homelab administrator wants a higher level of assurance. The Pocket-ID policy forces a PIN prompt, ensuring the user deliberately verifies their identity on this new device.
Preventing a compromised hardware key from granting access: A user with a YubiKey loses it. The PIN configured on the key itself is the primary defense. The Pocket-ID policy for all logins reinforces this by always requesting a PIN, making the stolen key useless on its own.
It could defined by user and app and maybe even by device
@savely-krasovsky commented on GitHub:
@lordraiden I will research on this, it definitely makes sense to implement, thanks for pointing!
@lordraiden commented on GitHub:
@savely-krasovsky not sure how it is implement but it's not the default.
I think the issue is better explained here
https://www.corbado.com/blog/webauthn-user-verification?hl=es-ES#6-recommendations-for-user-verification
@stonith404 commented on GitHub:
"userVerification" is already set to "required", see
77f30c4e47/backend/internal/service/webauthn_service.go (L34)@lordraiden commented on GitHub:
Thanks, also take a look to this link https://web.dev/articles/webauthn-user-verification