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:

  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.

AuthN flow diagram

Getting started

Create a database:

  1. Go to the Fauna Dashboard and sign in.

    You can sign up for a free account at https://dashboard.fauna.com/register.

  2. Click Create Database to create a database.

  3. Give your database a Name.

  4. Choose your preferred Region Group.

  5. Click Create.

    Your new database is listed on the Home page.

  6. 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:

UserRegistration("jon.doe@fauna.com", "pass123456")
{
  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:

UserLogin("jon.doe@fauna.com", "pass123456")
{
  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.

Movie.create({
  "title": "Reservoir Dogs",
  "director": "Quentin Tarantino",
  "release": "Jan 21, 1992"
})
Movie.create({
  "title": "The Hateful Eight",
  "director": "Quentin Tarantino",
  "release": "December 25, 2015"
})
Movie.create({
  "title": "Once Upon a Time in Hollywood",
  "director": "Quentin Tarantino",
  "release": "July 26, 2019"
})
{
  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:

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

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

  3. 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 and UserLogin 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:

    Dashboard Shell use secret

    Then run the following command in the Dashboard Shell:

    UserRegistration("jane.doe@fauna.com", "pass123456")
    {
      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:

    Movie.all()
    {
      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:

User login

…and use the value to test accessing the Movie collection:

Use secret

Run the following command in the shell:

Movie.all()
{
  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:

Use secret

…​and run the following command in the shell:

UserLogout()
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!