DEV Community

Cover image for User authentication in Fauna (an opinionated guide)
Shadid Haque for Fauna, Inc.

Posted on • Updated on • Originally published at fauna.com

User authentication in Fauna (an opinionated guide)

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) 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 .

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

User registration

Head over to the Fauna dashboard and create a new database.

Creating a new database

Select Collections and create a new collection called Account
Creating a new collection

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

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

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.

Query(
    Lambda(["email", "password"],
        Create(Collection("Account"), {
            credentials: { password: Var("password") },
            data: {
                email: Var("email")
            }
        })
    )
)
Enter fullscreen mode Exit fullscreen mode

Creating a new user-defined function

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.

Call("UserRegistration", "shadid@test.com", "pass123456")
Enter fullscreen mode Exit fullscreen mode

Calling a UDF from the dashboard shell

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.

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

        )
    )
)
Enter fullscreen mode Exit fullscreen mode

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

Call("UserLogin", "shadid@test.com", "pass123456")
Enter fullscreen mode Exit fullscreen mode

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

{
  ref: Ref(Ref("tokens"), "310382039289299523"),
  ts: 1632262229150000,
  ttl: Time("2021-09-21T22:20:29.019666Z"),
  instance: Ref(Collection("Account"), "310358409884992069"),
  secret: "fnEE....."
}
Enter fullscreen mode Exit fullscreen mode

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.

curl https://db.fauna.com/tokens/self  -u <YOUR_TOKEN>
Enter fullscreen mode Exit fullscreen mode
{
  "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"
  }
}
Enter fullscreen mode Exit fullscreen mode

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

{
    "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"
}

Enter fullscreen mode Exit fullscreen mode

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 .

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

Creating a custom role

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

Function permission

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

Creating a new key

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

New key name and role configuration

Key Secret

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

Run the following command in the shell.

Call("UserRegistration", "john@gmail.com", "pass12345"")
Enter fullscreen mode Exit fullscreen mode
Call("UserRegistration", "john@gmail.com", "pass12345")

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

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.

Get(Documents(Collection("Movie")))
Enter fullscreen mode Exit fullscreen mode
// Output
Error: [
  {
    "position": [],
    "code": "permission denied",
    "description": "Insufficient privileges to perform the action."
  }
]
Enter fullscreen mode Exit fullscreen mode

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.

Call("UserLogin", "john@gmail.com", "pass12345")
Enter fullscreen mode Exit fullscreen mode

Review the output. The function returned a secret.

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

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

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

Role membership

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

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

Get(Documents(Collection("Movie")))
Enter fullscreen mode Exit fullscreen mode
// Output
{
  ref: Ref(Collection("Movie"), "310384575062737476"),
  ts: 1632264647455000,
  data: {
    title: "The Hateful Eight",
    director: "Quentin Tarantino",
    release: "December 25, 2015"
  }
}
Enter fullscreen mode Exit fullscreen mode

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.

Query(
    Lambda("x", Logout(true))
)
Enter fullscreen mode Exit fullscreen mode

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

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

Call("UserLogout")
Enter fullscreen mode Exit fullscreen mode

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. If you are interested in using third-party identity providers with Fauna, take a look at the Fauna and Auth0 integration post. Want to use AWS Cognito as an auth provider with Fauna? Take a look at the AWS Cognito and Fauna article.

If you enjoyed this article and want to see more articles like this one, let us know in the community forum.

Discussion (0)