Attribute-based access control (ABAC)
Attribute-based access control (ABAC) is a security model that conditionally grants access to resources based on attributes. In Fauna, these attributes can relate to the:
-
User or system requesting access, if you’re using tokens
-
Resource being accessed
-
Operation to perform and its effect
-
Environment, such as date or time of day
RBAC vs. ABAC
Dynamic ABAC in Fauna
In Fauna, you implement ABAC using role-related predicates. You can use the predicates to:
Fauna evaluates and assigns a secret’s roles and privileges, including any predicates, at query time for every query. This lets you grant access based on real-time user data and the environment.
Dynamically assign roles
You can dynamically assign roles to:
Dynamically assign roles to JWTs
When you define an access
provider schema, you can specify one or more role
properties. Fauna assigns
these roles to the provider’s JWTs. Each role
property can include a
predicate:
access provider someIssuer {
...
// If the predicate is `true`,
// assign the `manaager` role to the provider's JWTs.
role manager {
// Check that the JWT's `scope` includes `manager`.
predicate (jwt => jwt!.scope.includes("manager"))
}
}
The predicate is passed one argument: an object containing the JWT’s payload.
Dynamically assign roles to tokens
Fauna assigns roles to a token based on its identity document’s collection and
the role schema's membership
properties. Each membership
property can include a
membership
predicate:
role customer {
// If the predicate is `true`,
// assign the `customer` role to tokens with
// identity documents in the `Customer` collection.
membership Customer {
// Checks the `accessLevel` field in the
// token's identity document.
predicate (idDoc => idDoc.accessLevel == "customer")
}
}
The predicate is passed one argument: an object containing the token’s identity document.
Dynamically grant privileges
A role schema typically includes several
privileges
. Each privilege can include a
privilege predicate:
role customer {
privileges Order {
// If the predicate is `true`,
// grant `read` access to the `Order` collection.
read {
predicate (doc =>
// Check the order's `status` field.
doc.status != "Deleted"
)
}
// If the predicate is `true`,
// grant `write` access to the `Order` collection.
write {
predicate ((oldDoc, newDoc) =>
// Check the existing order's status.
oldDoc.status != "Deleted" &&
// Check that `customer` isn't changed by the write.
oldDoc.customer == newDoc.customer &&
// Check the current time.
// Allow access after 07:00 (7 AM).
Time.now().hour > 7 &&
// Disallow access after 20:00 (8 PM).
Time.now().hour < 20
)
}
}
}
Privilege predicates are passed different arguments based on the action the privilege grants. See Privilege predicate arguments.
Identity-based attributes
Tokens are tied to an identity document. You can fetch a token’s
identity document using the Query.identity()
method:
role customer {
privileges Order {
read {
// `Query.identity()` gets the token's identity document.
// The identity document typically represents a user or system.
// In this example, `doc.customer` is the order's customer.
// The predicate checks that the order belongs to the customer.
predicate (doc => Query.identity() == doc.customer)
}
}
}
Predicates can also check an identity document’s fields:
role customer {
privileges Order {
write {
predicate ((oldDoc, newDoc) =>
// Check that the user's `country` is in
// the updated order's `allowedCountries`.
newDoc.allowedCountries.includes( Query.identity()!.country) &&
// Disallow `write` access 10 hours after
// the last document timestamp.
Time.now().difference(oldDoc!.ts, "hours") < 10
)
}
}
}
For JWTs and keys, Query.identity()
returns
null
. JWTs and keys aren’t tied to an identity document and don’t support
identity-based attributes.
Token metadata
Fauna stores tokens as documents in the Token
system collection. This token
document is distinct from the token’s identity document.
A token document can include metadata in its data
field. You later check
this metadata in a predicate for ABAC.
Use token.update()
to add metadata to a token
document:
// Get an existing credential for a `Manager` collection document.
let credential = Credential.byDocument(Manager.byId("111"))
// Use `login()` to create a token using the credential and its password.
// Returns a document in the `Token` system collection.
let token = credential!.login("<PASSWORD>")
// Add metadata to the token document using the `data` property.
// The result doesn't include this property, but it's added.
token!.update({
data: {
clientIpAddr: "123.123.12.1"
}
})
Use Query.token()
to fetch the token document
for the transaction’s authentication token. You can then access the document’s
data
field:
role manager {
membership Manager {
// Assign the `manager` role if
// the token document's `clientIpAddr` metadata is
// in the manager's `approvedIpAddresses`.
predicate (_ =>
Query.identity()!.approvedIpAddresses
.includes(Query.token()!.data!.clientIpAddr)
)
}
}
Environmental attributes
You can use predicates to assign roles and grant privileges based on the current date or time.
Use Date.today()
to get the current date:
role manager {
membership Manager {
// Assign the `manager` role only on weekdays.
predicate (_ => Date.today().dayOfWeek < 6)
}
}
Use Time.now()
to get the current time:
role manager {
privileges Order {
write {
// Disallow `write` access if the user is logged in for
// more than 60 minutes.
predicate ((_, _) =>
Time.now().difference(Query.identity()!.login, "minutes") < 60
)
}
}
}
Is this article helpful?
Tell Fauna how the article can be improved:
Visit Fauna's forums
or email docs@fauna.com
Thank you for your feedback!