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 executed in three places in UserClouds:
- Every accessor (read path) 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 path) 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").
In addition, two special types of access policies are available:
- Column Default Access Policies: These policies are associated with specific columns and are applied by default to all reads that extract data from those columns. They ensure consistent application of access rules for sensitive data, such as automatically applying a role check to the SSN column. They can be overridden for individual accessors. Learn more here.
- Global Baseline Access Policies: These policies are applied by default to all reads, providing a consistent security baseline. For example, a global policy might always require a valid token or restrict access to trusted IP addresses. They cannot be overridden. Learn more here.
Access policies provide central, fine-grained control over sensitive data access. They can evaluate purpose, identity, authorization, location, , and more. They can range from simple "always allow resolution" policies to complex evaluations.
Example Use Cases
Let’s look at four possible access policies applied to a phone number token, to see how they work.
Example Logic | Use case |
---|---|
Only allow resolution for integrity and operational purposes | Marketing department cannot resolve phone number for SMS-based marketing |
Only allow resolution by users with role = employee | Contractor/third-party engineers cannot download phone number |
Only allow resolution after 30 days for fraud purposes | Phone number is effectively deleted from all systems after 30 days, but retained for fraud investigation |
Only allow resolution on trusted IP addresses | Phone 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 numberalpha_3
: the ISO 3166 three-letter country codecountry_name
: the ISO 3166 country name, as defined here ; andcountry_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.
Updated 3 months ago