Password Strength
Show a strength hint in copied Zaidan password forms, and surface breach rejections on the field itself.
Every form that sets a new password renders a four-segment strength meter under the field: sign-up, reset password, change password, and the OTP and phone-number reset variants. The score is computed in the browser as the user types.
Zaidan copies <PasswordStrengthMeter /> into your app alongside those forms, so you own the markup and can restyle the bars or drop the label entirely.
The meter is a hint, not a security control. It never blocks submission and it never reaches your server. Your Better Auth password rules stay the only thing that decides what is acceptable.
Turning it off
The meter is on by default. Switch it off through the emailAndPassword config:
<AuthProvider
authClient={authClient}
navigate={navigate}
emailAndPassword={{ strengthMeter: false }}
>
{children}
</AuthProvider>How the score works
evaluatePasswordStrength scores length first, then character variety, then marks the password down for patterns that read as strong but are not:
- Length at or above
minPasswordLength, then again at+4, then again at 16 characters. - Three or more of lowercase, uppercase, digits, and symbols. Using all four scores again.
- A password built from one or two distinct characters loses two points.
- A run of four or more characters from the alphabet, the digits, or the top keyboard row loses one point.
abcd,4321, andqwerall count, in either direction.
Anything shorter than minPasswordLength is capped at Weak, so the meter never disagrees with the rule the form itself enforces.
You can call the same function directly if you need the score somewhere else:
import { evaluatePasswordStrength } from "@better-auth-ui/core"
const { score, level } = evaluatePasswordStrength(password, { minLength: 8 })
// score: 0 | 1 | 2 | 3 | 4
// level: "empty" | "weak" | "fair" | "good" | "strong"Breached passwords
Better Auth's haveIBeenPwned plugin rejects passwords that appear in a known breach corpus. Add it on the server:
import { betterAuth } from "better-auth"
import { haveIBeenPwned } from "better-auth/plugins"
export const auth = betterAuth({
// ...
plugins: [
haveIBeenPwned()
]
})No UI plugin is needed. The rejection arrives as a PASSWORD_COMPROMISED error, and Better Auth UI renders it against the password field rather than as a toast, because it is something the user can fix right there. <ErrorToaster /> skips the code for the same reason.
Reword it through localization:
<AuthProvider
authClient={authClient}
navigate={navigate}
localization={{
auth: {
passwordCompromised: "That password has leaked before. Choose another."
}
}}
>
{children}
</AuthProvider>To detect the same rejection in your own code, use the exported guard:
import { isPasswordCompromisedError } from "@better-auth-ui/core"Last updated on