Build a user authentication system
This guide presents the fundamentals of implementing end-user registration and login using Fauna’s built in Credentials functionality.
We highlight the best-practice principle of least privilege, which Fauna allows you to easily implement into the set of resources needed to implement the use-case: You ship your client applications with a Fauna API key that only has limited privileges, being only able to access resources needed to register and login users, and nothing else.
The login implementation then grants authenticated users temporary access tokens needed to access Fauna collections. User-defined functions are the key to this implementation.
Solution overview
In this guide, you:
-
Create a new secret key in Fauna.
-
Configure a role for the secret key so that your client application can only invoke the User Registration and Login UDFs using this key.
-
Ship the secret key as an environment variable with your client application.
-
Run the User Registration UDF from the client application using the secret key to create a new user.
-
Run the Login UDF from the client application to acquire a user access token.
-
Use the user access token in the client application to interact with Fauna resources.
The following diagram demonstrates the overall authentication flow.
Getting started
Create a database:
-
Go to the Fauna Dashboard and sign in.
You can sign up for a free account at https://dashboard.fauna.com/register.
-
Click Create Database to create a database.
-
Give your database a Name.
-
Choose your preferred Region Group.
-
Click Create.
Your new database is listed on the Home page.
-
Navigate to your new database and create an
Account
collection with the following definition:collection Account { history_days 0 index byEmail { terms [.email] } unique [.email] }
User registration
Create a function to register new users using this function definition:
function UserRegistration(email, password) {
let acc = Account.create({
email: email
})
Credential.create({
document: acc,
password: password
})
}
Test the above function by running the following query in the shell:
{
id: "394172095713509409",
coll: Credential,
ts: Time("2024-04-03T18:57:33.970Z"),
document: Account("394172095708266529")
}
User login
Create a function to login the user. Use the following definition. Note there
is a ttl
argument included in the function (set to "now" + 3600 seconds).
This ensures that the generated token expires after the specified time.
function UserLogin(email, password) {
let acc = Account.byEmail(email)!.first()
Credential.byDocument(acc)!.login(password, Time.now().add(3600, "second"))
}
Test the login function by running the following query in the shell:
{
id: "394173555758071841",
coll: Token,
ts: Time("2024-04-03T19:20:46.380Z"),
ttl: Time("2024-04-03T20:20:46.245777Z"),
secret: "fnEFeGLCstAAIQV0h5ruEAAhi01pz5vM9bd6v64wkWPFG5U6T9U",
document: Account("394172095708266529")
}
Set up sample data
Create a basic collection called Movie
:
collection Movie {
history_days 0
}
Populate it with sample data. Note: you’ll only see feedback output for the last Create() expression. You can check the database to verify that 3 documents were created.
{
id: "394173959622362145",
coll: Movie,
ts: Time("2024-04-03T19:27:11.530Z"),
title: "Once Upon a Time in Hollywood",
director: "Quentin Tarantino",
release: "July 26, 2019"
}
Connect Fauna with the client application
An unauthenticated user should only be able to call the UserRegistration and UserLogin functions. To implement this behavior, you configure Roles in Fauna and assign permissions to the role. Then, create a Fauna API key with the role. This API key is then provided to the client application. This way, the client application can only use these 2 functions and nothing else.
Let’s walk through the steps:
-
Create Role
Create a Role named
UnAuthRole
with the following definition:role UnAuthRole { privileges UserRegistration { call } privileges UserLogin { call } privileges Account { read create } privileges Credential { read create } privileges Token { create } }
Inspect the definition above: In addition to providing the role with privileges to call the two functions, you must also provide privileges to read and create Credentials (which the functions perform), and finally, create Tokens.
-
Create an API key with the above role:
-
In the Explorer page, locate and click your database.
-
In the Keys tab, click Create Key.
-
Choose the
UnAuthRole
Role and provide a name for the key. -
Click the Save button.
-
Copy the Secret Key and save it somewhere safe as this is the only time you’ll be able to see it. If you lose it, you must create a new key.
-
-
Use the API key in the client application.
You ship this key as an environment variable in your client application. Your client application can use this key to call only the
UserRegistration
andUserLogin
functions. You can not access any other resources in Fauna with this key.To test this, navigate to the Dashboard Shell and at the bottom of the screen, populate Secret from the dropdown and enter the API KEY in the box to the right of it:
Then run the following command in the Dashboard Shell:
{ id: "394176192554467361", coll: Credential, ts: Time("2024-04-03T20:02:41.020Z"), document: Account("394176192551321633") }
Next, try the
UserLogin
function{ id: "394176345598328865", coll: Token, ts: Time("2024-04-03T20:05:06.975Z"), ttl: Time("2024-04-03T21:05:06.890484Z"), document: Account("394176192551321633"), secret: "fnEFeGVMQkAAIQV0h5ruEAAhQrZ3TKZzhtEQNoXAdIQmtpHaLSs" }
The output returns a secret, which is your temporary access token that can access other resources in Fauna. Note, the
ttl
should assume a value that you specified for it in the login function.Now, try a query that the
UnAuthRole
shouldn’t have access to:{ data: [] }
In the above response, an empty set was returned because you have not yet defined what resources this token has access to. In the next section, you learn how to give your user access tokens permission to certain resources.
Authenticated user role
Create a new role called AuthRole
with the following definition:
role AuthRole {
membership Account
privileges Movie {
create
read
write
}
}
Note the membership property, which associates the role with secrets
generated on behalf of Account
.
Run the UserLogin
function again, and from the result, copy the secret:
…and use the value to test accessing the Movie
collection:
Run the following command in the shell:
{
data: [
{
id: "394173959621312545",
coll: Movie,
ts: Time("2024-04-03T19:27:11.530Z"),
title: "Reservoir Dogs",
director: "Quentin Tarantino",
release: "Jan 21, 1992"
},
{
id: "394173959622361121",
coll: Movie,
ts: Time("2024-04-03T19:27:11.530Z"),
title: "The Hateful Eight",
director: "Quentin Tarantino",
release: "December 25, 2015"
},
{
id: "394173959622362145",
coll: Movie,
ts: Time("2024-04-03T19:27:11.530Z"),
title: "Once Upon a Time in Hollywood",
director: "Quentin Tarantino",
release: "July 26, 2019"
}
]
}
The results successfully return all the Movie
documents
User logout
Lastly, implement a logout function by defining the following function:
function UserLogout() {
Query.token()!.delete()
}
Update the AuthRole
to have access to the above function:
role AuthRole {
membership Account
privileges Movie {
create
read
write
}
privileges UserLogout {
call
}
}
Finally, test the UserLogout
function. Make sure that the shell
is still using a secret/token returned from a UserLogin() function:
…and run the following command in the shell:
Token("394177415326203937") /* deleted */
You should see that the token was successfully deleted.
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!