Role-based authentication
The following procedure demonstrates setting up User-defined roles and assigning them to UDFs for better data security.
For purposes of illustration, this tutorial imagines an online service called Weather Data, in which users can read about and report on weather information around the world. The service has three kinds of users:
-
Readers, who can only read the available information.
-
Reporters, who can read from and write to the database.
-
Managers, who can create new users and also have read/write access to the database.
The service has three UDFs:
-
Get_weather_reports
, which anyone can call. -
Create_weather_report
, which only Reporters can call. -
Create_weather_user
, which only Managers can call.
To follow along with this procedure, you need a Fauna account. For help with setting up a Fauna account, see the Dashboard quick start.
Step 1: Create a new database
Navigate to the Fauna Dashboard and create a new database called
weather_data
.
Once your database is created, you can interact with it either with a
driver, with the Dashboard’s 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 Fauna Query Language for use in the
Dashboard’s Shell or fauna-shell
reference.
Step 2: Create two new collections
Create a new collection called weather_reports
and another one
called weather_users
:
[
{
ref: Collection("weather_reports"),
ts: 1648443426630000,
history_days: 30,
name: 'weather_reports'
},
{
ref: Collection("weather_users"),
ts: 1648443426630000,
history_days: 30,
name: 'weather_users'
}
]
Step 3: Create UDFs
We need three UDFs to perform the functions of this application. The
first one is called Get_weather_reports
, and it uses the
Documents
function to retrieve all the records in the
weather_reports
collection.
We haven’t created the required roles yet, so these UDFs
are created without the role definitions. Once the roles have
been created, we can update them to add the role definitions.
|
{
ref: Function("Get_weather_reports"),
ts: 1648443426910000,
name: 'Get_weather_reports',
body: Query(Lambda([], Map(Paginate(Documents(Collection("weather_reports"))), Lambda("report", Get(Var("report"))))))
}
The next function, Create_weather_report
, allows Reporters to create
new documents in the weather_reports
collection.
{
ref: Function("Create_weather_report"),
ts: 1648443427200000,
name: 'Create_weather_report',
body: Query(Lambda(["date", "city", "country", "temperature"], Create(Collection("weather_reports"), {"data": {"date": Var("date"), "city": Var("city"), "country": Var("country"), "temperature": Var("temperature")}})))
}
The last UDF, Create_weather_user
, allows Managers to create new
documents in the weather_users
collection. Note that the password
field is passed via the credentials
parameter, which causes the
password string to be stored as a one-way cryptographic hash that is
subsequently used to authenticate the user. For more information, see
Credentials.
{
ref: Function("Create_weather_user"),
ts: 1648443427490000,
name: 'Create_weather_user',
body: Query(Lambda(["name", "email", "password"], Create(Collection("weather_users"), {"credentials": {"password": Var("password")}, "data": {"email": Var("email"), "name": Var("name")}})))
}
Step 4: Create new user-defined roles
The next step is to create roles with narrowly-defined privileges to execute the UDFs.
First, create a new role called Reader
with the read
privilege on
the weather_reports
collection and the call
privilege on the
Get_weather_reports
function:
{
ref: Role("Reader"),
ts: 1648443427780000,
name: 'Reader',
privileges: [
{
resource: Collection("weather_reports"),
actions: { read: true }
},
{
resource: Function("Get_weather_reports"),
actions: { call: true }
}
]
}
Next, create a role named Reporter
. This role can create new documents
in weather_reports
, read from weather_reports
, and call the
Create_weather_report
and Get_weather_reports
functions.
{
ref: Role("Reporter"),
ts: 1648443428070000,
name: 'Reporter',
privileges: [
{
resource: Collection("weather_reports"),
actions: { read: true, create: true }
},
{
resource: Function("Get_weather_reports"),
actions: { call: true }
},
{
resource: Function("Create_weather_report"),
actions: { call: true }
}
]
}
Lastly, create a role named Manager
. This role has read
and create
privileges on the weather_users
collection, read
privileges on
weather_reports
, and can call Create_weather_user
and
Get_weather_reports
.
{
ref: Role("Manager"),
ts: 1648443428350000,
name: 'Manager',
privileges: [
{
resource: Collection("weather_reports"),
actions: { read: true }
},
{
resource: Collection("weather_users"),
actions: { read: true, create: true }
},
{
resource: Function("Get_weather_reports"),
actions: { call: true }
},
{
resource: Function("Create_weather_user"),
actions: { call: true }
}
]
}
Now your roles are all established, and you can assign them to the UDFs you created earlier.
Step 5: Assign roles to UDFs
Use the Update
function to update your UDFs to have associated
roles.
The following query:
-
Updates the
Get_weather_reports
function to have theReader
role. -
Updates the
Create_weather_report
function to have theReporter
role. -
Updates the
Create_weather_user
function to have theManager
role.
[
{
ref: Function("Get_weather_reports"),
ts: 1648443428630000,
name: 'Get_weather_reports',
body: Query(Lambda([], Map(Paginate(Documents(Collection("weather_reports"))), Lambda("report", Get(Var("report")))))),
role: Role("Reader")
},
{
ref: Function("Create_weather_report"),
ts: 1648443428630000,
name: 'Create_weather_report',
body: Query(Lambda(["date", "city", "country", "temperature"], Create(Collection("weather_reports"), {"data": {"date": Var("date"), "city": Var("city"), "country": Var("country"), "temperature": Var("temperature")}}))),
role: Role("Reporter")
},
{
ref: Function("Create_weather_user"),
ts: 1648443428630000,
name: 'Create_weather_user',
body: Query(Lambda(["name", "email", "password"], Create(Collection("weather_users"), {"credentials": {"password": Var("password")}, "data": {"email": Var("email"), "name": Var("name")}}))),
role: Role("Manager")
}
]
Step 6: Create API keys
If you are following along using the Dashboard’s Shell, you can skip ahead to Step 7: Create documents with the UDFs. |
Client applications which access your Fauna database with the secret from a key or a token. A secret is a password-equivalent that connects the application to a specific database using role-based privileges.
Each key that you create should be assigned a role with sufficient privileges to do its intended job, and no more. Tokens have no implicit privileges, and require creation of custom roles to grant privileges.
The following query creates three keys, having the roles we need for this example:
[
{
ref: Ref(Keys(), "327349257024569856"),
ts: 1648443428920000,
role: Role("Reader"),
secret: 'fnAEivpqNnACAEYLR13MyfbKJCtIE89XzF-uVouT',
hashed_secret: '$2a$05$92XljLnZx617a.1OtwGOM.qiDvCqlX9rBPCsvKCchDgs2SUblJXhO'
},
{
ref: Ref(Keys(), "327349257024570880"),
ts: 1648443428920000,
role: Role("Reporter"),
secret: 'fnAEivpqNnAGANdBiR5wMXWfDA7srJboP56W9BJ3',
hashed_secret: '$2a$05$7h8izslky8S2R7J5k4GkwO2nQy60YjABf/ZZQGV.xd9mPx3AtEyDG'
},
{
ref: Ref(Keys(), "327349257024571904"),
ts: 1648443428920000,
role: Role("Manager"),
secret: 'fnAEivpqNnAKAKvpBgWBqozEZ-MUmtysS2ds8Qzi',
hashed_secret: '$2a$05$IC5MLu9d.shgWt0Mzfe/5uJ5u1myYtj4qUhMI5mFaf.BRtiMqE1I.'
}
]
A key’s secret is only displayed once, so be sure to save it in a safe place. |
If you’d like to do the same in the Fauna Dashboard:
-
Click the SECURITY link in the left-side navigation.
-
Click the NEW KEY link.
-
Select an appropriate role from the ROLE dropdown menu.
-
Give the key a name which describes its intended function.
-
Click SAVE.
Step 7: Create documents with the UDFs
So far, neither of the two collections have any documents. Now you can use your UDFs to add some documents.
Create a weather user document
In the Dashboard
-
Click SHELL in the left-side navigation.
-
Click the RUN AS button near the bottom of the screen. Select
Manager
from the dropdown menu. -
Enter the following query and click the RUN QUERY AS button:
{ ref: Ref(Collection("weather_users"), "327349258408690176"), ts: 1648443430240000, data: { email: 'alice@example.com', name: 'Alice Smith' } }
Note that if you change the RUN AS dropdown menu to Reader
and
run the query again it fails, because the Reader
role does not have
the necessary privilege to run the Create_weather_user
UDF.
Create a weather report
In the Dashboard
-
If the Shell screen is not in view, click SHELL in the left-side navigation.
-
Change the RUN AS dropdown menu to
Reporter
and run the following query:{ ref: Ref(Collection("weather_reports"), "327349259058807296"), ts: 1648443430860000, data: { date: '2022-02-24', city: 'Paris', country: 'France', temperature: '42 degrees F' } }
Step 8: Fetch existing weather reports
In the Dashboard
-
If the Shell screen is not in view, click SHELL in the left-side navigation.
-
Change the RUN AS dropdown menu to
Reader
and run the following query:{ data: [ { ref: Ref(Collection("weather_reports"), "327349259058807296"), ts: 1648443430860000, data: { date: '2022-02-24', city: 'Paris', country: 'France', temperature: '42 degrees F' } } ] }
You can also run this query as either of the other roles, because they
all have the Call
privilege for it.
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!