methods/email-password/server
The emailPassword method: accounts have an email address
and a password, and the email must be validated before the first sign-in.
Type Aliases
ChangePasswordResult
type ChangePasswordResult = Infer<typeof changePasswordResult>;
The result of changePassword.
INVALID_CREDENTIALS and RATE_LIMITED are about the current password.
The other errors are about the new password.
CheckPasswordRecoveryResult
type CheckPasswordRecoveryResult = Infer<typeof checkPasswordRecoveryResult>;
The result of checkPasswordRecovery.
CompleteChangeEmailResult
type CompleteChangeEmailResult = Infer<typeof completeChangeEmailResult>;
The result of completeChangeEmail.
CompletePasswordRecoveryResult
type CompletePasswordRecoveryResult = Infer<typeof completePasswordRecoveryResult>;
The result of completePasswordRecovery: the minted session tokens, or an error.
CompleteSignUpResult
type CompleteSignUpResult = Infer<typeof completeSignUpResult>;
The result of completeSignUpWithEmailPassword: the minted session tokens, or an error.
EmailPasswordOptions
type EmailPasswordOptions = object;
Options for setupEmailPassword.
Properties
| Property | Type | Description |
|---|---|---|
component | ComponentApi | The mounted email component (components.authEmail). The method uses it to track verified emails and to run the challenges. |
emailSender | EmailSenderOptions | How the method sends emails. |
passwordComponent | PasswordComponentApi | The mounted password component (components.authPassword). The method drives its setPassword / verifyPassword mutations. |
urls | EmailPasswordUrls | The landing pages the emailed links point at. |
EmailPasswordProfile
type EmailPasswordProfile = Record<string, never>;
EmailPasswordUrls
type EmailPasswordUrls = object;
The landing pages the emailed links point at. The link's code query
parameter will be appended to these URLs.
Properties
EmailSenderOptions
type EmailSenderOptions = object;
How the method sends emails: challenge links (through the email component) and security notifications (directly).
Only Resend is supported for now, through the @convex-dev/resend
component. Mount that component in the app and pass its sendEmail
reference and your Resend API key here.
TODO: support other email providers. TODO: offer a first-party zero-configuration email service. TODO: let applications customize the email templates.
Properties
SignInResult
type SignInResult = Infer<typeof signInResult>;
The result of signInWithEmailPassword: the minted session tokens, or an error.
SignUpResult
type SignUpResult = Infer<typeof signUpResult>;
The result of signUpWithEmailPassword. On success, the secret the browser must keep for
completeSignUpWithEmailPassword; the session arrives only after the email is validated.
StartChangeEmailResult
type StartChangeEmailResult = Infer<typeof startChangeEmailResult>;
The result of startChangeEmail.
StartPasswordRecoveryResult
type StartPasswordRecoveryResult = Infer<typeof startPasswordRecoveryResult>;
The result of startPasswordRecovery.
Functions
setupEmailPassword()
function setupEmailPassword<UsersTable>(core, options): object;
A password sign-in method where accounts have a validated email address and a password.
- Sign-up creates the user immediately but without a session; the user
signs in only after they open the challenge link. Wire it up in
convex/auth.ts:
import { components, internal } from "./_generated/api";
import { env } from "./_generated/server";
import { setupConvexAuth } from "@convex-dev/auth/server";
import { setupEmailPassword } from "@convex-dev/auth/methods/email-password/server";
// The frontend origin the emailed links point at.
const SITE_URL = env.SITE_URL ?? "http://localhost:5173";
const auth = setupConvexAuth({ component: components.auth });
export const { signOut, refreshSession, isAuthenticated } = auth;
export const {
signUpWithEmailPassword,
completeSignUpWithEmailPassword,
signInWithEmailPassword,
changePassword,
startPasswordRecovery,
checkPasswordRecovery,
completePasswordRecovery,
startChangeEmail,
completeChangeEmail,
} = setupEmailPassword(auth, {
component: components.authEmail,
passwordComponent: components.authPassword,
emailSender: {
kind: "resend",
sendEmail: components.resend.lib.sendEmail,
apiKey: env.RESEND_API_KEY,
from: "My App <auth@example.com>",
},
urls: {
signUp: `${SITE_URL}/validate-email`,
changeEmail: `${SITE_URL}/confirm-email-change`,
recovery: `${SITE_URL}/reset-password`,
},
}).attachUserCallbacks({
createUser: internal.users.createUserEmailPassword,
});
- Sign-in accepts any verified email of the account.
- Change-password and change-email require the session and the current password (OWASP ASVS v5 6.2.3), and send a security notification to the affected address (ASVS 6.3.7).
- Recovery proves ownership of a verified email through a 10-minute link, then sets the new password and signs the user in.
Account resolution (email → app user id) is owned by the email component;
the password component stores only { userId, passwordHash }.
Type Parameters
| Type Parameter |
|---|
UsersTable extends string |
Parameters
| Parameter | Type |
|---|---|
core | AuthCore<UsersTable> |
options | EmailPasswordOptions |
Returns
attachUserCallbacks()
attachUserCallbacks(__namedParameters): object;
Supply the app's user callbacks (see UserCallbacks for how their args must be declared) and get the functions of this method to export.
Parameters
| Parameter | Type |
|---|---|
__namedParameters | UserCallbacks<"emailPassword", EmailPasswordProfile, UsersTable> |
Returns
changePassword
changePassword: RegisteredMutation<"public", {
currentPassword: string;
newPassword: string;
signOutOtherSessions?: boolean;
}, Promise<
| {
success: true;
}
| {
success: false;
userError: | {
error: "PASSWORD_TOO_SHORT";
minimumLength: number;
}
| {
error: "PASSWORD_TOO_LONG";
maximumLength: number;
}
| {
error: "PASSWORD_HAS_SURROUNDING_WHITESPACE";
}
| {
error: "PASSWORD_TOO_COMMON";
}
| {
error: "NOT_LOGGED_IN";
}
| {
error: "INVALID_CREDENTIALS";
}
| {
error: "RATE_LIMITED";
retryAfterMs: number;
};
}>>;
Change the signed-in user's password. Requires the session and the
current password (OWASP ASVS v5 6.2.3), and notifies the primary
email address (ASVS 6.3.7). Signs out the user's other sessions
if signOutOtherSessions is true (ASVS v5 7.4.3).
checkPasswordRecovery
checkPasswordRecovery: RegisteredQuery<"public", {
browserSecret: string;
emailCode: string;
}, Promise<
| {
success: true;
}
| {
success: false;
userError: | {
error: "INVALID_CHALLENGE";
}
| {
error: "INCORRECT_CODE";
};
}>>;
Check a password recovery link before the user types a new
password: tell whether completePasswordRecovery would accept the
link code + browser secret. It does not consume the link.
completeChangeEmail
completeChangeEmail: RegisteredMutation<"public", {
browserSecret: string;
emailCode: string;
}, Promise<
| {
success: true;
}
| {
success: false;
userError: | {
error: "EMAIL_TAKEN";
}
| {
error: "INVALID_CHALLENGE";
}
| {
error: "INCORRECT_CODE";
}
| {
error: "NOT_LOGGED_IN";
}
| {
error: "WRONG_USER";
};
}>>;
Complete an email change. The old primary address is removed and replaced with the new address. This also notifies old address (OWASP ASVS 6.3.7).
No session is minted — the user already has one.
completePasswordRecovery
completePasswordRecovery: RegisteredMutation<"public", {
browserSecret: string;
emailCode: string;
newPassword: string;
}, Promise<
| {
status: "complete";
tokens: {
accessToken: string;
refreshToken: string;
refreshTokenExpiresAt: number;
userId: string;
};
}
| {
status: "error";
userError: | {
error: "PASSWORD_TOO_SHORT";
minimumLength: number;
}
| {
error: "PASSWORD_TOO_LONG";
maximumLength: number;
}
| {
error: "PASSWORD_HAS_SURROUNDING_WHITESPACE";
}
| {
error: "PASSWORD_TOO_COMMON";
}
| {
error: "INVALID_CHALLENGE";
}
| {
error: "INCORRECT_CODE";
};
}>>;
Complete a password recovery: the link code + browser secret prove ownership of the email, so set the new password, sign out every existing session of the user, and sign the user in, in one transaction. Notifies the primary email (ASVS 6.3.7).
completeSignUpWithEmailPassword
completeSignUpWithEmailPassword: RegisteredMutation<"public", {
browserSecret: string;
emailCode: string;
}, Promise<
| {
status: "complete";
tokens: {
accessToken: string;
refreshToken: string;
refreshTokenExpiresAt: number;
userId: string;
};
}
| {
status: "error";
userError: | {
error: "EMAIL_TAKEN";
}
| {
error: "INVALID_CHALLENGE";
}
| {
error: "INCORRECT_CODE";
};
}>>;
Complete a sign-up: validate the email with the code from the link and the secret from the starting browser, then sign the user in. Validation and sign-in happen in one transaction.
signInWithEmailPassword
signInWithEmailPassword: RegisteredMutation<"public", {
email: string;
password: string;
}, Promise<
| {
status: "complete";
tokens: {
accessToken: string;
refreshToken: string;
refreshTokenExpiresAt: number;
userId: string;
};
}
| {
status: "error";
userError: | {
error: "PASSWORD_TOO_SHORT";
minimumLength: number;
}
| {
error: "PASSWORD_TOO_LONG";
maximumLength: number;
}
| {
error: "PASSWORD_HAS_SURROUNDING_WHITESPACE";
}
| {
error: "INVALID_CREDENTIALS";
}
| {
error: "RATE_LIMITED";
retryAfterMs: number;
}
| {
error: "USER_NOT_FOUND";
};
}>>;
Verify an existing account's password and, on success, mint a
session. Any verified email of the account works. Returns
USER_NOT_FOUND when no account has verified the email and
INVALID_CREDENTIALS when the password is wrong. (Address existence
is already observable via sign-up's EMAIL_TAKEN, so distinguishing
them here leaks nothing new.) The lookup of the address has a
per-IP limit, and returns RATE_LIMITED when the client has done
too many lookups.
signUpWithEmailPassword
signUpWithEmailPassword: RegisteredMutation<"public", {
email: string;
password: string;
}, Promise<
| {
browserSecret: string;
success: true;
}
| {
success: false;
userError: | {
error: "PASSWORD_TOO_SHORT";
minimumLength: number;
}
| {
error: "PASSWORD_TOO_LONG";
maximumLength: number;
}
| {
error: "PASSWORD_HAS_SURROUNDING_WHITESPACE";
}
| {
error: "PASSWORD_TOO_COMMON";
}
| {
error: "INVALID_EMAIL";
}
| {
error: "RATE_LIMITED";
retryAfterMs: number;
}
| {
error: "EMAIL_TAKEN";
};
}>>;
Create a new account: the app user, the account and the password are
written immediately, but no session is minted. The user signs in for
the first time through completeSignUpWithEmailPassword, after they open the
challenge link.
Repeated sign-ups with one unvalidated address create separate users; the first completed validation wins, and the others can never sign in.
startChangeEmail
startChangeEmail: RegisteredMutation<"public", {
currentPassword: string;
newEmail: string;
}, Promise<
| {
browserSecret: string;
success: true;
}
| {
success: false;
userError: | {
error: "PASSWORD_TOO_SHORT";
minimumLength: number;
}
| {
error: "PASSWORD_TOO_LONG";
maximumLength: number;
}
| {
error: "PASSWORD_HAS_SURROUNDING_WHITESPACE";
}
| {
error: "INVALID_CREDENTIALS";
}
| {
error: "RATE_LIMITED";
retryAfterMs: number;
}
| {
error: "INVALID_EMAIL";
}
| {
error: "RATE_LIMITED";
retryAfterMs: number;
}
| {
error: "EMAIL_TAKEN";
}
| {
error: "NOT_LOGGED_IN";
};
}>>;
Start changing the signed-in user's primary email address. The new
address replaces the old one: after the change, the account no
longer has the old address. Requires the session and the current
password (OWASP ASVS v5 6.2.3). Sends a challenge link to the new
address; the change happens in completeChangeEmail.
startPasswordRecovery
startPasswordRecovery: RegisteredMutation<"public", {
email: string;
}, Promise<
| {
browserSecret: string;
sentTo: string;
success: true;
}
| {
success: false;
userError: | {
error: "INVALID_EMAIL";
}
| {
error: "RATE_LIMITED";
retryAfterMs: number;
}
| {
error: "EMAIL_NOT_FOUND";
};
}>>;
Start a password recovery: send a reset link (valid 10 minutes) to a verified email address. Any verified address of the account works, not only the primary one.
EMAIL_NOT_FOUND is surfaced to the caller. This reveals whether an
address has an account, which sign-up's EMAIL_TAKEN reveals
anyway; the recipe accepts that trade-off for a clearer flow. The
lookup has a per-IP limit, so a client cannot probe many addresses.