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
andpartyB
below, consider the following scenario. You have a protocol with the bound party,partyA
, that calls an action on another, with bound partypartyB
. In this case,partyA
will pass the claims evaluation as bothpartyA
andpartyB
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.