Skip to content

Configure Pre-Update Password Action with Vercel
5 mins

Using Vercel

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

This implementation is done using Node.js and follows the logic of validating user passwords against the Have I Been Pwned (HIBP) service to prevent users from setting passwords that have been exposed in known data breaches.

The deployment is handled through Vercel’s serverless functions capability, which makes it very easy to expose APIs without needing to manage your own servers.

Set Up Your Node.js Project

First, create a new project folder on your local machine by opening your terminal or command prompt and executing:

mkdir password-update-validator
cd password-update-validator

This creates a folder named password-update-validator and moves you into it.

Initialize a new Node.js project by running:

npm init -y

This will generate a package.json file, which manages your project’s metadata and dependencies. The -y flag automatically accepts all default settings, so you don't have to manually answer prompts. Please make sure to open the generated package.json file and set the value of type to module.

Install required dependencies. We will use the following libraries:

  • express — To create and manage the HTTP server and define API routes
  • validator — To perform validation checks such as ensuring valid JSON payloads
  • axios — To make HTTP requests to external services like the HIBP API

Run the following command to install the libraries:

npm install express validator axios

These libraries will be downloaded into a node_modules directory, and your package.json will update with these new dependencies under dependencies.

Create the API Structure for Vercel

Inside the project folder, create a new folder called api.

mkdir api

This api folder will contain the serverless function file — in Vercel, any file inside api/ becomes a separate API endpoint automatically.

Inside the api folder, create a new file named index.js to expose the API.

touch api/index.js

Add the Profile Update Validation Logic

Implement the following basic structure to the api/index.js file. This will serve as the foundation for implementing the password update validation logic:

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"}));

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

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 above source code includes the following embedded logic that is honored during execution.

  • JSON Validation: Ensures the incoming request body is valid JSON before proceeding with further checks.
  • Credential Extraction: Extracts the password credential from the request and verifies that the type is PASSWORD.
  • Password Decoding: Handles both plain text and base64-encoded (hashed) passwords by decoding if necessary.
  • HIBP Check: Uses the Have I Been Pwned (HIBP) API to check if the SHA-1 hash of the password appears in known data breaches.
  • Compromised Password Handling: If the password is found in HIBP data, the request is marked as failed and the user is notified that the password is compromised.
  • Success Response: If the password is not found in HIBP records, a success status is returned indicating the password is safe.
  • Error Handling: Gracefully handles unexpected errors or rate-limiting from the HIBP API and responds with a meaningful error message.

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"
  );
});

export default app;

Configure Vercel Settings

Create a vercel.json file at the root of the project which instructs Vercel to route all incoming traffic to the api/index.js where your logic resides:

touch vercel.json

Add the following configuration:

{
  "version": 2,
  "builds": [
    {
      "src": "api/index.js",
      "use": "@vercel/node"
    }
  ],
  "routes": [
    {
      "src": "/(.*)",
      "dest": "/api/index.js"
    }
  ]
}

Install Vercel CLI (Optional for Local Testing)

If you want to test the API locally using Vercel’s CLI, install it globally:

npm install -g vercel

Now you can use the vercel command to deploy or test locally. To run the project locally:

vercel dev

This will simulate the Vercel environment locally and allow you to access your API on http://localhost:3000/<endpoint-name>.

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 Vercel 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 to Vercel

Log in to the Vercel Dashboard, click on Add New > Project, and import the GitHub repository you pushed earlier.

Vercel Add Project

During the setup:

  • Confirm that Vercel detects the correct root folder structure (API functions).

Finally, deploy the project. Vercel will automatically build and host your serverless functions. Once deployed, you'll receive a live endpoint URL that you can use under Domains.

Vercel Deployed Project

Test Deployed Service

To test the deployed service, you will need the URL (extracted from Domains). A sample request for a successful scenario is shown below.

curl --location 'https://<domains_url>/passwordcheck' \
--header 'Content-Type: application/json' \
--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 Vercel, append the endpoint name defined in the source code to the generated Vercel domain URL and set the authentication mechanism to None. 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.