Password Reset Flow
The two-page forgot/reset flow: request a link, then set a new password from the emailed token.
Live preview
ForgotPasswordPage.svelte
Forgot password
Enter your email address and we will send you a link to reset your password.
Back to sign in// 1. src/routes/api/auth/forgot-password/+server.ts — request a reset link
import { createForgotPasswordHandler } from '@urbicon-ui/auth/server';
import { authDeps } from '$lib/server/auth-setup';
export const { POST } = createForgotPasswordHandler(authDeps);
// 2. Observe delivery failures. forgot-password is fire-and-forget (so response
// time can't reveal whether the account exists), so a broken mail transport
// can't surface as an HTTP error — wire the hook in auth-setup.ts:
// hooks: { onPasswordResetFailed: (email, err) => reportError(err) }
// 3. src/routes/auth/forgot-password/+page.svelte
<script lang="ts">
import { ForgotPasswordPage } from '@urbicon-ui/auth';
import { en } from '@urbicon-ui/auth/i18n/en';
</script>
<ForgotPasswordPage t={en} />ResetPasswordPage.svelte
?token=, and the page sets the new password against it.// 1. src/routes/api/auth/reset-password/+server.ts — consume the token
import { createResetPasswordHandler } from '@urbicon-ui/auth/server';
import { authDeps } from '$lib/server/auth-setup';
export const { POST } = createResetPasswordHandler(authDeps);
// 2. src/routes/auth/reset-password/+page.svelte — the token arrives as ?token=...
<script lang="ts">
import { ResetPasswordPage } from '@urbicon-ui/auth';
import { en } from '@urbicon-ui/auth/i18n/en';
import { page } from '$app/state';
const token = page.url.searchParams.get('token') ?? '';
</script>
<!-- On success the page shows a confirmation + a link to loginUrl (default /auth/login). -->
<ResetPasswordPage t={en} {token} />Three decisions
Step two has no demo
The reset page's one meaningful input is the token step one mails out. The docs site has
no mailbox to read, and a made-up token would only demonstrate the failure path, so ResetPasswordPage ships here as code. In your app
the emailed link carries ?token=, and the route
above hands it to the page.
The confirmation never says whether the account exists
createForgotPasswordHandler answers with the same
confirmation for known and unknown addresses, and the mail leaves fire-and-forget,
detached from the response, so neither the message nor the response time reveals whether
an account exists. The cost: a broken mail transport cannot surface as an HTTP error,
which is what the onPasswordResetFailed hook in the code
is for.
A used link is dead, and so are the sessions
consumeResetToken claims the token atomically, so a
link spends once even when submitted twice. A successful reset then bumps the user's tokenVersion and revokes every refresh token: access
cookies issued before the reset fail their next check, and a stolen pre-reset refresh cookie
cannot mint new ones.