Access Policies

Introduction

Access Policies control the circumstances under which data can be retrieved or edited. These policies are functions that receive contextual data and return true or false to determine whether access is allowed or denied. The context is generated from the server and can be combined with additional context sent from the client.

Access Policies are used in three places in UserClouds:

  • Every accessor (read API) is associated with an access policy that controls access for each target user record and filters the records in the response accordingly.
  • Every mutator (write API) is associated with an access policy that governs whether the write is allowed.
  • Every token is associated with an access policy that governs the circumstances in which the token can be exchanged for the original data ("resolved").

Access policies provide central, fine-grained control over sensitive data access. They can evaluate purpose, identity, authorization, location, expiration timelines, and more. They can range from simple "always allow resolution" policies to complex evaluations.

Access policies give you central, fine-grained control over sensitive data access. Policies can evaluate purpose, identity, permissions, location, expiration timelines, rate limits and more.

Access policies give you central, fine-grained control over sensitive data access. Policies can evaluate purpose, identity, permissions, location, expiration timelines, rate limits and more.

Example Use Cases

Let’s look at four possible access policies applied to a phone number token, to see how they work.

Example LogicUse case
Only allow resolution for integrity and operational purposesMarketing department cannot resolve phone number for SMS-based marketing
Only allow resolution by users with role = employeeContractor/third-party engineers cannot download phone number
Only allow resolution after 30 days for fraud purposesPhone number is effectively deleted from all systems after 30 days, but retained for fraud investigation
Only allow resolution on trusted IP addressesPhone number cannot be downloaded outside of company VPN

Context Evaluation Structure

Access policies evaluate claims and key/value pairs in the provided context. The structure of the context is as follows:

  • context.server: This context is generated by the server and is generally considered trusted. It includes information like trusted JWT claims, IP addresses, and actions.
    • context.server.claims: Contains claims extracted from a JSON Web Token (JWT) that has been signed by a trusted issuer. This is typically used for claims that are standardized and validated by an identity provider like Okta.
    • context.server.purpose_names: the purposes specified on an accessor to which this access policy is attached (does not apply in all cases)
    • context.server.ip_address: the IP address of the user or system initiating the request
  • context.client: This context contains key/value pairs specified in the request comments by the client and is not considered trusted. It includes data such as user-specified parameters that are not verified by the server.
  • context.user: Information about data for the user whose data is row being accessed.
    evaluated by the access policy
  • context.query: Specific query parameters relevant to the request.
  • context.row_data: Specific Column data for the user row data values related to being evaluated by the request.

Access Policy Templates

Access Policies are composed from Access Policy Templates. Templates are parametrizable functions that can be parametrized to create multiple access policies with parallel logic. For example, you might create a template "User is over X years old". You may use this template to create several access policy instances, allowing you to create conditional logic on a user's age group.

Example 1: User is over 16 Years Old

Template function

function getAge(DOB) {  
    const today = new Date();  
    const birthDate = new Date(DOB);  
    let age = today.getFullYear() - birthDate.getFullYear();  
    const m = today.getMonth() - birthDate.getMonth();  
    if (m \< 0 || (m === 0 && today.getDate() \< birthDate.getDate())) {  
        age--;  
    }  
    return age;  
}

function policy(context, params) {  
  return getAge(context.user[params.column_name]) >= params.expected_years_old;  
}

Parameters to instantiate a policy

// Example Policy Parameters (not specified in the template function)
const params = { expected_years_old: 16, column_name: "birthdate" };

Example 2: Country Claim in JWT is USA

Here is an example of how you can create a policy to check if the country claim matches a specified country:

Template function

function policy(context, params) {  
    const country = context.server.claims.country;  
    const specifiedCountry = params.specified_country;  
    return country === specifiedCountry;  
}

Parameters to instantiate a policy

// Example Policy Parameters (not specified in the template function)
const params = { specified_country: "USA" };

Example 3: User-Specified Country is USA

If the country information is not in the JWT but in the client context, the policy would look like this:

Template function

function policy(context, params) {  
    const country = context.client.country;  
    const specifiedCountry = params.specified_country;  
    return country === specifiedCountry;  
}

Parameters to instantiate a policy

// Example Policy Parameters (not specified in the template function)
const params = { specified_country: "USA" };

Built-in Functions

We provide a number of built-in functions available as global variables in your access policy templates (and transformers!). They are:

  • networkRequest
  • checkAttribute
  • getCountryNameForPhoneNumber

networkRequest

The built-in networkRequest function allows you to reach external services via HTTPS requests. You can pass headers in the network request, allowing you to authorize into third-party services, such as Zendesk via Basic Auth. This feature is useful for scenarios where your policy needs to check external data, e.g. for verifying if an employee has been assigned an open ticket for the user whose data they are trying to access.

Example:

This example shows how to make a network request to verify the user's country based on their IP address. Note that the interface for networkRequest is synchronous, as in the example below:

function policy(context, params) {  
  let countryCode = null;  
  const resp = networkRequest({url: `https://api.iplocation.net/?ip=${context.server.ip_address}`, method: 'GET' });  
  if (resp) {  
    countryCode = JSON.parse(resp).country_code2;  
  }  
  return countryCode === "US";  
}

Example:

This example shows how to verify an employee is active in a private employee directory, using Basic Auth.

Template Function:

function policy(context, params) {  
  let isActiveEmployee = false;

  const resp = networkRequest({  
    url: `https://api.company.com/employees/${context.user.id}/status`,  
    method: 'GET',  
    headers: {  
      'Authorization': 'Basic ' + btoa('username:password'),  
      'Content-Type': 'application/json'  
    }  
  });

  if (resp) {  
    const employeeData = JSON.parse(resp);  
    isActiveEmployee = employeeData.status === "active";  
  }

  return isActiveEmployee;  
}

Parameters to instantiate a policy

// Example Policy Parameters (not specified in the template function)
const params = {};  // No specific parameters needed for this example

checkAttribute

The checkAttribute function runs a permission check against the UserClouds authorization graph. If you are using UserClouds for authorization as a service, this can verify if a user has the necessary permissions. In short, it asks whether a given object (usually a user) has an attribute (e.g. "can-read" or "is-admin") on another object (which could be just about any entity in your system). You can read more about this in the Authorization Documentation.

Example:

Use Case: Does the calling user have view permission on the target user?

function policy(context) {
    const callingUserId = context.user.id;
    const targetUserId = context.params.targetUserId;
    const attribute = "viewPermission";
    if (!callingUserId || !targetUserId || !attribute) {
        return false;
    }
    return checkAttribute(callingUserId, targetUserId, attribute);
}

getCountryNameForPhoneNumber

getCountryNameForPhoneNumber takes a phone number with country code as a string and returns a JavaScript object with the following attributes, all with string values:

  • alpha_2: the ISO 3166 two-letter country code for the number
  • alpha_3: the ISO 3166 three-letter country code
  • country_name: the ISO 3166 country name, as defined here ; and
  • country_code: the numeric telephone country code

Example:

function policy(context, params) {  
  let { phone_numer } = params; // e.g. "+12065551234"
  const country = getCountryNameForPhoneNumber(phone_number);  
   
  return country.alpha_2 === "US";  
}

Managing access policies

UserClouds has several built-in access policies for common use cases, like role-based and time-based expiration of data. However you can also create custom policies, in two ways:

  • Call the CreateAccessPolicy API
  • Compose a new policy from existing policies and parametrizable templates in the UserClouds Console

To learn more about creating access policies, see our How to Guide on Creating Access Policies.


What’s Next