Skip to Content

Password reset

Password recovery is three steps: request a recovery email, enter the code from the email, change the password. Under the hood, verifyOtp (with type: 'recovery') and updatePassword are reused, so there’s no separate “recovery mode” in AuthClient — after verifyOtp the user is in a regular signed-in session, just with an access token from a recovery grant.

await auth.requestPasswordReset({ email }); // user enters the code from the email: await auth.verifyOtp({ email, token: code, type: 'recovery' }); // now we have a valid session — change the password: await auth.updatePassword({ password: newPassword });

requestPasswordReset

Triggers a recovery email. Like sendOtp, always returns ok (anti-enumeration).

auth.requestPasswordReset(input: { email: string; }): Promise<void>

What’s in the email — a code or a link? The SDK expects a 6-digit code. The platform’s default email template sends a code; if a link arrives instead (legacy templates), extract token=... from it in your UI before calling verifyOtp.

updatePassword

Changes the password of the current signed-in session. Works after verifyOtp({ type: 'recovery' }) (a recovery session) and from a regular signed-in session (the “change password in settings” feature).

auth.updatePassword(input: { password: string }): Promise<void>

When there’s no session

The method throws PaywallError('not_authenticated') before the network request. That means:

  • the user didn’t pass verifyOtp (or did, but AuthClient hadn’t stored the session — no such races exist, the method awaits);
  • the session expired and the refresh token is invalid.

In real UI this is a rare path: there are seconds between “enter code” and “enter new password”.

When the password is weak

The platform’s auth backend validates length (minimum 6) and applies any additional policy rules. A weak password yields PaywallError with code: 'weak_password', status: 400.

try { await auth.updatePassword({ password: newPassword }); } catch (e) { if (e instanceof PaywallError && e.code === 'weak_password') { showError('Password is too weak'); return; } throw e; }

After updatePassword the session is NOT recreated. The access token stays the one issued after verifyOtp/regular signin. This is deliberate: there’s no need to sign out a user who just changed their password. If you want “sign out from all devices after a password change” — that’s a separate feature on the backend; the SDK doesn’t ship it.

Full “Forgot password” flow

Step 1: “Forgot password?” → request email

UI asks for the email and calls requestPasswordReset:

await auth.requestPasswordReset({ email }); showStep('enter-recovery-code');

Step 2: enter the code

try { await auth.verifyOtp({ email, token: code, type: 'recovery' }); showStep('enter-new-password'); } catch (e) { if (e instanceof PaywallError && e.code === 'invalid_otp') { showError('Wrong or expired code'); return; } throw e; }

Step 3: enter the new password

try { await auth.updatePassword({ password: newPassword }); showStep('done'); } catch (e) { if (e instanceof PaywallError && e.code === 'weak_password') { showError('Password is too weak — add more characters'); return; } throw e; }

Step 4: done

After updatePassword the user is already signed in (AuthClient.getCachedUser() returns them) — redirect straight into the app, no need to force a re-login.

Alt-flow: “change password in settings”

If the user is already signed in and just wants to change their password — only updatePassword is needed. No recovery email.

// on the "Settings → Security" screen: async function changePassword(currentPassword: string, newPassword: string) { // (optional) re-verify the current password via signInWithEmail const email = auth.getCachedUser()?.email; if (!email) throw new Error('not authenticated'); await auth.signInWithEmail({ email, password: currentPassword }); // if signIn fails — current password is wrong; surface the error await auth.updatePassword({ password: newPassword }); }

Re-verifying via signInWithEmail is needed because updatePassword itself doesn’t require the current password — the access token is enough. Re-auth protects against “user left a laptop unlocked, someone changed the password”.

Security

  • Anti-enumeration in UI. Show neutral messages on every step. After requestPasswordReset — “If that email exists, we sent a message”, not “Email sent” (the latter reveals registration).
  • Recovery token is short-lived. The recovery session has the same lifetime as a regular access token (1 hour by default). If the user leaves the “enter new password” screen for half an hour — updatePassword still works; for an hour and a half — they have to start over.
  • Hold the email in state between steps. Don’t make the user type it again — that’s friction and a typo opportunity.

Next steps

  • Email OTPverifyOtp and the general code mechanics.
  • Session managementsignOut, onAuthChange, what the user sees after a password change.