Skip to content

Claims evaluation

Introduction

Claims evaluation occurs during protocol access authorization. In this process, the party claims, which are attributes that identify the party, are compared to the claims bound to the protocol party. The two sets of claims are evaluated to determine authorization.

Evaluation rules

Here we will describe the rules that govern whether the calling party, defined below, is authorized to access the given protocol. A request to access a protocol can come either via an API request or internally from other NPL code. Claims evaluation occurs both for internal and external requests. These two method of interacting with protocols are largely identical but there are some minor differences -- see API Access Evaluation (external) and NPL Access Evaluation (internal). Common rules to both will be discussed below.

Terms

Calling party: The party given when executing a protocol action or accessing one of its properties. In this example, ISSUER would be the calling party when executing the pay action.

var iou = Iou[ISSUER, PAYEE](100);
iou.pay[ISSUER](50);

Bound party: A bound party is the party that is specified during creation of the protocol or permission. In this example, the bound parties are issuer and payee.

protocol[issuer, payee] Iou(var forAmount: Number) {
    permission[issuer] pay(amount: Number) {
        // payment logic
    };
};

Bound claims: Bound claims are the claims of the bound party specified at instantiation of the protocol.

If a party has access to execute protocol actions, they will also have access to fields of that protocol.

How claims are evaluated

The calling party's claims must be a superset of the entity claims of the bound party, which means it must contain all the key values of the entity claims but could contain more. If the entity key is found in the calling party claims, the values too must be a superset of the entity values.

The rules for the access evaluation of claims are slightly different. Like with entity claims, the calling party's claims keys must be a superset of the bound party's access claim keys. However, for every matched key, the intersection of the two values sets must have at least one overlapping value.

See party methods to see how to access claim elements or to test for claim compatibility.

Note: In some cases, it may be desired that a protocol's members be publicly accessible. If declared, observers with no entity or access claims can be used for this purpose.

Evaluation process

Let's look at the process step by step.

Evaluating a simple protocol

We have a simple protocol, called externally, that has no references to any other protocol.

Joe has a reference to Iou and wants to execute the protocol action, pay:

protocol[issuer, payee] Iou(var forAmount: Number) {
    permission[issuer] pay(amount: Number) {
        // payment logic
    };
};

Claims bound to the issuer protocol party:

{
  "entity": {
    "company": ["client-company"]
  },
  "access": {
    "department": ["sales"]
  }
}

Since Joe is accessing the system externally, he is first authenticated by the identity management system and a JWT token is returned. Joe's claims are contained in the JWT.

Joe's raw JWT:

{
  "exp": 1673864644,
  "iat": 1673864344,
  "iss": "http://idm.com/token",
  "sub": "54e593fe-a928",
  "name": "Joe",
  "email": "joe@client-company.com",
  "email_verified": false,
  "given_name": "",
  "family_name": "",
  "company": "client-company",
  "position": ["ceo", "sales"],
  "department": ["executive", "sales"]
}

The JWT values are stringified (for non-string values) and converted to sets. Several JWT standard reserved claims are removed, and claims with empty values are omitted.

Joe's converted claims:

{
  "iss": ["http://idm.com/token"],
  "name": ["Joe"],
  "email": ["joe@client-company.com"],
  "email_verified": ["false"],
  "company": ["client-company"],
  "position": ["ceo", "sales"],
  "department": ["executive", "sales"]
}

These converted claims will now be used to evaluate if Joe is authorized to execute the permission on Iou.

Once access is given after a successful authorization check, the party will take on the identity of the bound party. This means, that the claims will be identical to the claims bound to issuer, listed above, not Joe's original claims. This also means that Joe is now identified as issuer and all actions in this permission will be executed as issuer with the associated claims.

In other words, even though Joe can play many roles (as reflected in position and/or department claims) in client-company, he will now be limited only to the roles that issuer has access.

The set of claims are now converted to their NPL representation containing both entity and access claims.

Converted claims:

{
  "entity": {
    "company": ["client-company"],
    "position": ["sales"]
  },
  "access": {
    "department": ["sales"]
  }
}

API boundary claims versus NPL boundary claims

It is important to remember, even though claims are represented within NPL as entity and access, the claims are once again merged during the claims evaluation process. This has the following effect:

  • Whenever claims evaluation happens, the union of the bound party claims will only be equivalent to or a subset of the union of the calling party claims.
  • During evaluation, entity and access claims are not constrained as such. Meaning, that an entity claim on the calling party could become an access claim on the bound party. Given partyA and partyB below, consider the following scenario. You have a protocol with the bound party, partyA, that calls an action on another, with bound party partyB. In this case, partyA will pass the claims evaluation as both partyA and partyB because the union set of the claims will match both parties.
  • As illustrated in the above example, multiple party matches are possible within NPL. In the event that two parties exist on one protocol where the calling party matches both, the first matching party is arbitrarily chosen, unless the tie-breaker is specified using the caller parameter. See the "exercise action request" engine API endpoint.

partyA claims:

{
  "entity": {
    "company": ["client-company"],
    "location": ["california"]
  },
  "access": {
    "department": ["security"],
    "role": ["guard"]
  }
}

partyB claims:

{
  "entity": {
    "department": ["security"],
    "role": ["guard"]
  },
  "access": {
    "company": ["client-company"],
    "location": ["california"]
  }
}

Evaluating a protocol referencing another protocol

Joe has a reference to ProtocolB and wants to execute actionB. The permission actionB attempts to access a permission actionA on ProtocolA.

Example of ProtocolB referencing actionA on ProtocolA:

  protocol[partyA] ProtocolA() {
      @api
      permission[partyA] actionA() {
          // do something
      };
  }

  protocol[partyB] ProtocolB(var protoRef: ProtocolA) {
      @api
      permission[partyB] actionB() {
          protoRef.actionA[partyB](); // references action on protocolA
      };
  }

The claims bound to the protocol party (partyB) are:

{
  "entity": {
    "company": ["client-company"],
    "position": ["sales"]
  },
  "access": {
    "department": ["sales"]
  }
}

The authorization process is the same with a slight difference with regard to the claims.

In the previous scenario, the claims are provided by the JWT. In this scenario, the invocation is internal so the claims are resolved by the system. The claims returned are those bound to the protocol party.

partyB claims are matched against partyA claims to determine compatibility. The claims are not evaluated as entity and access but are merged into a single map of claims prior to evaluation. Once merged, the matching process is identical.