Server validation
Run the same policy in Server Actions and API routes with @virastack/password/server.
Server entry
The browser can be skipped, so the server decides. validatePassword uses the same engine as the form; the same input gives the same issues on both sides. Use it for sign-up, password reset and password change.
import { validatePassword } from "@virastack/password/server"
const result = validatePassword(formData.get("password"), policy, {
forbidden: [formData.get("email")],
confirm: formData.get("confirm"), // adds password.mismatch when it differs
})
// result.valid, result.issues, result.score, result.password (NFKC)Anything that isn't a string (a missing field, a File) counts as an empty password.
Server Action
// app/signup/actions.ts
"use server";
import { validatePassword } from "@virastack/password/server";
import type { PasswordIssue } from "@virastack/password/strength";
import { policy } from "@/lib/password-policy";
export type SignupState = { ok: boolean; issues: PasswordIssue[] } | null;
export async function signup(_state: SignupState, formData: FormData): Promise<SignupState> {
// The browser can be skipped; the server checks again with the same policy.
const result = validatePassword(formData.get("password"), policy, {
forbidden: [formData.get("email")],
confirm: formData.get("confirm"),
});
if (!result.valid) return { ok: false, issues: result.issues };
// await createUser(email, await hash(result.password)) — result.password is NFKC-normalized
return { ok: true, issues: [] };
}// app/api/password/route.ts
import { validatePassword } from "@virastack/password/server"
import { policy } from "@/lib/password-policy"
export async function POST(request: Request) {
const { password, email } = await request.json()
const result = validatePassword(password, policy, { forbidden: [email] })
return Response.json({ valid: result.valid, issues: result.issues })
}What to hash
Hash result.password: it is NFKC-normalized, so the same password typed on another keyboard still verifies at login. Normalize the login input the same way with normalizePassword.
SSR & Next.js
- Only
@virastack/passwordhas"use client". Server Components, Server Actions and route handlers import/serveror/strength. - Keep the policy in a shared file without
"use client". - The hook renders the same markup on the server and the client (ids come from
useId), so there is no hydration mismatch. - Tested with Next.js in
next devandnext start.
