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 roles
. Fauna assigns
these roles to the provider’s JWTs. Each role can include a predicate:
access provider SomeIssuer {
...
// If the predicate is `true`,
// assign the `customer` role to the provider's JWTs.
role customer {
// Check that JWT's `scope` includes `customer`
predicate (jwt => jwt!.scope.includes("customer"))
}
}
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 update()
to add metadata to a token
document:
// Get an existing credential for a `Manager` collection document.
let credential = Credential.byDocument(Manager.byId("<DOCUMENT_ID>"))
// 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!