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 validation
response.formFields.forEach(formField => {
if (formField.id === "email") {
// Email validation failed (for example incorrect email syntax).
window.alert(formField.error)
}
})
} else if (response.status === "PASSWORD_RESET_NOT_ALLOWED") {
// this can happen due to automatic account linking. Please read our account linking docs
} 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 validation
response.formFields.forEach(formField => {
if (formField.id === "email") {
// Email validation failed (for example incorrect email syntax).
window.alert(formField.error)
}
})
} else if (response.status === "PASSWORD_RESET_NOT_ALLOWED") {
// this can happen due to automatic account linking. Please read our account linking docs
} 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.");
}
}
}
- Single tenant setup
- Multi tenant setup
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 'Content-Type: application/json; charset=utf-8' \
--data-raw '{
"formFields": [{
"id": "email",
"value": "[email protected]"
}]
}'
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/<TENANT_ID>/user/password/reset/token' \
--header 'Content-Type: application/json; charset=utf-8' \
--data-raw '{
"formFields": [{
"id": "email",
"value": "[email protected]"
}]
}'
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.status: "PASSWORD_RESET_NOT_ALLOWED"
: This can happen due to automatic account linking. Please read our account linking docs.
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
- Other Frameworks
Important
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, InputAppInfo
from supertokens_python.recipe.emailpassword.types import EmailDeliveryOverrideInput, EmailTemplateVars
from supertokens_python.recipe import emailpassword
from typing import Dict, Any
from 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)
)
]
)
Multi Tenancy
For a multi tenant setup, the input to the sendEmail function will also contain the tenantId
. You can use this to determine the correct value to set for the websiteDomain in the generated link.
#
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.");
}
}
}
- Single tenant setup
- Multi tenant setup
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 'Content-Type: application/json; charset=utf-8' \
--data-raw '{
"method": "token",
"formFields": [
{
"id": "password",
"value": "newPass123"
}
],
"token": "ZTRiOTBjNz...jI5MTZlODkxw"
}'
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/<TENANT_ID>/user/password/reset' \
--header 'Content-Type: application/json; charset=utf-8' \
--data-raw '{
"method": "token",
"formFields": [
{
"id": "password",
"value": "newPass123"
}
],
"token": "ZTRiOTBjNz...jI5MTZlODkxw"
}'
Multi Tenancy
For a multi tenancy setup, the <TENANT_ID>
value can be fetched from tenantId
query parameter from the reset password link. If it's not there in the link, you can use the value "public"
(which is the default tenant).
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.