Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adds change password guide #212

Merged
merged 11 commits into from
Feb 8, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
332 changes: 332 additions & 0 deletions v2/emailpassword/common-customizations/change-password.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,332 @@
---
id: change-password
title: Allow users to change their passwords
hide_title: true
---

<!-- COPY DOCS -->
<!-- ./thirdpartyemailpassword/common-customizations/change-password.mdx -->

import BackendSDKTabs from "/src/components/tabs/BackendSDKTabs";
import BackendSDKCasing from "/src/components/BackendSDKCasing"
import TabItem from '@theme/TabItem';

# 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](verify-session)

<BackendSDKTabs>

<TabItem value="nodejs">

```jsx
// the following example uses express
let ^{recipeNameCapitalLetters} = require("supertokens-node/recipe/^{codeImportRecipeName}");
let { verifySession } = require("supertokens-node/recipe/session/framework/express");

// highlight-start
app.post("/change-password", verifySession(), async (req, res) => {
// TODO: see next steps
})

// highlight-end
```
</TabItem>

<TabItem value="go">

```go
// highlight-start
// the following example uses net/http
func main() {
// 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
}
// highlight-end
```

</TabItem>


<TabItem value="python">

```python
# the following example uses flask
from supertokens_python.recipe.session.framework.flask import verify_session

# highlight-start
@app.route('/change-password', methods=['POST'])
@verify_session()
def change_password():
# TODO: see next steps
# highlight-end
```

</TabItem>

</BackendSDKTabs>

## Step 2: Validate and update the user's password

- You can now use `session` object to retrive the logged in user's `userId`.
- Use the recipe's sign in function and check if the old password is valid
- Update the user's password.

<BackendSDKTabs>
<TabItem value="nodejs">

```jsx
// the following example uses express
let ^{recipeNameCapitalLetters} = require("supertokens-node/recipe/^{codeImportRecipeName}");
let { verifySession } = require("supertokens-node/recipe/session/framework/express");

app.post("/change-password", verifySession(), async (req, res) => {

// highlight-start
// 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 user's Id from the session
let userId = session.getUserId()

// get the signed in user's email from the getUserById function
let userInfo = await ^{recipeNameCapitalLetters}.getUserById(userId)

// call signin to check that input password is correct
let isPasswordValid = await ^{recipeNameCapitalLetters}.signIn(userInfo.email, oldPassword)

if (isPasswordValid.status !== "OK") {
// TODO: handle incorrect password error
return
}

// update the user's password using updateEmailOrPassword
let response = await ^{recipeNameCapitalLetters}.updateEmailOrPassword({
userId,
password: updatedPassword
})

// TODO: send successful password update response
// highlight-end

})

```
</TabItem>

<TabItem value="go">

```go

func main() {
// 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())

// retrive the old password from the request body
var response ResponseBody
err := json.NewDecoder(r.Body).Decode(&response)
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 := ^{rid}.GetUserById(userID)
if err != nil {
// TODO: Handle error
return
}

// call signin to check that the input is correct
isPasswordValid, err := ^{rid}.SignIn(userInfo.Email, response.OldPassword)
if err != nil {
// TODO: Handle error
return
}

// highlight-start
if isPasswordValid.OK != nil {
// TODO: Handle error
return
}

response, err = ^{rid}.UpdateEmailOrPassword(userID, &userInfo.Email, &response.NewPassword)
if err != nil {
// TODO: Handle error
return
}
// TODO: send successful password update response
// highlight-end

}

```
</TabItem>

<TabItem value="python">

```python
from supertokens_python.recipe.session.framework.flask import verify_session
from supertokens_python.recipe.^{rid}.syncio import get_user_by_id, sign_in

@app.route('/change-password', methods=['POST'])
@verify_session()
def change_password():

# highlight-start
session_ = 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)

# call signin to check that the input password is correct
isPasswordValid = sign_in(users_info.email, request.json["oldPassword"])

if isPasswordValid.status != "OK":
# TODO: handle incorrect password error
return

# update the users password
response = update_email_or_password(user_id, password=request.json["newPassword"])

# TODO: send successful password update response
# highlight-end

```
</TabItem>
</BackendSDKTabs>

## 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.

<BackendSDKTabs>
<TabItem value="nodejs">

```jsx
// the following example uses express
let ^{recipeNameCapitalLetters} = require("supertokens-node/recipe/^{codeImportRecipeName}");
let Session = require("supertokens-node/recipe/session");
let { verifySession } = require("supertokens-node/recipe/session/framework/express");

app.get("/change-password", verifySession(), async (req, res) => {

// TODO: see previous step

// highlight-start
// 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()
//highlight-end

// TODO: send successful password update response

})

```
</TabItem>

<TabItem value="go">

```go

func main() {
// 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) {

// TODO: see previous step

// highlight-start

// revoke all sessions for the user
_, err = session.RevokeAllSessionsForUser(userID)
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 = session.GetSessionFromRequestContext(r.Context()).RevokeSession()
if err != nil {
// TODO: Handle error
}

// highlight-end

// TODO: send successful password update response
}

```
</TabItem>

<TabItem value="python">

```python
from supertokens_python.recipe.session.framework.flask import verify_session
from supertokens_python.recipe.^{rid}.syncio import get_user_by_id, sign_in, update_email_or_password
from supertokens_python.recipe.session.syncio import revoke_all_sessions_for_user, revoke_session

@app.route('/change-password', methods=['POST'])
@verify_session()
def change_password():

# TODO: see previous step

# highlight-start
# 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
g.supertokens.revoke_session()
# highlight-end

# TODO: send successful password update response

```
</TabItem>

</BackendSDKTabs>
1 change: 1 addition & 0 deletions v2/emailpassword/sidebars.js
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ module.exports = {
"common-customizations/user-pagination",
"common-customizations/delete-user",
"common-customizations/embed-sign-in-up-form",
"common-customizations/change-password",
{
type: "category",
label: "User Roles",
Expand Down
Loading