Skip to content

Configure Pre-Update Password Action with Choreo
5 mins

Using Choreo

In this section, we will walk through how to implement and deploy the Pre-Update Password Action using Choreo based on the scenario discussed earlier.

This implementation is done using Node.js and follows the logic of validating the new password using the Have I Been Pwned (HIBP) service to determine if it has been exposed in any known data breaches. If the password is identified as compromised, the update will be prevented, and the user will be prompted to select a more secure alternative.

Set Up Your Node.js Project

Create a new project folder on your local machine. Open your terminal or command prompt and create a new directory where your project files will be stored. You can do this by running:

mkdir password-update-validator

This command creates a new folder named password-update-validator. Then, move inside that folder by running:

cd password-update-validator

Now, any new files or commands you use will be applied inside this project folder. Once you're inside the folder, run:

npm init -y

This will create a file named package.json automatically. The package.json file is very important, it keeps track of your project details (like name, version, and dependencies) and will help others (or platforms like Choreo) understand how to run your project.

The -y flag automatically fills in default values for you, so you don’t need to answer any setup questions manually. Please make sure to open the generated package.json file and set the value of type to module.

Install the required dependencies. In this guide, we use express to build the service, validator for input validation, axios to make HTTP requests, and Node.js built-in crypto for secure hashing.

Still inside your project folder, install the necessary libraries by running:

npm install express validator axios

express: A fast, lightweight framework that makes building web servers in Node.js very simple and structured.

validator: A library that provides a set of string validation and sanitization functions, making it easy to validate and clean user input in your application.

axios: A promise-based HTTP client that allows you to make asynchronous HTTP requests, both in the browser and Node.js, simplifying interactions with APIs and external services.

This command will download the libraries and save them inside a folder called node_modules and also update your package.json file under dependencies, showing that your project uses these libraries.

Create a file named index.js and add the following basic structure: In your project folder, create a new file called index.js. (You can right-click and choose “New File” if using a code editor like VS Code, or create it via terminal using the command touch index.js.)

import express from "express";
import crypto from "node:crypto";
import validator from "validator";
import axios from "axios";
import {json} from "express";

const app = express();

const PORT = 3000;

app.use(json({limit: "100kb"}));

After saving this file, you will have a basic server ready that you can expand by adding routes (like /passwordcheck) in the next steps.

Implement the Password Update Validation Logic

Implement the /passwordcheck API endpoint by creating a POST API that listens for user password update requests from Asgardeo.

Inside this API:

  • Check if the request is for a password check.
  • Extract the password credential.
  • Check if the credential type is password and if it is in plain text or hash.
  • Perform an external validation (check if the password is compromised using HIBP).
  • Respond based on the outcome (compromised or not).
app.post("/passwordcheck", async (req, res) => {
    try {
        if (!validator.isJSON(JSON.stringify(req.body))) {
            return res.status(400).json({
                actionStatus: "ERROR",
                error: "invalid_request",
                errorDescription: "Invalid JSON payload."
            });
        }

        const cred = req.body?.event?.user?.updatingCredential;
        if (!cred || cred.type !== "PASSWORD") {
            return res.status(400).json({
                actionStatus: "ERROR",
                error: "invalid_credential",
                errorDescription: "No password credential found."
            });
        }

        // Handle encrypted (base64-encoded) or plain text passwords
        let plain = cred.value;
        if (cred.format === "HASH") {
            try {
                plain = Buffer.from(cred.value, "base64").toString("utf8");
            } catch {
                return res.status(400).json({
                    actionStatus: "ERROR",
                    error: "invalid_credential",
                    errorDescription: "Expects the encrypted credential."
                });
            }
        }

        const sha1 = crypto.createHash("sha1").update(plain).digest("hex").toUpperCase();
        const prefix = sha1.slice(0, 5);
        const suffix = sha1.slice(5);

        const hibpResp = await axios.get(
            `https://api.pwnedpasswords.com/range/${prefix}`,
            {
                headers: {
                    "Add-Padding": "true",
                    "User-Agent": "hibp-demo"
                }
            }
        );

        const hitLine = hibpResp.data
            .split("\n")
            .find((line) => line.startsWith(suffix));

        const count = hitLine ? parseInt(hitLine.split(":")[1], 10) : 0;

        if (count > 0) {
            return res.status(200).json({
                actionStatus: "FAILED",
                failureReason: "password_compromised",
                failureDescription: "The provided password is compromised."
            });
        }

        return res.json({
            actionStatus: "SUCCESS",
            message: "Password is not compromised."
        });
    } catch (err) {
        console.error("🔥", err);
        const status = err.response?.status || 500;
        const msg =
            status === 429
                ? "External HIBP rate limit hit—try again in a few seconds."
                : err.message || "Unexpected server error";
        res.status(status).json({error: msg});
    }
});

The final source code should look similar to the following.

import express from "express";
import crypto from "node:crypto";
import validator from "validator";
import axios from "axios";
import {json} from "express";

const app = express();

const PORT = 3000;

app.use(json({limit: "100kb"}));

app.get("/", (_req, res) => {
    res.json({
        message: "Pre-password update service up and running!",
        status: "OK",
    });
});

app.post("/passwordcheck", async (req, res) => {
    try {
        if (!validator.isJSON(JSON.stringify(req.body))) {
            return res.status(400).json({
                actionStatus: "ERROR",
                error: "invalid_request",
                errorDescription: "Invalid JSON payload."
            });
        }

        const cred = req.body?.event?.user?.updatingCredential;
        if (!cred || cred.type !== "PASSWORD") {
            return res.status(400).json({
                actionStatus: "ERROR",
                error: "invalid_credential",
                errorDescription: "No password credential found."
            });
        }

        // Handle encrypted (base64-encoded) or plain text passwords
        let plain = cred.value;
        if (cred.format === "HASH") {
            try {
                plain = Buffer.from(cred.value, "base64").toString("utf8");
            } catch {
                return res.status(400).json({
                    actionStatus: "ERROR",
                    error: "invalid_credential",
                    errorDescription: "Expects the encrypted credential."
                });
            }
        }

        const sha1 = crypto.createHash("sha1").update(plain).digest("hex").toUpperCase();
        const prefix = sha1.slice(0, 5);
        const suffix = sha1.slice(5);

        const hibpResp = await axios.get(
            `https://api.pwnedpasswords.com/range/${prefix}`,
            {
                headers: {
                    "Add-Padding": "true",
                    "User-Agent": "hibp-demo"
                }
            }
        );

        const hitLine = hibpResp.data
            .split("\n")
            .find((line) => line.startsWith(suffix));

        const count = hitLine ? parseInt(hitLine.split(":")[1], 10) : 0;

        if (count > 0) {
            return res.status(200).json({
                actionStatus: "FAILED",
                failureReason: "password_compromised",
                failureDescription: "The provided password is compromised."
            });
        }

        return res.json({
            actionStatus: "SUCCESS",
            message: "Password is not compromised."
        });
    } catch (err) {
        console.error("🔥", err);
        const status = err.response?.status || 500;
        const msg =
            status === 429
                ? "External HIBP rate limit hit—try again in a few seconds."
                : err.message || "Unexpected server error";
        res.status(status).json({error: msg});
    }
});

app.listen(PORT, () => {
    console.log(
        `🚀  Pre-password update service started on http://localhost:${PORT} — ` +
        "press Ctrl+C to stop"
    );
});

Run an Express Node Project Locally

Navigate to the root of your project and install all required dependencies using npm:

npm install

Once dependencies are installed, you can start the Express server using:

node index.js

Or, if a custom script is defined (e.g., dev), run:

npm run dev

This will start the server, and your API will be accessible on:

http://localhost:3000/<endpoint-name>

If you're using tools like nodemon for auto-reloading during development, make sure it's installed and used in the dev script.

Push Your Code to GitHub

First, initialize a Git repository in your project folder:

git init

Then, add all your project files to the Git repository. Make sure not to commit files containing sensitive information or unnecessary files, you can use the .gitignore file to exclude them.

git add .

Commit your changes with a message:

git commit -m "Initial commit for Choreo deployment"

Finally, link your local Git repository to a remote repository (e.g., on GitHub) and push your code:

git remote add origin https://github.com/yourusername/yourrepository.git
git push -u origin master

This makes your code available in the cloud and allows easy collaboration or version control.

Deploy on Choreo

Log in to your Choreo Console and create a new project by signing in to your Choreo account and creating a new project from the dashboard.

Create Choreo Project

Within the created project, go to the "Components" section and create a new component. Select 'API Service' as the component type.

Link your GitHub account and select the password-update-validator repository that contains your code. Choreo will use this to build the project.

Create Choreo Service

After the build is complete, go to the "Deploy" tab and click on 'Configure and Deploy' to set up the deployment settings and initiate the deployment process.

Deploy Choreo Service

For security, make sure to enable the API Key protection mechanism. This will ensure that only authorized users can access your API.

Add Choreo API Key Protection

After the deployment is complete, Choreo will provide a 'Public URL' for your API under Endpoints > Endpoint Details. Be sure to copy this URL for future reference.

Additionally, Go to Manage > Lifecycle and click 'Publish' to move your API from the "Created" state to the "Published" state.

Choreo API Lifecycle Update

Once the API is published, navigate to the Dev portal (via the "Go to Devportal" link in the top right corner). In the Dev portal, go to Credentials > Sandbox and generate a new API key. This key is required for accessing the API securely.

Create Choreo API Key

The API key will be generated along with an application in Asgardeo. Copy and save the key securely for later use in your API calls.

Test Deployed Service

To test the deployed service, you will need the public URL and the API key. A sample request for a successful scenario is shown below.

curl --location '<public_url>/passwordcheck' \
--header 'Content-Type: application/json' \
--header 'api-key: <api_key>' \
--data '{
  "actionType": "PRE_UPDATE_PASSWORD",
  "event": {
    "tenant": {
      "id": "2210",
      "name": "testwso2"
    },
    "user": {
      "id": "18b6b431-16e9-4107-a828-33778824c8af",
      "updatingCredential": {
        "type": "PASSWORD",
        "format": "HASH",
        "value": "ec4Zktg/dqruY3ZHVjwTCZ9422Bu0Xi3F56ZcFxkcjU=",
        "additionalData": {
          "algorithm": "SHA256"
        }
      }
    },
    "userStore": {
      "id": "REVGQVVMVA==",
      "name": "DEFAULT"
    },
    "initiatorType": "ADMIN",
    "action": "UPDATE"
  }
}'

Configure Asgardeo for Pre-Update Password Action Workflow

First, sign in to your Asgardeo account using your admin credentials, click on "Actions" and then select the action type Pre Update Password.

Add an action name, the endpoint extracted from the deployment, and the appropriate authentication mechanism. For Choreo, append the endpoint name defined in the source code to the generated Choreo URL, and set the authentication mechanism to use an API key with the header name api-key and the value generated through the Dev Portal. For the password sharing mechanism, you can use either SHA-256 hashed or plain text, as the implementation supports both formats.

Configure Pre Update Password Action

Once the action is configured, make sure it is marked as active to ensure it is triggered during relevant operations. Then, log in to the Console application using the administrator account, navigate to the User Management > Users section, and add a new user with a predefined password. This user will be used to test the configured pre-password update action.

Add User

Validate Pre-Update Password Action Workflow

To test this scenario, attempt to update a user's password using both compromised and uncompromised passwords, verified through Pwned Passwords. The update can be performed via either the Console ( administrator action) or the My Account application (user self-service) to ensure that only uncompromised passwords are accepted.

Console (administrator update)

  1. Log in to the Asgardeo Console application using an administrator account.
  2. Navigate to User Management > Users.
  3. Select a user and reset their password.

My Account (self-update)

  1. Log in to the Asgardeo My Account as a user.
  2. Go to Security > Change Password.
  3. Attempt to update the password.