Login and logout

A token requires an identity document field. This document can represent a person, service, system, or process that needs access to database resources. Typically, though, an identity document represents a person who is a user accessing a Fauna database from a client application.

In this tutorial, you learn about creating tokens associated with people.

Associate a credential with a document

In other databases, you might have stored a hashed password with user data. Fauna supports this with features that are easier to use and more secure. The way to tell Fauna that an identity, such as People, document has a password is by associating it with a Credential document.

A document can only have one associated credential. Attempts to create a second credential return a constraint_failure error.

Your CoffeeStore database should have a populated People collection. If it doesn’t, you can create one as instructed in the previous section.

  1. Open the Fauna Shell in the CoffeeStore database.

    A Token document can only access one database. Access to a parent database in a hierarchy does not grant access to its children.

  2. In the run menu, set Admin or an equivalent key secret.

  3. Find the first People document where there is an email.

    People.firstWhere(.email != null)
    {
      id: "370723013175279650",
      coll: People,
      ts: Time("2023-07-20T15:21:59.220Z"),
      name: "Janine Labrune",
      email: "jlabrune@gmail.com",
      address: {
        street: "67, rue des Cinquante Otages",
        city: "Nantes",
        country: "France",
        zip: "44000"
      },
      status: "active"
    }
  4. Make note of the id field on this person.

  5. Create a Credential for this id providing a required password field.

    Credential.create({
      document: People.byId("370723013175279650"),
      password: "sekret"
    })
    {
      id: "371153420791316514",
      coll: Credential,
      ts: Time("2023-07-24T17:05:34.890Z"),
      document: People.byId("370723013175279650")
    }

    The People document serves as the required identity document for the credential. The credential securely encrypts but never displays the password. This design ensures a caller cannot expose hashed credentials by mistake.

    Fauna cannot recover a password that is discarded or lost. Copy and save it to a password manager or other safe location. You can call Credential.update() to set a new password.

  6. Using the credential id and the user password, make the Credential.login() query.

    Credentials.byId("371153420791316514")!.login("sekret")
    {
      id: "371153519651061794",
      coll: Token,
      ts: Time("2023-07-24T17:07:09.170Z"),
      secret: "fnEFJpoo3_AAIgUiXlNNgAAi7f_DzBJ_4i_Y3fJHlD_KYwtEGfc",
      document: People.byId("370723013175279650")
    }

    At creation is the only time Fauna displays the secret field. The value in this field is equivalent to a password. Fauna cannot recover a secret that is discarded or lost. Copy and save the secret to a password manager or other safe location. Delete and replace keys or tokens for which you have lost the secret. If you no longer need a key or token, you should delete it.

    The token has no ttl, time-to-live, field so it remains until it is deleted or expired. You can set this value.

  7. Update the token adding a ttl value that expires the token after a day.

    Token.byId("371153519651061794")!.update({ttl: Time.now().add(1, "day")})
    {
      id: "371153519651061794",
      coll: Token,
      ts: Time("2023-07-24T21:23:47.260Z"),
      ttl: Time("2023-07-25T21:23:44.631Z"),
      document: People.byId("370723013175279650")
    }

Sign in and Log in

In this procedure, you use the basic queries you learned to log in users to create functions. Commonly, an application uses the email and password combination for signing up new users. Each user is identified by their unique email address so it is important to constrain the People.email field so that it is unique.

In the Shell go to the run menu and set Admin or an equivalent key secret, then do the following.

  1. Add a unique constraint to the email field in People.

    People.definition.update({
      constraints: [
        {
          unique: [ "email" ],
        }
      ]
    })
    {
      name: "People",
      coll: Collection,
      ts: Time("2023-07-25T22:03:50.820Z"),
      constraints: [
        {
          unique: [
            "email"
          ],
          status: "active"
        }
      ]
    }
  2. Create a user-defined function to sign up users.

    The FQL function body is written like a standard JavaScript arrow function. When called from the Shell or an application the caller provides username, email and password as parameters. The function creates a People document that becomes the identity provided in the Credential document that the function returns.

    Function.create({
      name: "Signup",
      role: "server",
      body: "(username, email, password) => {
        let user = People.create({ name: username, email: email })
        Credentials.create({ document: user, password: password })
      }"
    })
    {
      name: "Signup",
      coll: Function,
      ts: Time("2023-07-25T15:59:03.950Z"),
      role: "server",
      body: <<-END
        (username, email, password) => {
            let user = People.create({ name: username, email: email })
            Credentials.create({ document: user, password: password })
          }
      END
    }
  3. Call the Signup function to test it is correct.

    Signup("Alice Jones", "ajones@gmail.com", "sekret")
    {
      id: "371240301830864930",
      coll: Credential,
      ts: Time("2023-07-25T16:06:31.110Z"),
      document: People.byId("371240301825622050")
    }
  4. Test the email constraint by supplying the same parameters again to the Signup() function.

    Your second call should fail the unique constraint. Typically, applications must support users who log into an application after signing up. Now that email is constrained, it makes sense to create an index on that field.

  5. Create an index on the People collection.

    People.definition.update({
      indexes: {
        byEmail: {
          terms: [{ field: "email" }]
        }
      }
    })
    {
      name: "People",
      coll: Collection,
      ts: Time("2023-07-24T15:50:31.105Z"),
      indexes: {
        byEmail: {
          terms: [
            {
              field: "email"
            }
          ],
          queryable: true,
          status: "complete"
        }
      },
      constraints: [
        {
          unique: [
            "email"
          ],
          status: "active"
        }
      ]
    }
  6. Create a new loginUser UDF that takes makes use of this new index.

    Function.create({
      name: "loginUser",
      role: "server",
      body: "(email, password) => {
        let user = People.byEmail(email).first()
        Credentials.byDocument(user)!.login(password)
      }"
    })
    {
      name: "Login",
      coll: Function,
      ts: Time("2023-07-25T22:19:52.930Z"),
      body: <<-END
        (email, password) => {
            let user = People.byEmail(email).first()
            Credentials.byDocument(user)!.login(password)
          }
      END,
      role: "server"
    }

    This function uses the email index to find a user document. Then, it supplies the document together with the password provided by the caller to the Credential.login() function.

  7. Use the loginUser function to login the user.

    loginUser("ajones@gmail.com", "sekret")
    {
      id: "371264487134593058",
      coll: Token,
      ts: Time("2023-07-25T22:30:56.010Z"),
      document: People.byId("371264255805095970"),
      secret: "fnEFJv8VgWAAIgUiXlNNgAAivHz8zbJGD2srnxB55AmR16fv-o4"
    }
  8. Make note of the secret value for this token as you need it in the next section.

    At creation is the only time Fauna displays the secret field. The value in this field is equivalent to a password. Fauna cannot recover a secret that is discarded or lost. Copy and save the secret to a password manager or other safe location. Delete and replace keys or tokens for which you have lost the secret. If you no longer need a key or token, you should delete it.

  9. Experiment with the loginUser() function by giving it the wrong email or password.

Your experiment should find that Fauna inherently provides proper security and error messaging for your new login function.

Log out

To log out, you can destroy the token created when logging in. The Fauna Query namespace has methods that you can use to get the token in the current session. You can use these methods to log out a user by destroying their access token.

  1. In the run menu, set Secret and enter the token secret created by the loginUser() function.

    Subsequent queries you enter use this secret.

  2. Verify that you are in the session with the correct token.

    Query.token()
    {
      id: "371287435110252578",
      coll: Token,
      ts: Time("2023-07-26T04:35:40.910Z"),
      document: People.byId("371264255805095970")
    }

    As you are running with the token secret, Fauna knows your identity. It allows you to do queries related to your own identity data.

  3. List all the People documents.

    People.all()
    {
      data: [
        {
          id: "371264255805095970",
          coll: People,
          ts: Time("2023-07-25T22:27:15.410Z"),
          name: "Alice Jones",
          email: "ajones@gmail.com"
        }
      ]
    }

    Even though the People collection has several documents, the Fauna returns only the document whose id matches the current identity.

  4. To logout of this session, delete the token.

    Query.token()!.delete()
    Token.byId("371287435110252578") /* permission denied */
  5. Subsequent queries fail as your secret is no longer valid.

    People.all()
    Invalid secret

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!