auth/invalid-password: App Allowed Shorter Passwords
This article may contain affiliate links. Its content is not affected by advertising.
In short
A password passing app validation at 4-5 chars is still rejected as auth/invalid-password by Identity Platform's 6-char floor — uncaught, it reached the error boundary.
The short version
The app’s own password validation allowed 4 characters or more, but the identity provider actually holding the password — Identity Platform’s email/password auth — rejects anything under 6 characters with auth/invalid-password. When an admin set a 4-5 character shared password for a school, the app’s validation let it through, and the IdP’s rejection wasn’t caught anywhere, so it propagated straight up to a Next.js error boundary instead of surfacing as a form error.
Symptom
- An admin entering a 4-5 character password on the “shared teacher password” screen sees the save fail
- The app’s own form validation passes at this point — its minimum was 4 characters, so nothing flags in the UI
- Instead of a normal validation message, the failure shows up as a Next.js error boundary (digest
912693075). It reads as an unexpected exception, and the screen gives no hint about what actually went wrong
Root cause
There were two independent layers at fault; fixing only one leaves the door open for the same failure to happen again.
Layer 1: the app’s floor was looser than the IdP’s
The app’s policy function, validateTeacherPasswordPolicy, originally set MIN_TEACHER_PASSWORD_LENGTH = 4. That number came from prioritizing low operational friction for schools — 4 characters or more was accepted by design.
But this shared password is the password on an Identity Platform email/password account — there’s no separate storage for it in the app. Identity Platform’s password policy can only be configured with a minimum length between 6 and 30 characters; anything under 6 is rejected outright, by design, on the IdP side. So a 4-5 character password that passed the app’s check would still get rejected as auth/invalid-password the moment provisionSharedTeacherAccount called IdP’s createUser/updateUser internally. The app’s validation and the external system that actually stores the value were checking against two different thresholds.
Layer 2: the IdP’s rejection wasn’t caught
setSchoolTeacherPasswordAction originally just awaited the result of provisionSharedTeacherAccount with no dedicated branch for auth/invalid-password. Whatever IdP threw propagated straight past the Server Action and into Next.js’s error boundary. From the admin’s point of view, the form looked fine right up until “something went wrong” — with no indication of what.
The fix
- Raised
MIN_TEACHER_PASSWORD_LENGTHfrom 4 to 6, matching IdP’s floor. Passing the app’s validation now guarantees the password won’t be rejected by IdP on length alone - That alone doesn’t prevent the two thresholds from drifting apart again later, so we added
isPasswordRejectedError(). It classifiesauth/invalid-password(under 6 characters, or non-string input) andauth/weak-password(any violation of the IdP project’s password policy) as “the user can fix this by entering something different,” andsetSchoolTeacherPasswordActionnow catches these and turns them into a normalinvalid()form validation error instead of letting them reach the error boundary - Permission errors and unclassified infrastructure exceptions deliberately stay out of that branch and are re-thrown as-is — treating every unknown error as “the user typed something wrong” would quietly destroy observability for the errors that actually matter
- Updated the “4 characters” reference left over in the ADR and the action’s JSDoc to say 6
Takeaway
Looking only at the app’s own validation and concluding “the input constraints are covered” misses whatever constraint actually lives on the system that stores the value. Here, the password was ultimately held by Identity Platform, but the app’s floor had been set by a completely different consideration — operational load on schools. Validation on the app side needs to be aligned with the constraints of whatever system finally persists the value.
The second lesson: aligning the two thresholds once isn’t enough on its own, because thresholds can drift apart again later — an IdP-side policy change, a relaxed app-side requirement, or any number of other reasons. A separate layer that catches rejections from the external system and turns them into a message the user can act on means that even if validation drifts or has a gap, you still avoid the failure mode of an exception reaching the error boundary. Keeping validation aligned and keeping a defense-in-depth catch layer are two different safeguards, and both are worth having.
Frequently asked questions
Q1Why did saving fail even though the form's own validation passed?
The password isn't stored by the app's database — Identity Platform holds it. The app's floor was 4 characters, IdP's is 6, so a 4-5 character password passed the form check and was then rejected by createUser/updateUser, uncaught.
Q2How low can Identity Platform's minimum password length go?
6 characters is the floor. The password policy's configurable minimum length range is 6-30 characters; you cannot set it below 6.
Q3Why did this surface as an error boundary instead of a normal validation message?
The password-setting action just awaited the Identity Platform call without a dedicated catch for auth/invalid-password. The exception propagated straight past the Server Action boundary and up to Next.js's error boundary.
Q4Does aligning the app's minimum to 6 characters fully prevent this?
Not by itself — the two limits can drift apart again later. We added a catch layer that converts IdP rejections into user-facing validation errors, while still re-throwing permissions or infrastructure errors instead of swallowing them.
What this article is based on
- TypeScript file lines 1-14commit fb01ce6
- TypeScript file lines 86-97commit fb01ce6
- TypeScript file lines 299-334commit fb01ce6
- Markdown file lines 40-40commit 3086d02
Every claim in this article comes from the records above. The repositories we operate are private so we cannot link to them, but which file, which lines, and at which commit we read them is recorded for every article. Nothing here is written from guesswork.