---
title: "Security — Auth for Laravel | Roundly"
description: "Guard isolation, enumeration safety, single-use secrets, invalidation and recovery safeguards — and the known limits."
url: https://roundly-consulting.com/open-source/docs/auth-for-laravel/security
language: en
---

[All packages](https://roundly-consulting.com/open-source.md)

[Auth for Laravel](https://roundly-consulting.com/open-source/docs/auth-for-laravel.md)

# Security

Each property below is covered by a test in the package suite.

- Enumeration — guest endpoints answer known, unknown, disabled and unverified accounts identically (status and body); mail goes only to real, active accounts, after the response. A password check always runs, against a dummy hash of the same cost for unknown accounts, and a locked account’s password login answers like a wrong password (same status, body, hash cost and throttle count); a correct password behind any uniform invalid\_credentials keeps its throttle attempt and leaves the lockout counter alone, like a wrong one. An email change to a taken address answers and notifies the old address exactly like one to a free address. Only completed logins make a device known — a reset or sign-in link requested from a device does not, nor does a re-authentication made with a (possibly stolen) access token.
- Guard isolation — a token, link, code, invitation, challenge or passkey of one guard never works on another.
- Single use — every claim is a conditional update; challenges advance with an optimistic version check and snapshot the account’s token version.
- Invalidation — tv++ kills every outstanding access token; session families are revoked (one minted under an older tv — a login racing the invalidation — at its first refresh, so no session outlives a reset, a disable or a logout everywhere), and a refresh retires the session’s previous access token, so revoking a session kills every token it minted; pending sign-in, reset, re-authentication and email-change links die with them; the jti denylist is belt-and-braces.
- Throttling — attempts are counted atomically before a password or code is checked — the login, email and re-authentication throttles, email codes and every challenge’s own max\_attempts — so a burst of concurrent guesses cannot all pass the limit. The challenge cap is per challenge: with two-factor’s per-account limiter off (two-factor.attempts = null) it is the only bound and each new login opens a fresh budget, so keep the limiter on or throttle the challenge routes yourself (authentication:check warns).
- Risk step-up — require\_second\_factor applies to every login method, also when after\_email\_login = false; an account with no factor to step up with is denied. A passkey login that counts as MFA (passkeys.satisfies\_mfa) already is the step-up.
- Re-authentication — accounts with a second factor must re-authenticate with it; a password or email-code proof, or a login that skipped the factor, never satisfies a gate for them — also when the factor was enrolled after that proof. reauthentication.methods is checked against the factor that actually matched.
- Secrets at rest — HMAC-SHA-256 with a key derived from APP\_KEY (or hash\_key); plaintext exists only in the response or the notification.
- Account recovery — last-credential protection, forced enrolment only for verified addresses, email changes verified and announced twice, a reset never bypasses an enrolled second factor and registration never skips required two-factor enrolment.
- Transport — Cache-Control: no-store on every package response, encrypted queued notifications and secrets in URL fragments.
- Stack traces — the Authentication facade uses package-toolkit’s RedactsSensitiveArguments, so the #\[SensitiveParameter\] arguments of consumeMagicLink() ($token), verifyEmailOtp() ($code) and refresh() ($refreshToken) show as SensitiveParameterValue even in the facade’s own frame, while every other argument stays visible.

## Known limits

- The denylist lives in cache — a flush revives revoked, unexpired access tokens until they expire. The token version covers invalidations; a short access\_ttl bounds the rest.
- Forced enrolment — a password-only attacker could enrol their own factor under a required mode; mitigated by the verified-address gate and notifications, and a challenge whose enrolment step went stale (the factor was set up meanwhile) is ended.
- Registration with immediate tokens reveals a taken address — use the enumeration-safe modes.
- One passkeys relying party for all guards, and one key type for every guard model.

## Show your open-source love

This package is free and MIT-licensed. If it saves you time, a one-off donation or a Patreon membership keeps it maintained, tested and documented.

[More ways to support, including crypto](https://roundly-consulting.com/support-us.md)

By donating, you agree to our [donation terms](https://roundly-consulting.com/donation-terms.md).

[Support our open source work (opens in a new tab)](https://donate.stripe.com/dRmeVe8FX5PF1Qd9pXcEw00) [Join us on Patreon (opens in a new tab)](https://www.patreon.com/cw/roundly)

## Want this built into your product?

We integrate our packages into custom Laravel and AI builds. Tell us what you're working on and we'll reply within 48 hours.

[Get a quote in 48 hours](https://roundly-consulting.com/contact.md) [Browse all packages](https://roundly-consulting.com/open-source/docs/auth-for-laravel.md)
