auth/invalid-password: App Allowed Shorter Passwords
※本記事にはアフィリエイトリンクを含む場合があります。内容は広告の有無に影響されません。
結論
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.
よくある質問
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.
この記事の根拠
- TypeScriptファイル 1〜14行目コミット fb01ce6
- TypeScriptファイル 86〜97行目コミット fb01ce6
- TypeScriptファイル 299〜334行目コミット fb01ce6
- ドキュメントファイル 40〜40行目コミット 3086d02
本文の主張は、上の記録に書かれていることだけです。運用しているリポジトリは非公開のため リンクは張れませんが、どのファイルの何行目を、どのコミット時点で見て書いたかは 記事ごとに残しています。推測で書いた箇所はありません。