|
| 1 | +--- |
| 2 | +title: Passkeys |
| 3 | +icon: KeyRound |
| 4 | +description: Let users sign in without a password using a device screen lock or a security key (WebAuthn). Covers enabling passkeys, the relying party settings, enrollment limits, and how passkeys interact with two-factor authentication. |
| 5 | +--- |
| 6 | + |
| 7 | +Passkeys let users with a local (email and password) account sign in with their device screen lock, a password manager, or a hardware security key instead of typing a password. LibreChat implements them with WebAuthn. They are off by default. |
| 8 | + |
| 9 | +<Callout type="info" title="Availability"> |
| 10 | + Newer than **v0.8.8**. Available now on LibreChat's `dev` and `canary` branches, and in the next release. |
| 11 | +</Callout> |
| 12 | + |
| 13 | +Use passkeys when you want a phishing-resistant, passwordless sign-in for local accounts. Accounts that sign in through OAuth, OpenID Connect, SAML, or LDAP keep using their provider and cannot add passkeys. |
| 14 | + |
| 15 | +## Enable passkeys |
| 16 | + |
| 17 | +<Steps> |
| 18 | +<Step> |
| 19 | + |
| 20 | +**Serve LibreChat over HTTPS.** Browsers only allow WebAuthn in a secure context. `http://localhost` is exempt, so local testing works without TLS. |
| 21 | + |
| 22 | +</Step> |
| 23 | +<Step> |
| 24 | + |
| 25 | +**Turn the feature on in `.env`.** For a standard deployment, this is the only required setting. The relying party ID and allowed origins are derived from `DOMAIN_CLIENT` and `DOMAIN_SERVER`. |
| 26 | + |
| 27 | +```bash filename=".env" |
| 28 | +ALLOW_PASSKEY_LOGIN=true |
| 29 | +``` |
| 30 | + |
| 31 | +</Step> |
| 32 | +<Step> |
| 33 | + |
| 34 | +**Pin the relying party ID (recommended).** Each passkey is permanently bound to the RP ID it was created under. If `DOMAIN_CLIENT` might ever change, set `PASSKEY_RP_ID` now so existing passkeys keep working. |
| 35 | + |
| 36 | +```bash filename=".env" |
| 37 | +PASSKEY_RP_ID=chat.example.com |
| 38 | +``` |
| 39 | + |
| 40 | +</Step> |
| 41 | +<Step> |
| 42 | + |
| 43 | +**Restart LibreChat.** The login page shows **Sign in with a passkey**, and **Settings > Account** shows a **Passkeys** section. |
| 44 | + |
| 45 | +</Step> |
| 46 | +</Steps> |
| 47 | + |
| 48 | +## Configuration reference |
| 49 | + |
| 50 | +<OptionTable |
| 51 | + options={[ |
| 52 | + [ |
| 53 | + 'ALLOW_PASSKEY_LOGIN', |
| 54 | + 'boolean', |
| 55 | + 'Enables passkey sign-in and the Passkeys section in account settings. Default: false.', |
| 56 | + 'ALLOW_PASSKEY_LOGIN=true', |
| 57 | + ], |
| 58 | + [ |
| 59 | + 'PASSKEY_RP_ID', |
| 60 | + 'string', |
| 61 | + 'Relying party ID: the domain passkeys are bound to. Must be a domain name (not an IP address) equal to, or a parent domain of, the host users visit. Defaults to the hostname of DOMAIN_CLIENT, or localhost.', |
| 62 | + '# PASSKEY_RP_ID=chat.example.com', |
| 63 | + ], |
| 64 | + [ |
| 65 | + 'PASSKEY_RP_NAME', |
| 66 | + 'string', |
| 67 | + 'Name the authenticator shows when creating or using a passkey. Defaults to APP_TITLE, then LibreChat.', |
| 68 | + '# PASSKEY_RP_NAME=LibreChat', |
| 69 | + ], |
| 70 | + [ |
| 71 | + 'PASSKEY_ORIGINS', |
| 72 | + 'string', |
| 73 | + 'Comma-separated list of origins allowed to run passkey ceremonies. Defaults to DOMAIN_CLIENT and DOMAIN_SERVER.', |
| 74 | + '# PASSKEY_ORIGINS=https://chat.example.com', |
| 75 | + ], |
| 76 | + [ |
| 77 | + 'MAX_PASSKEYS_PER_USER', |
| 78 | + 'integer', |
| 79 | + 'Passkeys each account may enroll, from 1 to 100. Overridden by passkeys.perUserMax in librechat.yaml. Default: 20.', |
| 80 | + '# MAX_PASSKEYS_PER_USER=20', |
| 81 | + ], |
| 82 | + [ |
| 83 | + 'PASSKEY_STEPUP_MAX', |
| 84 | + 'integer', |
| 85 | + 'Password-confirmed passkey add or remove attempts allowed per user in PASSKEY_STEPUP_WINDOW. Default: 20.', |
| 86 | + 'PASSKEY_STEPUP_MAX=20', |
| 87 | + ], |
| 88 | + [ |
| 89 | + 'PASSKEY_STEPUP_WINDOW', |
| 90 | + 'integer', |
| 91 | + 'Window in minutes for PASSKEY_STEPUP_MAX. Default: 15.', |
| 92 | + 'PASSKEY_STEPUP_WINDOW=15', |
| 93 | + ], |
| 94 | + [ |
| 95 | + 'PASSKEY_MAX', |
| 96 | + 'integer', |
| 97 | + 'Passkey sign-in requests allowed per IP in PASSKEY_WINDOW. One sign-in uses two requests. Requests over the limit get HTTP 429; they are not scored as violations because no user is signed in yet. Default: 20.', |
| 98 | + '# PASSKEY_MAX=20', |
| 99 | + ], |
| 100 | + [ |
| 101 | + 'PASSKEY_WINDOW', |
| 102 | + 'integer', |
| 103 | + 'Window in minutes for PASSKEY_MAX. Default: 5.', |
| 104 | + '# PASSKEY_WINDOW=5', |
| 105 | + ], |
| 106 | + ]} |
| 107 | +/> |
| 108 | + |
| 109 | +### Per-user limit in `librechat.yaml` |
| 110 | + |
| 111 | +You can also set the enrollment cap in `librechat.yaml`: |
| 112 | + |
| 113 | +```yaml filename="librechat.yaml" |
| 114 | +passkeys: |
| 115 | + perUserMax: 10 |
| 116 | +``` |
| 117 | +
|
| 118 | +The cap resolves in this order: `passkeys.perUserMax` in `librechat.yaml`, then `MAX_PASSKEYS_PER_USER`, then the default of `20`. Values outside `1` to `100` are ignored and the next source is used. When a user reaches the cap, **Settings > Account** shows "You have reached the maximum number of passkeys". See [`passkeys`](/docs/configuration/librechat_yaml/object_structure/config#passkeys) in the config reference. |
| 119 | + |
| 120 | +## What users see |
| 121 | + |
| 122 | +**Adding a passkey** |
| 123 | + |
| 124 | +1. Open **Settings > Account**, find **Passkeys**, and select **Manage**. |
| 125 | +2. Select **Add passkey**. |
| 126 | +3. Enter the account password in the **Confirm your password** dialog. A passkey is a complete sign-in on its own, so LibreChat asks for the password before creating one. |
| 127 | +4. Complete the browser or device prompt. The new passkey appears in the list with a default name such as **This device**, **Phone or tablet**, or **Security key**. |
| 128 | + |
| 129 | +Each entry shows when it was added and last used, and a **Synced** badge when the authenticator reports that the passkey is backed up (for example, by a password manager). Users can rename a passkey, and removing one (**Remove passkey**) also asks for the account password. |
| 130 | + |
| 131 | +**Signing in** |
| 132 | + |
| 133 | +On the login page, users select **Sign in with a passkey** and approve the device prompt. If the account has two-factor authentication enabled, LibreChat still asks for the 2FA code afterward, exactly as it does after a password sign-in. |
| 134 | + |
| 135 | +## Behavior and caveats |
| 136 | + |
| 137 | +- **Local accounts only.** Passkey enrollment and sign-in are limited to local accounts. Accounts created through an identity provider or LDAP must keep authenticating through that provider. |
| 138 | +- **Works with email login disabled.** The **Sign in with a passkey** button and its routes depend only on `ALLOW_PASSKEY_LOGIN`. With `ALLOW_EMAIL_LOGIN=false`, existing local users can still sign in with passkeys they enrolled earlier. |
| 139 | +- **Two-factor authentication.** Passkey sign-in goes through the same 2FA gate as password sign-in. With [`ENFORCE_TWO_FACTOR_AUTHENTICATION=true`](/docs/configuration/authentication#required-two-factor-authentication), a user who has not enrolled in 2FA is sent to the setup flow after signing in with a passkey. |
| 140 | +- **Changing the RP ID orphans passkeys.** Passkeys created under one RP ID never work under another. Changing `PASSKEY_RP_ID`, or changing `DOMAIN_CLIENT` while `PASSKEY_RP_ID` is unset, leaves every existing passkey unusable; users must sign in another way and enroll again. |
| 141 | +- **Origins must match.** If users reach LibreChat on an origin not covered by `PASSKEY_ORIGINS` (or the derived defaults), the browser shows "Passkeys are not available on this domain". Add every public origin to `PASSKEY_ORIGINS` when you serve LibreChat on more than one. |
| 142 | +- **Password reset removes passkeys.** A completed password reset signs the account out everywhere and deletes all of its passkeys, so a passkey enrolled before the reset can no longer be used to get in. Users enroll again afterward. |
0 commit comments