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: 1649797456750000,
history_days: 30,
name: 'Users'
},
{
ref: Collection("Customers"),
ts: 1649797456750000,
history_days: 30,
name: 'Customers'
}
]
[{'ref': Ref(id=Users, collection=Ref(id=collections)), 'ts': 1650393167970000, 'history_days': 30, 'name': 'Users'}, {'ref': Ref(id=Customers, collection=Ref(id=collections)), 'ts': 1650393167970000, 'history_days': 30, 'name': 'Customers'}]
[map[history_days:30 name:Users ref:{Users 0xc00028a240 0xc00028a240 <nil>} ts:1649797447170000] map[history_days:30 name:Customers ref:{Customers 0xc00028a360 0xc00028a360 <nil>} ts:1649797447170000]]
Arr(ObjectV(ref: RefV(id = "Users", collection = RefV(id = "collections")),ts: LongV(1649797443800000),history_days: LongV(30),name: StringV(Users)), ObjectV(ref: RefV(id = "Customers", collection = RefV(id = "collections")),ts: LongV(1649797443800000),history_days: LongV(30),name: StringV(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: 1649808534450000,
data: { name: 'Donna Merrick', role: 'manager', department: 'HR' }
},
{
ref: Ref(Collection("Users"), "2"),
ts: 1649808534450000,
data: { name: 'John Morales', role: 'recruiter', department: 'HR' }
},
{
ref: Ref(Collection("Users"), "3"),
ts: 1649808534450000,
data: {
name: 'Sam Grant',
role: 'manager',
department: 'Data Analytics'
}
},
{
ref: Ref(Collection("Users"), "4"),
ts: 1649808534450000,
data: {
name: 'Arlene Lee',
role: 'analyst',
department: 'Data Analytics'
}
}
]
[{'ref': Ref(id=1, collection=Ref(id=Users, collection=Ref(id=collections))), 'ts': 1650393168950000, 'data': {'name': 'Donna Merrick', 'role': 'manager', 'department': 'HR'}}, {'ref': Ref(id=2, collection=Ref(id=Users, collection=Ref(id=collections))), 'ts': 1650393168950000, 'data': {'name': 'John Morales', 'role': 'recruiter', 'department': 'HR'}}, {'ref': Ref(id=3, collection=Ref(id=Users, collection=Ref(id=collections))), 'ts': 1650393168950000, 'data': {'name': 'Sam Grant', 'role': 'manager', 'department': 'Data Analytics'}}, {'ref': Ref(id=4, collection=Ref(id=Users, collection=Ref(id=collections))), 'ts': 1650393168950000, 'data': {'name': 'Arlene Lee', 'role': 'analyst', 'department': 'Data Analytics'}}]
[map[data:map[department:HR name:Donna Merrick role:manager] ref:{1 0xc000184570 0xc000184570 <nil>} ts:1649805254620000] map[data:map[department:HR name:John Morales role:recruiter] ref:{2 0xc000184780 0xc000184780 <nil>} ts:1649805254620000] map[data:map[department:Data Analytics name:Sam Grant role:manager] ref:{3 0xc000184990 0xc000184990 <nil>} ts:1649805254620000] map[data:map[department:Data Analytics name:Arlene Lee role:analyst] ref:{4 0xc000184ba0 0xc000184ba0 <nil>} ts:1649805254620000]]
Arr(ObjectV(ref: RefV(id = "1", collection = RefV(id = "Users", collection = RefV(id = "collections"))),ts: LongV(1649894780890000),data: ObjectV(name: StringV(Donna Merrick),role: StringV(manager),department: StringV(HR))), ObjectV(ref: RefV(id = "2", collection = RefV(id = "Users", collection = RefV(id = "collections"))),ts: LongV(1649894780890000),data: ObjectV(name: StringV(John Morales),role: StringV(recruiter),department: StringV(HR))), ObjectV(ref: RefV(id = "3", collection = RefV(id = "Users", collection = RefV(id = "collections"))),ts: LongV(1649894780890000),data: ObjectV(name: StringV(Sam Grant),role: StringV(manager),department: StringV(Data Analytics))), ObjectV(ref: RefV(id = "4", collection = RefV(id = "Users", collection = RefV(id = "collections"))),ts: LongV(1649894780890000),data: ObjectV(name: StringV(Arlene Lee),role: StringV(analyst),department: StringV(Data Analytics))))
[
{
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"), "328780674072838656"),
ts: 1649808534590000,
data: {
name: 'Acme Widgets, Inc.',
location: 'Hoboken, NJ',
YTD_orders: 35
}
}
{'ref': Ref(id=329393708633948672, collection=Ref(id=Customers, collection=Ref(id=collections))), 'ts': 1650393169910000, 'data': {'name': 'Acme Widgets, Inc.', 'location': 'Hoboken, NJ', 'YTD_orders': 35}}
map[data:map[YTD_orders:35 location:Hoboken, NJ name:Acme Widgets, Inc.] ref:{328778607104098816 0xc00009bb30 0xc00009bb30 <nil>} ts:1649806563380000]
ObjectV(ref: RefV(id = "328871582255546880", collection = RefV(id = "Customers", collection = RefV(id = "collections"))),ts: LongV(1649895231390000),data: ObjectV(name: StringV(Acme Widgets, Inc.),location: StringV(Hoboken, NJ),YTD_orders: LongV(35)))
{
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: 1659657764790000,
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 } } ]
}
{'ref': Ref(id=DA-reader, collection=Ref(id=roles)), 'ts': 1650393170870000, 'name': 'DA-reader', 'membership': [{'resource': Ref(id=Customers, collection=Ref(id=collections)), 'predicate': Query({'api_version': '4', 'lambda': ['ref'], 'expr': {'equals': ['Data Analytics', {'select': ['data', 'department'], 'from': {'get': {'var': 'ref'}}}]}})}], 'privileges': [{'resource': Ref(id=Customers, collection=Ref(id=collections)), 'actions': {'read': True}}]}
map[membership:map[predicate:{[123 34 97 112 105 95 118 101 114 115 105 111 110 34 58 34 52 34 44 34 108 97 109 98 100 97 34 58 91 34 114 101 102 34 93 44 34 101 120 112 114 34 58 123 34 101 113 117 97 108 115 34 58 91 34 68 97 116 97 32 65 110 97 108 121 116 105 99 115 34 44 123 34 115 101 108 101 99 116 34 58 91 34 100 97 116 97 34 44 34 100 101 112 97 114 116 109 101 110 116 34 93 44 34 102 114 111 109 34 58 123 34 103 101 116 34 58 123 34 118 97 114 34 58 34 114 101 102 34 125 125 125 93 125 125]} resource:{Customers 0xc00019c2d0 0xc00019c2d0 <nil>}] name:DA-reader privileges:map[actions:map[read:true] resource:{Customers 0xc00019c420 0xc00019c420 <nil>}] ref:{DA-reader 0xc00019c1b0 0xc00019c1b0 <nil>} ts:1649806708100000]
ObjectV(ref: RefV(id = "DA-reader", collection = RefV(id = "roles")),ts: LongV(1649895545340000),name: StringV(DA-reader),membership: Arr(ObjectV(resource: RefV(id = "Customers", collection = RefV(id = "collections")),predicate: QueryV(System.Collections.Generic.Dictionary`2[System.String,FaunaDB.Query.Expr]))),privileges: Arr(ObjectV(resource: RefV(id = "Customers", collection = RefV(id = "collections")),actions: ObjectV(read: BooleanV(True)))))
{
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: 1659657612120000,
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 }
}
]
}
{'ref': Ref(id=DA-manager, collection=Ref(id=roles)), 'ts': 1650393171820000, 'name': 'DA-manager', 'membership': [{'resource': Ref(id=Customers, collection=Ref(id=collections)), 'predicate': Query({'api_version': '4', 'lambda': ['ref'], 'expr': {'and': [{'equals': ['Data Analytics', {'select': ['data', 'department'], 'from': {'get': {'var': 'ref'}}}]}, {'equals': ['manager', {'select': ['data', 'role'], 'from': {'get': {'var': 'ref'}}}]}]}})}], 'privileges': [{'resource': Ref(id=Customers, collection=Ref(id=collections)), 'actions': {'read': True, 'write': True, 'create': True, 'delete': True}}]}
map[membership:map[predicate:{[123 34 97 112 105 95 118 101 114 115 105 111 110 34 58 34 52 34 44 34 108 97 109 98 100 97 34 58 91 34 114 101 102 34 93 44 34 101 120 112 114 34 58 123 34 97 110 100 34 58 91 123 34 101 113 117 97 108 115 34 58 91 34 68 97 116 97 32 65 110 97 108 121 116 105 99 115 34 44 123 34 115 101 108 101 99 116 34 58 91 34 100 97 116 97 34 44 34 100 101 112 97 114 116 109 101 110 116 34 93 44 34 102 114 111 109 34 58 123 34 103 101 116 34 58 123 34 118 97 114 34 58 34 114 101 102 34 125 125 125 93 125 44 123 34 101 113 117 97 108 115 34 58 91 34 109 97 110 97 103 101 114 34 44 123 34 115 101 108 101 99 116 34 58 91 34 100 97 116 97 34 44 34 114 111 108 101 34 93 44 34 102 114 111 109 34 58 123 34 103 101 116 34 58 123 34 118 97 114 34 58 34 114 101 102 34 125 125 125 93 125 93 125 125]} resource:{Customers 0xc00019e3c0 0xc00019e3c0 <nil>}] name:DA-manager privileges:map[actions:map[create:true delete:true read:true write:true] resource:{Customers 0xc00019e510 0xc00019e510 <nil>}] ref:{DA-manager 0xc00019e2a0 0xc00019e2a0 <nil>} ts:1649807242590000]
ObjectV(ref: RefV(id = "DA-manager", collection = RefV(id = "roles")),ts: LongV(1649896118560000),name: StringV(DA-manager),membership: Arr(ObjectV(resource: RefV(id = "Customers", collection = RefV(id = "collections")),predicate: QueryV(System.Collections.Generic.Dictionary`2[System.String,FaunaDB.Query.Expr]))),privileges: Arr(ObjectV(resource: RefV(id = "Customers", collection = RefV(id = "collections")),actions: ObjectV(read: BooleanV(True)))))
{
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: 1659657612200000,
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 }
}
]
}
{'ref': Ref(id=HR-manager, collection=Ref(id=roles)), 'ts': 1650393172790000, 'name': 'HR-manager', 'membership': [{'resource': Ref(id=Users, collection=Ref(id=collections)), 'predicate': Query({'api_version': '4', 'lambda': ['ref'], 'expr': {'and': [{'equals': ['HR', {'select': ['data', 'department'], 'from': {'get': {'var': 'ref'}}}]}, {'equals': ['manager', {'select': ['data', 'role'], 'from': {'get': {'var': 'ref'}}}]}]}})}], 'privileges': [{'resource': Ref(id=Users, collection=Ref(id=collections)), 'actions': {'read': True, 'write': True, 'create': True, 'delete': True}}]}
map[membership:map[predicate:{[123 34 97 112 105 95 118 101 114 115 105 111 110 34 58 34 52 34 44 34 108 97 109 98 100 97 34 58 91 34 114 101 102 34 93 44 34 101 120 112 114 34 58 123 34 97 110 100 34 58 91 123 34 101 113 117 97 108 115 34 58 91 34 72 82 34 44 123 34 115 101 108 101 99 116 34 58 91 34 100 97 116 97 34 44 34 100 101 112 97 114 116 109 101 110 116 34 93 44 34 102 114 111 109 34 58 123 34 103 101 116 34 58 123 34 118 97 114 34 58 34 114 101 102 34 125 125 125 93 125 44 123 34 101 113 117 97 108 115 34 58 91 34 109 97 110 97 103 101 114 34 44 123 34 115 101 108 101 99 116 34 58 91 34 100 97 116 97 34 44 34 114 111 108 101 34 93 44 34 102 114 111 109 34 58 123 34 103 101 116 34 58 123 34 118 97 114 34 58 34 114 101 102 34 125 125 125 93 125 93 125 125]} resource:{Users 0xc0001963c0 0xc0001963c0 <nil>}] name:HR-manager privileges:map[actions:map[create:true delete:true read:true write:true] resource:{Users 0xc000196510 0xc000196510 <nil>}] ref:{HR-manager 0xc0001962a0 0xc0001962a0 <nil>} ts:1649807589810000]
ObjectV(ref: RefV(id = "HR-manager", collection = RefV(id = "roles")),ts: LongV(1649896122590000),name: StringV(HR-manager),membership: Arr(ObjectV(resource: RefV(id = "Users", collection = RefV(id = "collections")),predicate: QueryV(System.Collections.Generic.Dictionary`2[System.String,FaunaDB.Query.Expr]))),privileges: Arr(ObjectV(resource: RefV(id = "Users", collection = RefV(id = "collections")),actions: ObjectV(read: BooleanV(True),write: BooleanV(True),create: BooleanV(True),delete: BooleanV(True)))))
{
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: 1649885114250000,
name: 'HR-default',
membership: [ { resource: Collection("Users") } ],
privileges: []
}
{'ref': Ref(id=HR-default, collection=Ref(id=roles)), 'ts': 1650393173730000, 'name': 'HR-default', 'membership': [{'resource': Ref(id=Customers, collection=Ref(id=collections))}], 'privileges': []}
map[membership:map[resource:{Users 0xc0001845d0 0xc0001845d0 <nil>}] name:HR-default privileges:[] ref:{HR-default 0xc0001844b0 0xc0001844b0 <nil>} ts:1649807812980000]
ObjectV(ref: RefV(id = "HR-default", collection = RefV(id = "roles")),ts: LongV(1649896126620000),name: StringV(HR-default),membership: Arr(ObjectV(resource: RefV(id = "Users", collection = RefV(id = "collections")))),privileges: Arr())
{
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(), "328860973817397760"),
ts: 1649885114400000,
instance: Ref(Collection("Users"), "2"),
secret: 'fnEEkFlQNOACAASQWU7fgAYAnd2xJuc12gScwCVR80zXrcw4EqE'
}
{'ref': Ref(id=329393713593713152, collection=Ref(id=tokens)), 'ts': 1650393174650000, 'instance': Ref(id=2, collection=Ref(id=Users, collection=Ref(id=collections))), 'secret': 'fnEEkj3WWgACAASSPdDv8AoAS0PjXY27oLkqMpzF5NjOqbdezrU'}
map[instance:{2 0xc00007a660 0xc00007a660 <nil>} ref:{328780082127569408 0xc00007a4b0 0xc00007a4b0 <nil>} secret:fnEEkA--JHACAASQD7oWgAYAHWBLh3AgoNES_iRvgrRJWPC_yv0 ts:1649807970080000]
ObjectV(ref: RefV(id = "328872525113065984", collection = RefV(id = "tokens")),ts: LongV(1649896130580000),instance: RefV(id = "2", collection = RefV(id = "Users", collection = RefV(id = "collections"))),secret: StringV(fnEEkGPRs8ACAASQY7pGUAYAQAbs9IAG99zraRTqLOwM2ZoGxjs))
{
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!