Password Reset
The password reset flow allows users to change their password when they have forgotten it. It follows a three-step magic link pattern: the user requests a reset, clicks the link in their email, and submits a new password along with the 7-digit verification code.
All three routes are registered by magicLinksRouter and require no custom server code.
Route registration
Call magicLinksRouter with your router or app instance during startup.
import { createRouter } from 'h3'
import { magicLinksRouter } from 'auth-h3client/v1'
const router = createRouter()
magicLinksRouter(router, 'api')
import { H3 } from 'h3'
import { magicLinksRouter } from 'auth-h3client/v2'
const app = new H3()
magicLinksRouter(app, 'api')
When using the Nuxt module, all magic link routes are registered automatically by defineAuthConfiguration in your Nitro plugin. See the Nuxt Module setup for details.
With the 'api' prefix, the following routes are registered:
| Method | Path | Handler | Purpose |
|---|---|---|---|
| POST | /api/auth/password-reset | restartPasswordHandler | Sends the reset email |
| GET | /api/auth/reset-password | verifyTempPasswordLink | Validates the magic link |
| POST | /api/auth/reset-password | sendNewPasswordHandler | Submits the new password with the verification code |
Step 1: request a reset
The user submits their email address to POST /api/auth/password-reset. The route applies CSRF verification, Content-Type: application/json validation, and a 1 KB body limit before the handler runs.
The handler proxies the email to the IAM /auth/forgot-password endpoint. The IAM service:
- Looks up the user by email.
- Generates a magic link JWT with
purpose: "PASSWORD_RESET"and a 20-minute TTL. - Generates a 7-digit verification code.
- Sends both to the user's email address.
- Returns a generic success message regardless of whether the email exists, to prevent email enumeration.
The gateway forwards the response to the client. The frontend should show a "check your email" message.
Error responses
| IAM status | Gateway response |
|---|---|
| 200 | 200 with success message |
| 429 | 429 with Retry-After header |
| 500 | 500 |
Step 2: verify the link
When the user clicks the magic link in their email, the bounce route redirects them to your frontend verification page with query parameters (token, random, reason, visitor). The frontend code then needs to detect that the reason is "PASSWORD_RESET" and sends a GET request to /api/auth/reset-password with those parameters, and all cookies identifiers.
The handler:
- Sets
Cache-Control: no-store. - Validates the
canary_idcookie andtokenquery parameter. - Proxies the request to the IAM service to verify the link signature and expiry.
On success, the handler returns the verification result from the IAM service:
{
"ok": true,
"date": "2026-04-12T10:00:00.000Z",
"data": {
"reason": "PASSWORD_RESET",
"link": "Password Reset"
}
}
If the link is invalid or expired, the handler returns a 404 error response.
The useMagicLink composable takes care of sending the GET request with the right parameters.
Step 3: submit the new password
The user enters a new password and the 7-digit code from their email. The frontend submits both to POST /api/auth/reset-password with the magic link query parameters.
The route middleware stack:
- Link verification: re-validates the magic link parameters.
- CSRF verification: validates the
X-CSRF-Tokenheader. - Content-Type: requires
application/json. - Body limit: 1 KB maximum.
The handler proxies the new password and verification code to the IAM service, which:
- Validates the magic link JWT and ensures
purposeisPASSWORD_RESET. - Verifies the 7-digit code.
- Checks the new password against the Have I Been Pwned API.
- Hashes the new password with Argon2 and updates the database.
- Sends a security notification email.
- Returns success.
The password must meet the same policy enforced by the IAM service: at least 12 characters with one uppercase letter, one lowercase letter, one digit, and one special character.
Error responses
| IAM status | Gateway response | Meaning |
|---|---|---|
| 200 | 200 | Password updated |
| 400 | 400 | Invalid code, weak password, or validation error |
| 404 | 404 | Invalid or expired link |
| 429 | 429 with Retry-After | Rate limited |
| 500 | 500 | Server error |
Client-side integration
The useMagicLink composable handles routing automatically. When the reason query parameter is PASSWORD_RESET, it sends the GET request to /api/auth/reset-password and returns the verified data.
Your verification page checks the returned reason and renders the password reset form:
<script setup lang="ts">
const data = await useMagicLink()
// data.reason === 'PASSWORD_RESET'
// Render password reset form with code input
</script>
The form submits both the new password and the 7-digit code to POST /api/auth/reset-password, passing the original query parameters (token, random, reason, visitor) as query string values.
See Client-Side MFA for the full page implementation pattern.