User authentication in Fauna (an opinionated guide)
Tags: serverless, database, fauna, tutorial
Published: 2021-10-06 03:47:18

In this blog post, you learn the fundamentals of authenticating users in Fauna. You ship your client applications with a secret token from Fauna that has limited privileges. Ideally, this token can only register and login users in Fauna. Authenticated users then receive a temporary access token that they can use to access the Fauna resources securely.  [User-defined functions (UDFs)](https://docs.fauna.com/fauna/current/tutorials/basics/functions)  are the key to this implementation.

### Pre-requisites
Some familiarity with FQL will be helpful. You can still follow along without any prior knowledge of FQL. To learn more about FQL visit this  [series of articles](https://docs.fauna.com/fauna/current/tutorials/basics/functions) .

## Solution overview
In this post, you:
1. Create a new __secret key__ in Fauna.

2. Configure a role for the __secret key__ so that your client application can only invoke the *User Registration* and *Login* UDFs using this key.

3. Ship the secret key as an environment variable with your client application.

4. Run the *User Registration* UDF from the client application using the __secret key__ to create a new user.

5. Run the *Login* UDF from the client application to acquire a __user access token__.

6. Use the __user__ __access token__ in the client application to interact with Fauna resources. 

The following diagram demonstrates the overall authentication flow. 

![authentication flow diagram](//images.contentful.com/po4qc9xpmpuh/6BfopBQzhYDSAmaYjUtSvo/e062bf11d0286bd13e6296eae194b6b0/b53e8416-77f6-441a-af9e-d7ef728ce753.png)

## User registration
Head over to the  [Fauna dashboard](https://dashboard.fauna.com/)  and create a new database.

![Creating a new database](//images.contentful.com/po4qc9xpmpuh/7sM6mMqueWxf6rqk4RDoT0/bf8b2c7913c359ff36e807bd011832f0/2.png)

Select *Collections* and create a new collection called `Account`
![Creating a new collection](//images.contentful.com/po4qc9xpmpuh/5ASnIWuQ3YbSt6321zn2QD/eb4a897449a792510675104eaca24e6c/3.png)

The `Account` collection contains all user data. Navigate to *Indexes* and select *New Index*. Select `Account` as the source collection. Name the index `account_by_email`. You use this index to query users by their email address. In the Terms field, input `email`. Make sure to select the Unique option and the *Serialized* option to ensure that each user has a unique email address. Select *Save* to create the new index. 

![Creating a new index](//images.contentful.com/po4qc9xpmpuh/1HB5PYc19y7RBitG28zeiR/95b656f61c1dce5245bf90030e74f097/4.png)

Next, create a function to register new users. Select *Functions* from the dashboard menu and select *NEW FUNCTION* to create a new user-defined function (UDF).

![Creating a UDF](//images.contentful.com/po4qc9xpmpuh/64QNb6AulZJyApupAlVt3M/22ac27cc567c5bf5d562205652b87e00/5.png)

Name your function `UserRegistration` and enter the following code in the *Function Body*. You can leave the role as *None* for now. Select *Save* to create your function.

```jsx
Query(
    Lambda(["email", "password"],
        Create(Collection("Account"), {
            credentials: { password: Var("password") },
            data: {
                email: Var("email")
            }
        })
    )
)
```

![Creating a new user-defined function](//images.contentful.com/po4qc9xpmpuh/1ssWfJlkYYWlsxMODiIJGm/6eb57c613de0df139e8960a08a1304ff/6.png)

Navigate to the *Shell* and register a new user by calling the `UserRegistration` UDF. Enter the following code in the shell and select *Run Query*.

```jsx
Call("UserRegistration", "shadid@test.com", "pass123456")
```
![Calling a UDF from the dashboard shell](//images.contentful.com/po4qc9xpmpuh/1TY3AIZaHL4QInQeAIFtni/eff1eba02545187f148439d5c8b4e979/7.png)

Navigate back to *Collections > Account* and confirm that a new user is created.

## User login

Return to the Functions tab, create a new function, and name it `UserLogin`. Add the following code snippet to your function body and select Save. Notice there is a `ttl` argument in the `Login` function. This ensures that the generated token expires after the specified time.

```jsx
Query(
    Lambda(["email", "password"],
        Login(
            Match(Index("account_by_email"), Var("email")),
            { 
                password: Var("password"),
                ttl: TimeAdd(Now(), 3600, "seconds")
            },

        )
    )
)
```
Navigate back to the shell and call the function with the user's credentials.

```jsx
Call("UserLogin", "shadid@test.com", "pass123456")
```

The output of this function gives you a secret token. Following is a sample response from the UDF. Take a note of the secret.

```jsx
{
  ref: Ref(Ref("tokens"), "310382039289299523"),
  ts: 1632262229150000,
  ttl: Time("2021-09-21T22:20:29.019666Z"),
  instance: Ref(Collection("Account"), "310358409884992069"),
  secret: "fnEE....."
}
```
You can now use this secret token to access your Fauna resources from a client application. Verify the validity of the token by running the following `CURL` command in your terminal, replacing `<YOUR_TOKEN>` with the value of the secret your function returns.

```bash
curl https://db.fauna.com/tokens/self  -u <YOUR_TOKEN>
```

```bash
{
  "resource": {
    "ref": {
      "@ref": {
        "id": "310382039289299523",
        "class": { "@ref": { "id": "tokens" } }
      }
    },
    "ts": 1632262229150000,
    "ttl": { "@ts": "2021-09-21T22:20:29.019666Z" },
    "instance": {
      "@ref": {
        "id": "310358409884992069",
        "class": {
          "@ref": {
            "id": "Account",
            "class": { "@ref": { "id": "classes" } }
          }
        }
      }
    },
    "hashed_secret": "$2a$05$Ta2ScWEQhV39VTKLmmXXXOxnpQlXvloLSd3g9nP2Gsb.zJMP6QK6y"
  }
}
```

Next, navigate to Collections and create a new collection called `Movie`. Populate your collection with the following sample data.

```json
{
    "title": "Reservoir Dogs",
    "director": "Quentin Tarantino",
    "release": "Jan 21, 1992"
}

{
    "title": "The Hateful Eight",
    "director": "Quentin Tarantino",
    "release": "December 25, 2015"
}

{
    "title": "Once Upon a Time in Hollywood",
    "director": "Quentin Tarantino",
    "release": "July 26, 2019"
}

```
You will query this collection with the authenticated user's token in the next section.

## Connecting Fauna with the client application

Your client application should have limited access to your Fauna backend. An unauthenticated user should only be able to call `UserLogin` and `UserRegistration` functions from your client application. It is best practice to follow the  [principle of least privilege](https://en.wikipedia.org/wiki/Principle_of_least_privilege) .

Create a new role for your unauthenticated users. Navigate to *Security > Roles* and select *New Custom Role*.

![Creating a custom role](//images.contentful.com/po4qc9xpmpuh/5HPhikpMKCYih4t6D750Q7/70e4401fd59c7c280e8d8929ce18ede8/8.png)

Name your role `UnAuthRole`. Give the privilege to execute `UserLogin` and `UserRegistration`. As your functions are using the `account_by_email` index, provide read access to `account_by_email` as well. Also, provide read and create access to Account collection because `UserLogin` and `UserRegistration` UDFs need to read and create permission to this collection.

![Collection permission](//images.contentful.com/po4qc9xpmpuh/2CSOLFqlLs9lmNjQaWMA0a/f861a11a6e210b0b3a8345ce323ade77/9.png)

![Function permission](//images.contentful.com/po4qc9xpmpuh/1tBaLa1bgmhGiFfTztifIW/12aada0ee30cc6912610775ab5738e18/10.png)

Next, generate a security key for your `UnAuthRole`. Navigate to *Security > Keys > New Ke*y.

![Creating a new key](//images.contentful.com/po4qc9xpmpuh/1AOVVnnni9X0YkHb1MVkGn/41e0261da1e31b0b421e8ae70822f522/11.png)

Make sure to select `UnAuthRole` from the role options for your new key.

![New key name and role configuration](//images.contentful.com/po4qc9xpmpuh/50D0q8J9OQZZkesd4LI2rl/f61454175bedf073f8ad376e014a775d/12.png)

![Key Secret](//images.contentful.com/po4qc9xpmpuh/3l0kEu3kLfqCqw1o0cRoda/4e49b65ab69e8782c80ec6822ff89b96/13.png)

You ship this key as an environment variable in your client application. Your client application can use this key to call only the `UserRegistration` and `UserLogin` function. You can not access any other resources in Fauna with this key.

To test this navigate to the *Shell* from the Fauna dashboard and select Run *Query As > Specify a Secret* Option. In the secret field input your key.

![Running a query with a specified key](//images.contentful.com/po4qc9xpmpuh/6xrAt9c9vxl2FqEW4TvWrL/deb4fed7fcd05825b1cd29d5acfcda68/14.png)

Run the following command in the shell.

```jsx
Call("UserRegistration", "john@gmail.com", "pass12345"")
```

```jsx
Call("UserRegistration", "john@gmail.com", "pass12345")

{
  ref: Ref(Collection("Account"), "311012249060770373"),
  ts: 1632863244095000,
  data: {
    email: "john@gmail.com"
  }
}
```

Notice, that this will register a new user. Try accessing any other resources (i.e. Movie collection) using the same key. Run the following command in the shell.

```jsx
Get(Documents(Collection("Movie")))
```

```jsx
// Output
Error: [
  {
    "position": [],
    "code": "permission denied",
    "description": "Insufficient privileges to perform the action."
  }
]
```

Review the output in the shell. You get an `"Insufficient privileges to perform the action."` error. It throws this error because the specified key doesn’t have the privilege to access the `Movie` collection. This means the key is working as intended.

Next, run the `UserLogin` function in the shell.

```jsx
Call("UserLogin", "john@gmail.com", "pass12345")
```

Review the output. The function returned a secret.

```jsx
{
  ref: Ref(Ref("tokens"), "311013644284461635"),
  ts: 1632864574726000,
  ttl: Time("2021-09-28T21:39:34.245802Z"),
  instance: Ref(Collection("Account"), "311012249060770373"),
  secret: "fnEEUPFC--ACQwROmU7CwAZDlUcF9R3liL1iaNf9y80UQgc_qLI"
}
```

This secret is your temporary *access token* that can access other resources in Fauna. However, when you try to use it now to access the `Movie` collection you get the same error. This is because you have not yet defined what resources does 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

Navigate to *Security > Roles > New Role* to Create a new user role. Name your role `AuthRole` and provide `read`, `write`, and `create` privileges on the `Movie` collection.

![Configuring role privileges](//images.contentful.com/po4qc9xpmpuh/JLo36rpIsNEyZybdljDGv/bbc6ee51526498d4dd4f0d4faa27c2ef/15.png)

Select the Membership tab and add the Account collection as a member.

![Role membership](//images.contentful.com/po4qc9xpmpuh/7kzD3eNE9PaHjWO10Pc6m8/5ba5392ffa81a8a36a4a0114d2ee2384/16.png)

Run the `UserLogin` function again with the same credentials. Input the generated *secret* (user *access token*) next to the Specify a secret field in the shell.

![specify secret](//images.contentful.com/po4qc9xpmpuh/4CkHRBxZgZcRxFpX4NADWV/26d3c855bc35346309026b1650962e88/17.png)

Run the following command to query `Movie` collection. Notice this time it returns a successful response.

```jsx
Get(Documents(Collection("Movie")))
```

```jsx
// Output
{
  ref: Ref(Collection("Movie"), "310384575062737476"),
  ts: 1632264647455000,
  data: {
    title: "The Hateful Eight",
    director: "Quentin Tarantino",
    release: "December 25, 2015"
  }
}
```

## User logout

Navigate to the function section of your Fauna dashboard. Define a new UDF called `UserLogout` and add the following code snippet in the function body.

```jsx
Query(
    Lambda("x", Logout(true))
)
```

Make sure to assign AuthRole to UserLogout UDF.

Navigate to *Security > Roles > AuthRole* and assign call privilege to UserLogout UDF.

![Assigning the privilege to run a UDF](//images.contentful.com/po4qc9xpmpuh/5CbfN8EH3XzHUJFPdcsrS5/179429b0e7cbffcd85fb22921b9b7319/18.png)

Calling this UDF invalidates your secret token. You call this function with the following command.

```jsx
Call("UserLogout")
```

## Conclusion

In this post, you learn how to use UDF to do user authentication in Fauna. For more about advanced Authentication strategies (i.e., Token refresh) in Fauna, review [this post](https://fauna.com/blog/refreshing-authentication-tokens-in-fql). If you are interested in using third-party identity providers with Fauna, take a look at the [Fauna and Auth0](https://auth0.com/blog/what-is-fauna-and-how-does-it-work-with-auth0/) integration post. Want to use AWS Cognito as an auth provider with Fauna? Take a look at the [AWS Cognito and Fauna](https://fauna.com/blog/authenticating-users-with-aws-cognito-in-fauna) article.

If you enjoyed this article and want to see more articles like this one, let us know in the [community forum](https://forums.fauna.com/).