-
Notifications
You must be signed in to change notification settings - Fork 4
Account Protection
Layered defenses against credential attacks: account lockout, per-IP brute-force banning, impossible-travel detection, per-user IP allowlists, empty-password blocking, and hardened self-service factor changes.
After N failed attempts, an account is temporarily locked.
- Counts both wrong passwords and failed 2FA codes (since v2.5.10 — #55).
- The lock is enforced before the password is checked, and the login page shows a clear "temporarily locked" message.
- Administrators are exempt by default (toggle in Settings) so lockout can't be turned into a denial-of-service against the admin account — the per-IP brute-force ban still protects them.
Configure: Settings → Security → "Max failed attempts before lockout" + lockout duration (default 5 attempts → 15-minute cool-down).
When enabled, the plugin refuses empty/whitespace passwords for every sign-in (not just OIDC/passkey users).
- Toggle: Settings → Security → Block sign-in with an empty password (admin-UI toggle since v2.5.10; previously config-file-only).
- Protects pre-existing password-less accounts, and closes the auto-created-OIDC-user empty-password path (#68).
Auto-created OIDC users are hardened with a 256-bit random password at creation, so Jellyfin's default provider stops accepting "any password" for them.
Refuse username+password sign-in entirely so users go through your identity provider (or Quick Connect) — for OIDC-only deployments. OIDC sign-in and Quick Connect are never blocked, and the plugin's own /TwoFactorAuth/Login page always works.
Configure: Settings → Security → Disable password sign-in. On the login page the username/password fields are hidden, leaving a discreet "Sign in with a password instead" link.
Three independently-toggleable escape hatches stop a dead IdP from locking everyone out:
- Administrators can always use a password (default on).
- Allow password sign-in from the LAN (default on) — effectively "disable for remote users only".
- Exempt IP ranges (CIDR) — explicit addresses that may always use a password (e.g. your home IP).
⚠ Keep at least one escape hatch enabled. If you disable them all and your IdP becomes unreachable, use the always-available
/TwoFactorAuth/Loginpage to get back in.
Auto-bans source IPs that hammer the login endpoint — Fail2Ban-style, entirely in-process, no external service.
Configure: Settings → "Brute-Force Protection":
- Failure threshold (default 10)
- Window (default 10 min)
- Ban duration (default 24 h)
- Exempt CIDRs (never banned — e.g. your office IP)
Always exempt: LAN-bypass CIDRs, trusted-proxy CIDRs, anything in the exempt list.
Manage bans: IP Bans tab lists active bans with expiry. Click Unban to clear, or manually ban an IP. Bans persist across restarts via <config>/plugins/configurations/TwoFactorAuth/ip-bans.json.
Behind a reverse proxy you must set Trust X-Forwarded-For + Trusted Proxy CIDRs, or every request looks like it comes from the proxy and all clients share one ban bucket. See First-Time Setup → Reverse proxy.
Flags sign-ins where geographic distance vs. elapsed time exceeds commercial-jet cruise speed (~900 km/h default). London → Tokyo in 30 minutes ≈ Mach 20 → notification fires.
Requires: MaxMind GeoLite2-City.mmdb. Free signup → download the City DB → drop it in /config/geoip/ → paste the path in Settings → Impossible-Travel Detection.
Fires through the same notification channels (ntfy, Gotify, webhook, admin emails) with distance, duration, inferred speed, and country hop. Off by default.
Pin a user account to specific CIDRs. Empty = no restriction (default). Useful for admin accounts where lateral exposure hurts most.
- User self-service: Setup page → IP Allowlist card → one CIDR per line → Save.
-
Admin, per user:
PUT /TwoFactorAuth/IpAllowlist/User/{userId}(full UI not wired yet; edit the user JSON or use the API).
⚠ Self-lockout risk: a typo'd CIDR can lock you out. Recover by editing
/config/plugins/configurations/TwoFactorAuth/users/<your-guid>.jsonand clearingIpAllowlistCidrs.
Re-prompts the admin for a fresh 2FA challenge before sensitive operations — defends against a hijacked or unattended session.
Configure: Settings → Hardening → Step-up level:
| Level | What re-prompts |
|---|---|
Off |
Nothing (default — opt in deliberately). |
Destructive |
Deleting users, wiping 2FA state, rebuilding the audit chain, removing OIDC providers. |
AllConfigChanges |
All of Destructive, plus toggling settings and editing SMTP / push / brute-force / impossible-travel config. |
Everything |
All of AllConfigChanges, plus viewing the audit log, listing IP bans, exporting config. |
Step-up tokens are single-use; you re-prompt next time. RequireTwoFactorToDisable additionally re-prompts before a user can disable their own 2FA.
Closes the stolen-session takeover path: before this, a hijacked session could silently enroll the attacker's own authenticator. Now a proof-of-factor is required before adding/replacing TOTP, recovery codes, an app password, or a passkey, or toggling email OTP.
Setting: Settings → Hardening → Hardened security for users (factor changes) — tri-state:
- Off — change factors without a current code (legacy).
- User choice — per-user opt-in toggle on the Setup page.
- Forced (default) — everyone must submit a current factor before changing any.
Proof of factor accepts any of: a current TOTP code, an unused recovery code, a passkey assertion, an emailed 8-digit step-up code, or an OIDC re-auth. Step-up tokens are single-use, 60-second TTL, bound to the requesting user.
Lets a user mark a specific trusted browser/paired device as "trusted forever" instead of 30 days.
- Admin gate (default off): Settings → Hardening → AllowIndefiniteTrust. When off, the user-side toggle is hidden entirely.
- User opt-in: Setup → Trusted/Paired Devices card → toggle Indefinite trust per device.
⚠ An indefinite-trust device is your weakest link — if it's stolen, that browser stays signed in until you revoke it. Don't enable on shared/borrowed machines.
Getting started
Features
Reference
Help