Skip to content

Configure Pre-Update Profile 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 department updates and notifying the security team when sensitive user attributes are changed.

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 profile-update-validator
cd profile-update-validator

This creates a folder named profile-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.

Install required dependencies. We will use the following libraries:

  • nodemailer — To send email notifications
  • dotenv — To securely load sensitive environment variables from a .env file

Run the following command to install the libraries:

npm install nodemailer dotenv

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 validate-user-profile-update.js to expose the API.

touch api/validate-user-profile-update.js

Add the Profile Update Validation Logic

Implement the following basic structure to the api/validate-user-profile-update.js file. This will serve as the foundation for implementing the profile update validation logic:

const nodemailer = require('nodemailer');
require("dotenv").config();

const VALID_API_KEY = process.env.API_KEY; // Replace with your actual key

Add a helper function that looks through the incoming user data (claims) and picks out specific information like department, email, or phone number based on the provided field name.

// Helper to extract claim values
const getClaimValue = (claims, uri) => {
  const claim = claims.find(c => c.uri === uri);
  return claim ? claim.value : null;
};

Create a list of departments that are considered valid for your organization. Also, set up an email service (using Nodemailer) to send notifications when sensitive user profile updates happen. We will use a service like Mailtrap for development testing.

// Mock: valid department list (simulating a directory check)
const validDepartments = ["Engineering", "HR", "Sales", "Finance"];

// Email transporter config using environment variables
const transporter = nodemailer.createTransport({
  host: "sandbox.smtp.mailtrap.io", // The hostname should be smtp.gmail.com if Gmail is used.
  port: 2525, // The port should be 465 smtp.gmail.com if Gmail is used.
  auth: {
    user: process.env.EMAIL_USER,
    pass: process.env.EMAIL_PASS
  }
});

Implement the /validate-user-profile-update API endpoint by creating a POST API that listens for user profile update requests from Asgardeo.

module.exports = async (req, res) => {
  if (req.method !== 'POST') {
    return res.status(405).send('Method Not Allowed');
  }

  // Validate API key from headers
  const apiKey = req.headers['api-key'];
  if (!apiKey || apiKey !== VALID_API_KEY) {
    return res.status(401).json({
      actionStatus: 'FAILED',
      failureReason: 'unauthorized',
      failureDescription: 'Invalid or missing API key.',
    });
  }

  const payload = req.body;

  if (payload.actionType !== "PRE_UPDATE_PROFILE") {
    return res.status(200).json({
      actionStatus: "FAILED",
      failureReason: "invalid_input",
      failureDescription: "Invalid actionType provided."
    });
  }

  const claims = payload?.event?.request?.claims || [];
  const userId = payload?.event?.user?.id || "Unknown User";

  const department = getClaimValue(claims, "http://wso2.org/claims/department");
  const email = getClaimValue(claims, "http://wso2.org/claims/emailaddress");
  const phone = getClaimValue(claims, "http://wso2.org/claims/mobile");

  // Department validation
  if (department && !validDepartments.includes(department)) {
    return res.status(200).json({
      actionStatus: "FAILED",
      failureReason: "invalid_department_input",
      failureDescription: "Provided user department value is invalid."
    });
  }

  // Send security alert email if sensitive attributes are being updated
  const changes = [];
  if (department) changes.push(`Department: ${department}`);
  if (email) changes.push(`Email: ${email}`);
  if (phone) changes.push(`Phone: ${phone}`);

  if (changes.length > 0) {
    try {
      await transporter.sendMail({
        from: '"Security Alert" <[email protected]>',
        to: "[email protected]", // Replace with actual security email
        subject: "Sensitive Attribute Update Request",
        text: `User ${userId} is attempting to update:\n\n${changes.join("\n")}`
      });
    } catch (error) {
      console.error("Failed to send security email:", error);
      return res.status(200).json({
        actionStatus: "FAILED",
        failureReason: "email_error",
        failureDescription: "Failed to notify security team about sensitive data update."
      });
    }
  }

  return res.status(200).json({actionStatus: "SUCCESS"});
};

The above source code includes the following embedded logic that is honored during execution.

  • API Key Validation: Ensures that only requests with a valid API key (passed in headers) are processed.
  • Department Validation: Checks if the department field in the user’s updated profile belongs to an allowed list.
  • Security Notification: If sensitive fields like email, phone, or department are changed, an email alert is sent to the security team.
  • Error Handling: If email sending fails, the service responds with an appropriate failure status.

The final source code should look similar to the following.

const nodemailer = require('nodemailer');
require("dotenv").config();

// Mock: valid department list (simulating a directory check)
const validDepartments = ["Engineering", "HR", "Sales", "Finance"];
const VALID_API_KEY = process.env.API_KEY; // Replace with your actual key

// Email transporter config using environment variables
const transporter = nodemailer.createTransport({
  host: "sandbox.smtp.mailtrap.io",
  port: 2525,
  auth: {
    user: process.env.EMAIL_USER,
    pass: process.env.EMAIL_PASS
  }
});

// Helper to extract claim values
const getClaimValue = (claims, uri) => {
  const claim = claims.find(c => c.uri === uri);
  return claim ? claim.value : null;
};

module.exports = async (req, res) => {
  if (req.method !== 'POST') {
    return res.status(405).send('Method Not Allowed');
  }

  // Validate API key from headers
  const apiKey = req.headers['api-key'];
  if (!apiKey || apiKey !== VALID_API_KEY) {
    return res.status(401).json({
      actionStatus: 'FAILED',
      failureReason: 'unauthorized',
      failureDescription: 'Invalid or missing API key.',
    });
  }

  const payload = req.body;

  if (payload.actionType !== "PRE_UPDATE_PROFILE") {
    return res.status(200).json({
      actionStatus: "FAILED",
      failureReason: "invalid_input",
      failureDescription: "Invalid actionType provided."
    });
  }

  const claims = payload?.event?.request?.claims || [];
  const userId = payload?.event?.user?.id || "Unknown User";

  const department = getClaimValue(claims, "http://wso2.org/claims/department");
  const email = getClaimValue(claims, "http://wso2.org/claims/emailaddress");
  const phone = getClaimValue(claims, "http://wso2.org/claims/mobile");

  // Department validation
  if (department && !validDepartments.includes(department)) {
    return res.status(200).json({
      actionStatus: "FAILED",
      failureReason: "invalid_department_input",
      failureDescription: "Provided user department value is invalid."
    });
  }

  // Send security alert email if sensitive attributes are being updated
  const changes = [];
  if (department) changes.push(`Department: ${department}`);
  if (email) changes.push(`Email: ${email}`);
  if (phone) changes.push(`Phone: ${phone}`);

  if (changes.length > 0) {
    try {
      await transporter.sendMail({
        from: '"Security Alert" <[email protected]>',
        to: "[email protected]", // Replace with actual security email
        subject: "Sensitive Attribute Update Request",
        text: `User ${userId} is attempting to update:\n\n${changes.join("\n")}`
      });
    } catch (error) {
      console.error("Failed to send security email:", error);
      return res.status(200).json({
        actionStatus: "FAILED",
        failureReason: "email_error",
        failureDescription: "Failed to notify security team about sensitive data update."
      });
    }
  }

  return res.status(200).json({actionStatus: "SUCCESS"});
};

Create a .env file at the root of your project to keep your sensitive data secure instead of hardcoding it into your source code. The file is primarily used for local testing, but these are included separately in the Vercel deployment.

touch .env

Add the following content:

EMAIL_USER=your-smtp-username
EMAIL_PASS=your-smtp-password
API_KEY=your-secure-api-key

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/ folder where your logic resides:

touch vercel.json

Add the following configuration:

{
  "version": 2,
  "rewrites": [
    {
      "source": "/(.*)",
      "destination": "/api"
    }
  ]
}

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/api/<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:

  • Set your environment variables (EMAIL_USER, EMAIL_PASS, API_KEY) securely via the Vercel dashboard.
  • 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) and the API key. A sample request for a successful scenario is shown below.

curl --location '<domains_url>/validate-user-profile-update' \
--header 'Content-Type: application/json' \
--header 'api-key: <api_key>' \
--data-raw '{
    "actionType": "PRE_UPDATE_PROFILE",
    "event": {
        "request": {
            "claims": [
                {
                    "uri": "http://wso2.org/claims/department",
                    "value": "HR"
                },
                {
                    "uri": "http://wso2.org/claims/mobile",
                    "value": "+94771223448"
                },
                {
                    "uri": "http://wso2.org/claims/emailaddress",
                    "value": "[email protected]"
                }
            ]
        },
        "tenant": {
            "id": "2210",
            "name": "testwso2"
        },
        "user": {
            "id": "57b22cbf-4688-476c-a607-c0c9d089d25d",
            "claims": [
                {
                    "uri": "http://wso2.org/claims/username",
                    "value": "[email protected]"
                },
                {
                    "uri": "http://wso2.org/claims/identity/userSource",
                    "value": "DEFAULT"
                },
                {
                    "uri": "http://wso2.org/claims/identity/idpType",
                    "value": "Local"
                }
            ]
        },
        "userStore": {
            "id": "REVGQVVMVA==",
            "name": "DEFAULT"
        },
        "initiatorType": "ADMIN",
        "action": "UPDATE"
    }
}'

Configure Asgardeo for Pre-Update Profile Action Workflow

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

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 use an API key with the header name api-key and the value defined in the environment variables.

Configure Pre Update Profile Action

Once the action is configured, ensure that the action is marked as active. Additionally, navigate to the User Management > Users section and add a user with a predefined password for testing purposes.

Add User

Since "department" is a custom attribute, you will also need to add an attribute for it. You can navigate to User Attributes & Stores > Attributes > Manage Attributes > Attributes > New Attribute, and then add an attribute named department.

Add Attribute

Once it is added, mark it to be displayed in both the Administrator Console and End User Profile.

Attribute Configurations

Validate Pre-Update Profile Action Workflow

To test the scenario, update the user's profile with sensitive claim values such as department, email, and phone number. You can perform this update either through the Console (administrator update) or the My Account application ( self-update) to verify that the department validation is working and that an email is sent via the SMTP server. Additionally, test with invalid department values to ensure the implementation handles errors as expected.

Console (administrator update)

Log in to the Console application using the administrator account, navigate to User Management > Users, select a specific user, and update the profile with sensitive attributes such as department, email address, and phone number.

My Account (self-update)

Log in to the My Account application using a specific user, navigate to Personal Info > Change Password, and update the profile with sensitive attributes such as department, email address, and phone number.