Allow users to change their passwords
caution
SuperTokens does not provide the UI for users to change/update their password, you will need to create the UI and setup a route on your backend to have this functionality.
In this section we will go over how you can create a route on your backend which can update a user's password. Calling this route will check if the old password is valid and update the user's profile with the new password.
/change-password
route#
Step 1: Creating the - You will need to create a route on your backend which is protected by the session verification middleware, this will ensure that only a authenticated user can access the protected route.
- To learn more about how to use the session verfication middleware for other frameworks click here
- NodeJS
- GoLang
- Python
- Other Frameworks
Important
import { verifySession } from "supertokens-node/recipe/session/framework/express";
import { SessionRequest } from "supertokens-node/framework/express"
import express from "express";
let app = express();
app.post("/change-password", verifySession(), async (req: SessionRequest, res: express.Response) => {
// TODO: see next steps
})
import (
"net/http"
"github.com/supertokens/supertokens-golang/recipe/session"
)
// the following example uses net/http
func main() {
_ = http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
// Wrap the API handler in session.VerifySession
session.VerifySession(nil, changePasswordAPI).ServeHTTP(rw, r)
})
}
func changePasswordAPI(w http.ResponseWriter, r *http.Request) {
// TODO: see next steps
}
# the following example uses flask
from supertokens_python.recipe.session.framework.flask import verify_session
from flask import Flask
app = Flask(__name__)
@app.route('/change-password', methods=['POST'])
@verify_session()
def change_password():
pass # TODO: see next steps
#
Step 2: Validate and update the user's password- You can now use
session
object to retrive the logged in user'suserId
. - Use the recipe's sign in function and check if the old password is valid
- Update the user's password.
- NodeJS
- GoLang
- Python
- Other Frameworks
Important
// the following example uses express
import EmailPassword from "supertokens-node/recipe/emailpassword";
import { verifySession } from "supertokens-node/recipe/session/framework/express";
import { SessionRequest } from "supertokens-node/framework/express"
import express from "express";
import supertokens from "supertokens-node";
let app = express();
app.post("/change-password", verifySession(), async (req: SessionRequest, res: express.Response) => {
// get the supertokens session object from the req
let session = req.session
// retrive the old password from the request body
let oldPassword = req.body.oldPassword
// retrive the new password from the request body
let updatedPassword = req.body.newPassword
// get the signed in user's email from the getUserById function
let userInfo = await supertokens.getUser(session!.getUserId())
if (userInfo === undefined) {
throw new Error("Should never come here")
}
let loginMethod = userInfo.loginMethods.find((lM) => lM.recipeUserId.getAsString() === session!.getRecipeUserId().getAsString() && lM.recipeId === "emailpassword");
if (loginMethod === undefined) {
throw new Error("Should never come here")
}
const email = loginMethod.email!;
// call signin to check that input password is correct
let isPasswordValid = await EmailPassword.verifyCredentials(session!.getTenantId(), email, oldPassword)
if (isPasswordValid.status !== "OK") {
// TODO: handle incorrect password error
return
}
// update the user's password using updateEmailOrPassword
let response = await EmailPassword.updateEmailOrPassword({
recipeUserId: session!.getRecipeUserId(),
password: updatedPassword,
tenantIdForPasswordPolicy: session!.getTenantId()
})
if (response.status === "PASSWORD_POLICY_VIOLATED_ERROR") {
// TODO: handle incorrect password error
return
}
// TODO: send successful password update response
})
import (
"encoding/json"
"net/http"
"github.com/supertokens/supertokens-golang/recipe/emailpassword"
"github.com/supertokens/supertokens-golang/recipe/session"
)
func main() {
_ = http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
// Wrap the API handler in session.VerifySession
session.VerifySession(nil, changePasswordAPI).ServeHTTP(rw, r)
})
}
type RequestBody struct {
OldPassword string
NewPassword string
}
func changePasswordAPI(w http.ResponseWriter, r *http.Request) {
// retrieve the session object as shown below
sessionContainer := session.GetSessionFromRequestContext(r.Context())
// retrive the old password from the request body
var requestBody RequestBody
err := json.NewDecoder(r.Body).Decode(&requestBody)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
// get the userId from the session
userID := sessionContainer.GetUserID()
// get the signed in user's email from the getUserById function
userInfo, err := emailpassword.GetUserByID(userID)
if err != nil {
// TODO: Handle error
return
}
// call signin to check that the input is correct
isPasswordValid, err := emailpassword.SignIn(sessionContainer.GetTenantId(), userInfo.Email, requestBody.OldPassword)
if err != nil {
// TODO: Handle error
return
}
if isPasswordValid.WrongCredentialsError != nil {
// TODO: Handle error
return
}
tenantId := sessionContainer.GetTenantId()
updateResponse, err := emailpassword.UpdateEmailOrPassword(userID, &userInfo.Email, &requestBody.NewPassword, nil, &tenantId, nil)
if err != nil {
// TODO: Handle error
return
}
if updateResponse.PasswordPolicyViolatedError != nil {
// This error is returned if the new password doesn't match the defined password policy
// TODO: Handle error
return
}
// TODO: send successful password update response
}
from supertokens_python.recipe.session.framework.flask import verify_session
from supertokens_python.recipe.session import SessionContainer
from supertokens_python.recipe.emailpassword.syncio import (
verify_credentials,
update_email_or_password,
)
from supertokens_python.syncio import get_user
from supertokens_python.recipe.emailpassword.interfaces import (
WrongCredentialsError,
PasswordPolicyViolationError,
)
from flask import g, request
@app.route("/change-password", methods=["POST"])
@verify_session()
def change_password():
session: SessionContainer = g.supertokens
# get the signed in user's email from the getUserById function
users_info = get_user(session.get_user_id())
if users_info is None:
raise Exception("TODO: Handle error. User not found.")
# Find the login method for the current user
login_method = next(
(
lm
for lm in users_info.login_methods
if lm.recipe_user_id.get_as_string() == session.get_recipe_user_id()
and lm.recipe_id == "emailpassword"
),
None,
)
if login_method is None:
raise Exception("Should never come here")
email = login_method.email
if email is None:
raise Exception("Email not found for the user")
request_body = request.get_json()
if request_body is None:
# TODO: handle invalid body error
return
# call signin to check that the input password is correct
isPasswordValid = verify_credentials(
"public", email, password=request_body["oldPassword"]
)
if isinstance(isPasswordValid, WrongCredentialsError):
# TODO: handle incorrect password error
return
# update the users password
update_response = update_email_or_password(
session.get_recipe_user_id(),
password=request_body["newPassword"],
tenant_id_for_password_policy=session.get_tenant_id(),
)
if isinstance(update_response, PasswordPolicyViolationError):
# TODO: handle password policy violation error
return
# TODO: send successful password update response
Multi Tenancy
Notice that we pass in the tenantId as an argument to the signIn
and the updateEmailOrPassword
functions. This is because the we want to ensure that the current tenant has email password enabled, and we want to make sure that the we user's new password matches the password policy defined for their tenant (if you have defined different password polocies for different tenants).
If this user is shared across several tenants, their password is changed for all tenants.
#
Step 3: Revoke all sessions associated with the user (optional)- Revoking all sessions associated with the user will force them to reauthenticate with their new password.
- NodeJS
- GoLang
- Python
- Other Frameworks
Important
// the following example uses express
import Session from "supertokens-node/recipe/session";
import { verifySession } from "supertokens-node/recipe/session/framework/express";
import { SessionRequest } from "supertokens-node/framework/express"
import express from "express";
let app = express();
app.post("/change-password", verifySession(), async (req: SessionRequest, res: express.Response) => {
let userId = req.session!.getUserId();
/**
*
* ...
* see previous step
* ...
*
* */
// revoke all sessions for the user
await Session.revokeAllSessionsForUser(userId)
// revoke the current user's session, we do this to remove the auth cookies, logging out the user on the frontend.
await req.session!.revokeSession()
// TODO: send successful password update response
})
import (
"net/http"
"github.com/supertokens/supertokens-golang/recipe/session"
)
func main() {
_ = http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
// Wrap the API handler in session.VerifySession
session.VerifySession(nil, changePasswordAPI).ServeHTTP(rw, r)
})
}
type ResponseBody struct {
OldPassword string
NewPassword string
}
func changePasswordAPI(w http.ResponseWriter, r *http.Request) {
// retrieve the session object as shown below
sessionContainer := session.GetSessionFromRequestContext(r.Context())
userID := sessionContainer.GetUserID()
/**
*
* ...
* see previous step
* ...
*
* */
// revoke all sessions for the user
_, err := session.RevokeAllSessionsForUser(userID, nil)
if err != nil {
// TODO: Handle error
}
// revoke the user's current session, we do this to remove the auth cookies, logging out the user on the frontend
err = sessionContainer.RevokeSession()
if err != nil {
// TODO: Handle error
}
// TODO: send successful password update response
}
from supertokens_python.recipe.session.framework.flask import verify_session
from supertokens_python.recipe.session.syncio import revoke_all_sessions_for_user
from flask import Flask
from supertokens_python.recipe.session import SessionContainer
app = Flask(__name__)
@app.route('/change-password', methods=['POST'])
@verify_session()
def change_password():
session: SessionContainer = g.supertokens
# get the userId from the session object
user_id = session.get_user_id()
# TODO: see previous step...
# revoke all sessions for the user
revoke_all_sessions_for_user(user_id)
# revoke the user's current session, we do this to remove the auth cookies, logging out the user on the frontend
session.sync_revoke_session()
# TODO: send successful password update response