Forgot Password flow
There are two steps in the forgot password flow:
- Sending the password reset email to a user's email ID
- Once the user clicks the link, asking them for and updating their password.
#
Step 1: Sending the password reset email- Web
- Mobile
You should create a new screen on your website that asks the user to enter their email to which an email will be sent. This screen should ideally be linked to from the sign in form.
Once the user has enters their email, you can use the following function to send a reset password email to that user:
- Via NPM
- Via Script Tag
import { sendPasswordResetEmail } from "supertokens-web-js/recipe/emailpassword";
async function sendEmailClicked(email: string) { try { let response = await sendPasswordResetEmail({ formFields: [{ id: "email", value: email }] });
if (response.status === "FIELD_ERROR") { // one of the input formFields failed validaiton response.formFields.forEach(formField => { if (formField.id === "email") { // Email validation failed (for example incorrect email syntax). window.alert(formField.error) } }) } else { // reset password email sent. window.alert("Please check your email for the password reset link") } } catch (err: any) { if (err.isSuperTokensGeneralError === true) { // this may be a custom error message sent from the API by you. window.alert(err.message); } else { window.alert("Oops! Something went wrong."); } }}
async function signUpClicked(email: string) { try { let response = await supertokensEmailPassword.sendPasswordResetEmail({ formFields: [{ id: "email", value: email }] });
if (response.status === "FIELD_ERROR") { // one of the input formFields failed validaiton response.formFields.forEach(formField => { if (formField.id === "email") { // Email validation failed (for example incorrect email syntax). window.alert(formField.error) } }) } else { // reset password email sent. window.alert("Please check your email for the password reset link") } } catch (err: any) { if (err.isSuperTokensGeneralError === true) { // this may be a custom error message sent from the API by you. window.alert(err.message); } else { window.alert("Oops! Something went wrong."); } }}
You should create a new screen on your app that asks the user to enter their email to which an email will be sent. This screen should ideally be linked to from the sign in form.
Once the user has enters their email, you can call the following API to send a reset password email to that user:
curl --location --request POST '<YOUR_API_DOMAIN>/auth/user/password/reset/token' \--header 'rid: emailpassword' \--header 'Content-Type: application/json' \--data-raw '{ "formFields": [{ "id": "email", "value": "john@example.com" }]}'
The response body from the API call has a status
property in it:
status: "OK"
: If the user exists in SuperTokens, an email has been sent to them.status: "FIELD_ERROR"
: The input email failed the backend validation logic (i.e. the email is not a valid email from a syntax point of view). You want to show the user an error next to the input form field.status: "GENERAL_ERROR"
: This is only possible if you have overriden the backend API to send back a custom error message which should be displayed on the frontend.
important
If the input email ID does not belong to a user who signed up previously, SuperTokens will not send them an email. However, the frontend will still receive an "OK"
status back.
#
Changing the password reset linkBy default, the password reset link will point to the websiteDomain
that is configured on the backend, on the /auth/reset-password
route (where /auth
is the default value of websiteBasePath
).
If you want to change this to a different path, a different domain, or deep link it to your mobile / desktop app, then you can do so on the backend in the following way:
- NodeJS
- GoLang
- Python
import SuperTokens from "supertokens-node";import EmailPassword from "supertokens-node/recipe/emailpassword";import { SMTPService } from "supertokens-node/recipe/emailpassword/emaildelivery";
SuperTokens.init({ supertokens: { connectionURI: "...", }, appInfo: { apiDomain: "...", appName: "...", websiteDomain: "..." }, recipeList: [ EmailPassword.init({ emailDelivery: { override: (originalImplementation) => { return { ...originalImplementation, sendEmail: async function (input) { if (input.type === "PASSWORD_RESET") { // You can change the path, domain of the reset password link, // or even deep link it to your mobile app return originalImplementation.sendEmail({ ...input, passwordResetLink: input.passwordResetLink.replace( // This is: `${websiteDomain}${websiteBasePath}/reset-password` "http://localhost:3000/auth/reset-password", "http://localhost:3000/your/path" ) }) } return originalImplementation.sendEmail(input); } } } } }) ]});
import ( "strings" "github.com/supertokens/supertokens-golang/ingredients/emaildelivery" "github.com/supertokens/supertokens-golang/recipe/emailpassword" "github.com/supertokens/supertokens-golang/recipe/emailpassword/epmodels" "github.com/supertokens/supertokens-golang/supertokens")
func main() { supertokens.Init(supertokens.TypeInput{ RecipeList: []supertokens.Recipe{ emailpassword.Init(&epmodels.TypeInput{
EmailDelivery: &emaildelivery.TypeInput{ Override: func(originalImplementation emaildelivery.EmailDeliveryInterface) emaildelivery.EmailDeliveryInterface { ogSendEmail := *originalImplementation.SendEmail
(*originalImplementation.SendEmail) = func(input emaildelivery.EmailType, userContext supertokens.UserContext) error { // You can change the path, domain of the reset password link, // or even deep link it to your mobile app // This is: `${websiteDomain}${websiteBasePath}/reset-password` input.PasswordReset.PasswordResetLink = strings.Replace( input.PasswordReset.PasswordResetLink, "http://localhost:3000/auth/reset-password", "http://localhost:3000/your/path", 1, ) return ogSendEmail(input, userContext) } return originalImplementation }, },
}), }, })}
from supertokens_python import init, InputAppInfofrom supertokens_python.recipe.emailpassword.types import EmailDeliveryOverrideInput, EmailTemplateVarsfrom supertokens_python.recipe import emailpasswordfrom typing import Dict, Anyfrom supertokens_python.ingredients.emaildelivery.types import EmailDeliveryConfig
def custom_email_deliver(original_implementation: EmailDeliveryOverrideInput) -> EmailDeliveryOverrideInput: original_send_email = original_implementation.send_email
async def send_email(template_vars: EmailTemplateVars, user_context: Dict[str, Any]) -> None: # You can change the path, domain of the reset password link, # or even deep link it to your mobile app # This is: `${websiteDomain}${websiteBasePath}/reset-password` template_vars.password_reset_link = template_vars.password_reset_link.replace( "http://localhost:3000/auth/reset-password", "http://localhost:3000/your/path") return await original_send_email(template_vars, user_context)
original_implementation.send_email = send_email return original_implementation
init( app_info=InputAppInfo(api_domain="...", app_name="...", website_domain="..."), framework='...', recipe_list=[ emailpassword.init( email_delivery=EmailDeliveryConfig(override=custom_email_deliver) ) ])
#
Step 2: Updating the user's password- Web
- Mobile
The default password reset link is of the form ${websiteDomain}/auth/reset-password?token=...
where /auth
is the default value of websiteBasePath
that is configured on the backend.
Once the user clicks on the reset password link you need to ask them to enter their new password and call the function as shown below to change their password.
- Via NPM
- Via Script Tag
import { submitNewPassword } from "supertokens-web-js/recipe/emailpassword";
async function newPasswordEntered(newPassword: string) { try { let response = await submitNewPassword({ formFields: [{ id: "password", value: newPassword }] });
if (response.status === "FIELD_ERROR") { response.formFields.forEach(formField => { if (formField.id === "password") { // New password did not meet password criteria on the backend. window.alert(formField.error) } }) } else if (response.status === "RESET_PASSWORD_INVALID_TOKEN_ERROR") { // the password reset token in the URL is invalid, expired, or already consumed window.alert("Password reset failed. Please try again") window.location.assign("/auth") // back to the login scree. } else { window.alert("Password reset successful!") window.location.assign("/auth") } } catch (err: any) { if (err.isSuperTokensGeneralError === true) { // this may be a custom error message sent from the API by you. window.alert(err.message); } else { window.alert("Oops! Something went wrong."); } }}
async function newPasswordEntered(newPassword: string) { try { let response = await supertokensEmailPassword.submitNewPassword({ formFields: [{ id: "password", value: newPassword }] });
if (response.status === "FIELD_ERROR") { response.formFields.forEach(formField => { if (formField.id === "password") { // New password did not meet password criteria on the backend. window.alert(formField.error) } }) } else if (response.status === "RESET_PASSWORD_INVALID_TOKEN_ERROR") { // the password reset token in the URL is invalid, expired, or already consumed window.alert("Password reset failed. Please try again") window.location.assign("/auth") // back to the login scree. } else { window.alert("Password reset successful!") window.location.assign("/auth") } } catch (err: any) { if (err.isSuperTokensGeneralError === true) { // this may be a custom error message sent from the API by you. window.alert(err.message); } else { window.alert("Oops! Something went wrong."); } }}
The default password reset link is of the form ${websiteDomain}/auth/reset-password?token=...
where /auth
is the default value of websiteBasePath
that is configured on the backend.
Once the user clicks on the reset password link you need to ask them to enter their new password and call the API as shown below to change their password.
curl --location --request POST '<YOUR_API_DOMAIN>/auth/user/password/reset' \--header 'rid: emailpassword' \--header 'Content-Type: application/json' \--data-raw '{ "method": "token", "formFields": [ { "id": "password", "value": "newPass123" } ], "token": "ZTRiOTBjNz...jI5MTZlODkxw"}'
The response body from the API call has a status
property in it:
status: "OK"
: Password reset was successful.status: "FIELD_ERROR"
: The input password failed the backend validation logic. You should ask the user to type in another password.status: "RESET_PASSWORD_INVALID_TOKEN_ERROR"
: The password reset token in the URL is invalid, expired, or already consumed. You should redirect the user to the login screen asking them to try again.status: "GENERAL_ERROR"
: This is only possible if you have overriden the backend API to send back a custom error message which should be displayed on the frontend.