Check out v4 of the Fauna CLI
v4 of the Fauna CLI is now in beta. The new version introduces enhancements to the developer experience, including an improved authentication workflow. To get started, check out the CLI v4 quick start. |
Build an end-user authentication system
Learn: Credentials |
---|
You can use credentials and user-defined functions (UDFs) to create an end-user authentication system.
In this tutorial, you’ll build a user authentication system for an example e-commerce application. The system follows the principle of least privilege by:
-
Limiting the privileges of roles assigned to a user’s tokens
-
Only allowing unauthenticated users to access data through UDFs
Before you start
To complete this tutorial, you’ll need:
-
The Fauna CLI
-
Familiarity with the following Fauna features:
Setup
Set up a database with demo data.
-
Log in to the Fauna Dashboard and create a database. When creating the database, enable demo data.
-
If you haven’t already, log in to Fauna using the CLI:
fauna login
-
Create an
ecommerce
directory and navigate to it:mkdir ecommerce cd ecommerce
-
Create a
schema
directory:mkdir schema
-
Pull the database’s active schema to the local schema directory:
# Replace 'us' with your preferred Region Group: # 'us' (United States), 'eu' (Europe), or `global`. # Replace 'my_db' with your database's name. fauna schema pull \ --database us/my_db \ --dir ./schema
-
Navigate to the
schema
directory:cd schema
-
To simplify the tutorial, open
collections.fsl
and edit theCustomer
collection as follows:collection Customer { // Make `name` nullable. name: String? email: String // Make `address` nullable. address: { street: String city: String state: String postalCode: String country: String }? // Leave remaining schema as is.
Create user login functions
Create UDFs that let end users:
-
Sign up as application users by providing an email address and password
-
Log in to the application using their email and password
-
Log out of the application
-
In the schema directory, add the following function schema to the end of
functions.fsl
:... // Defines the `UserSignup()` UDF. Unauthenticated users can use // the UDF to create a `Customer` identity document and credential. @role("server") // Runs with the `server` role's privileges function UserSignup(email, password) { // Creates a `Customer` collection document. // The `Customer` document acts as an identity document that // represents the end user. let customer = Customer.create({ email: email }) // Creates a credential that associates the // `Customer` document with an end user's password. let credential = Credential.create({ document: customer, password: password }) // Outputs the credential as an object. Object.assign({ }, credential) } // Defines the `UserLogin()` UDF. Unauthenticated users can use the // UDF to create an token associated with their identity document. @role("server") // Runs with the `server` role's privileges. function UserLogin(email, password) { // Uses the `Customer` collection's `byEmail()` index to // get `Customer` collection documents by `email` field value. // In the `Customer` collection, `email` field values are unique // so return the `first()` (and only) document. let customer = Customer.byEmail(email)?.first() // Gets the credential for the above `Customer` document and // passes it to `login()` to create a token. // The `Customer` document is the token's identity document. // Set the token's `ttl` to 60 minutes from the current time at // query. The token's secret expires at its `ttl`. Credential.byDocument(customer) ?.login(password, Time.now().add(60, "minutes")) } // Defines the `UserLogout()` UDF. Authenticated users can use the // UDF to delete their authentication token. The user creates a // new token when they next log in. function UserLogout() { // `Query.token()` gets the token document for the // query's authentication token. Query.token()!.delete() }
Create a user-defined roles
Unauthenticated users should only be able to sign up or log in to the application.
Authenticated users should have read access to the application’s products. Authenticated users should also be able to log out of the application.
Update the database as follows:
-
Create a
customer
role with the ability to call theUserLogOut()
function. -
Create a
unauthenticated
role with the ability to call theUserSignup()
andUserLogin()
functions.
-
In the schema directory, create the
roles.fsl
file and add the following schema to it:// Defines the `customer` role. role customer { // Assign the `customer` role to tokens with // an identity document in the `Customer` collection. membership Customer // Grants `read` access to `Product` collection documents. privileges Product { read } // Grants the ability to call the `UserLogout()` UDF. privileges UserLogout { call } } // Defines the `unauthenticated` role. role unauthenticated { // Grants the ability to call the `UserSignup()` UDF. privileges UserSignup { call } // Grants the ability to call the `UserLogin()` UDF. privileges UserLogin { call } }
-
Save
collections.fsl
,functions.fsl
, androles.fsl
. Then push the schema to Fauna:fauna schema push \ --database us/my_db
When prompted, accept and stage the schema.
-
Check the status of the staged schema:
fauna schema status \ --database us/my_db
-
When the status is
ready
, commit the staged schema to the database:fauna schema commit \ --database us/my_db
The commit applies the staged schema to the database. This creates the new UDFs and role.
Create a key for unauthenticated users
Create a key that’s assigned the unauthenticated
role.
The key acts as a bootstrap to let unauthenticated users sign up or log in to
the application. You’d typically store the key’s secret in the FAUNA_SECRET
environment variable for use in a Fauna client driver.
-
Start a shell session in the Fauna CLI:
fauna shell \ --database us/my_db
-
Run the following FQL query:
Key.create({ role: "unauthenticated" })
Copy the returned key’s
secret
. You’ll use the secret later in the tutorial. -
Press Ctrl+D to exit the shell.
Test unauthenticated user access
Use the key’s secret to run queries as an unauthenticated user.
The key only has privileges for the login-related UDFs you created earlier. The key shouldn’t have access to product data.
-
Start a new shell session using the key’s secret:
fauna shell \ --secret <KEY_SECRET>
-
Enter editor mode to run multi-line queries:
> .editor
-
Run the following query:
// Attempt to read `Product` collection documents. Product.all()
The query returns an empty Set:
{ data: [] }
The
unauthenticated
role doesn’t grant access toProduct
collection documents.
Sign up and log in as an end user
Use the key to create a token tied to a user’s Customer
identity document.
Once created, the application can store the token’s secret and use it to authenticate Fauna queries on behalf of the user.
-
In the shell session, run the following query in editor mode:
// Calls the `UserSignup()` UDF. Passes the `email` for the // `Customer` identity document you created earlier as the first // argument. Passes the user's password as the second argument. UserSignup("john.doe@example.com", "sekret")
The results contains a permission-denied message. This is expected. The
unauthenticated
role doesn’t have read access toCustomer
documents. The function call still creates the related credential. -
Run the following query in editor mode:
// Calls the `UserLogin()` UDF. Passes the same arguments as the // earlier `UserSignup()` UDF call. UserLogin("john.doe@example.com", "sekret")
Copy the returned token’s
secret
. You’ll use the secret later in the tutorial. -
Press Ctrl+D to exit the shell.
Test authenticated user access
The user’s token can access product data. Use the token’s secret to access the data and then log out.
-
Start a new shell session using the token secret:
fauna shell \ --secret <TOKEN_SECRET>
-
Run the following query:
Product.all()
The query runs successfully:
{ data: [ { id: "111", coll: Product, ts: Time("2099-07-31T13:29:18.350Z"), name: "cups", description: "Translucent 9 Oz, 100 ct", price: 698, stock: 100, category: Category("123") }, ... ] }
-
Run the following query in editor mode:
// Calls the `UserLogout()` UDF. The call deletes the authentication // token and its secret. Users can create a new token and secret by // logging in again using `UserLogin()`. UserLogout()
The query runs successfully and indicates the token was 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!