FQL v4 will be decommissioned on June 30, 2025. Ensure that you complete your migration from FQL v4 to FQL v10 by that date. For more details, review the migration guide. Contact support@fauna.com with any questions. |
Identity-based authentication
User-defined roles can have a membership attribute which describes the set of documents that should have the role’s privileges. You can use the membership attribute to control which identities can perform actions on particular collections, as well as other restrictions.
A membership definition can include a predicate function which is evaluated whenever a query is made against the covered documents. The following example procedure demonstrates how to use a membership attribute to restrict data access.
This examples imagines a company database with two collections called Users
and Customers
. The Users
collection contains documents with information
about company employees, including name, role, and department. The Customers
collection contains information about the company’s customers, including name,
location, and year-to-date revenue generated. Different employees have
different levels of access to the two collections based on their department
and role.
Step 1: Create a new database
Navigate to the Fauna Dashboard and create a new database called
company_data
.
Once your database is created, you can interact with it either with a
driver, with the Dashboard Shell, or with the
fauna-shell
command-line tool.
All of the subsequent steps in this example provide FQL queries in each
supported language, well as FQL for use in the Dashboard Shell or
fauna-shell
reference.
Step 2: Create two new collections
Create a new collection called Users
and another one
called Customers
:
[
{
ref: Collection("Users"),
ts: 1649797461320000,
history_days: 30,
name: 'Users'
},
{
ref: Collection("Customers"),
ts: 1649797461320000,
history_days: 30,
name: 'Customers'
}
]
Step 3: Create new users
The following example creates four documents in the Users
collection, each
with personal information and login credentials for each user.
[
{
ref: Ref(Collection("Users"), "1"),
ts: 1649803776970000,
data: { name: 'Donna Merrick', role: 'manager', department: 'HR' }
},
{
ref: Ref(Collection("Users"), "2"),
ts: 1649803776970000,
data: { name: 'John Morales', role: 'recruiter', department: 'HR' }
},
{
ref: Ref(Collection("Users"), "3"),
ts: 1649803776970000,
data: {
name: 'Sam Grant',
role: 'manager',
department: 'Data Analytics'
}
},
{
ref: Ref(Collection("Users"), "4"),
ts: 1649803776970000,
data: {
name: 'Arlene Lee',
role: 'analyst',
department: 'Data Analytics'
}
}
]
Step 4: Create customer data
The next example creates a document in the Customers
collection.
{
ref: Ref(Collection("Customers"), "328775685877268992"),
ts: 1649803777480000,
data: {
name: 'Acme Widgets, Inc.',
location: 'Hoboken, NJ',
YTD_orders: 35
}
}
Step 5: Create roles
This is where we set up our data access rules. First, set up two roles which
govern access to the Customers
collection:
-
Any user who is a member of the
Data Analytics
department can perform read operations. -
Users who are managers in the
Data Analytics
department can perform read, write, create, and delete operations.
The first role is called DA-reader
. It covers documents in the Customers
collection and contains a predicate function which
checks to see if the caller belongs to the Data Analytics
department.
{
ref: Role("DA-reader"),
ts: 1649803777990000,
name: 'DA-reader',
membership: [
{
resource: Collection("Customers"),
predicate: Query(Lambda(["ref"], Equals("Data Analytics", Select(["data", "department"], Get(Var("ref"))))))
}
],
privileges: [ { resource: Collection("Customers"), actions: { read: true } } ]
}
The second role is called DA-manager
. It also covers documents in the
Customers
collection, and its predicate function checks to see if the
caller is a manager in the Data Analytics
department.
{
ref: Role("DA-manager"),
ts: 1649803778490000,
name: 'DA-manager',
membership: [
{
resource: Collection("Customers"),
predicate: Query(Lambda(["ref"], And(Equals("manager", Select(["data", "role"], Get(Var("ref")))), Equals("Data analytics", Select(["data", "department"], Get(Var("ref")))))))
}
],
privileges: [
{
resource: Collection("Customers"),
actions: { read: true, write: true, create: true, delete: true }
}
]
}
Next, set up a role to govern access to the Users
collection. This one is
simpler: only managers in the HR
department can access the Users
collection.
{
ref: Role("HR-manager"),
ts: 1649803778980000,
name: 'HR-manager',
membership: [
{
resource: Collection("Users"),
predicate: Query(Lambda(["ref"], And(Equals("manager", Select(["data", "role"], Get(Var("ref")))), Equals("HR", Select(["data", "department"], Get(Var("ref")))))))
}
],
privileges: [
{
resource: Collection("Users"),
actions: { read: true, write: true, create: true, delete: true }
}
]
}
There is, however, a special property of Fauna’s ABAC implementation:
a document can always read and modify itself, unless prevented by a
higher-priority role definition. Under our current rules, a user such as
Sam Grant, who is not a manager in the HR department, would be able to read
and modify his own document in the Users
collection (but no one else’s). To
prevent this, we can create a role with no permissions on the Users
collection and no predicate function, so it applies to all callers:
{
ref: Role("HR-default"),
ts: 1649803779480000,
name: 'HR-default',
membership: [ { resource: Collection("Users") } ],
privileges: []
}
This role prevents all users from accessing the Users
collection by default.
Users who meet the criteria in the predicate function of the HR-manager
role
can still perform actions according to the privileges
attribute of that role.
For more information about ABAC behavior when multiple roles apply to the same
collection, see overlapping roles.
Step 6: Test the roles
To test the roles, you can use the Login
function to log in as any one
of the existing users, then check to see that the user can only access the
data in the collections as the roles allow.
The following example logs in with the password for John Morales:
{
ref: Ref(Tokens(), "328775688500806144"),
ts: 1649803779990000,
instance: Ref(Collection("Users"), "2"),
secret: 'fnEEkAu_K-ACAASQC7uuwAYABpPNOFZ1S-MxQKGlFZcl_9QcRBg'
}
Once you have the secret returned by the Login
function, you can use it
to perform actions as the identity associated with it.
Summary
Creating custom roles with narrowly-defined privileges is the best way to maintain access control in your databases and client applications.
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!