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.
Step 1: Creating the /change-password route#
- 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 sessionobject 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 get_user_by_id, sign_in, update_email_or_password
from supertokens_python.recipe.emailpassword.interfaces import SignInWrongCredentialsError, UpdateEmailOrPasswordPasswordPolicyViolationError
from flask import g, request
@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()
    # get the signed in user's email from the getUserById function
    users_info = get_user_by_id(user_id)
    oldPassword = str(request.json["oldPassword"])  
    newPassword = str(request.json["newPassword"])  
    if users_info is None:
        raise Exception("TODO: Handle error. User not found.")
    
    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 = sign_in("public", users_info.email, password=request_body["oldPassword"])
    if isinstance(isPasswordValid, SignInWrongCredentialsError):
        # TODO: handle incorrect password error
        return
    # update the users password
    update_response = update_email_or_password(user_id, password=request_body["newPassword"], tenant_id_for_password_policy=session.get_tenant_id())
    if isinstance(update_response, UpdateEmailOrPasswordPasswordPolicyViolationError):
        # 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