Learning FQL, Part 3: Database Access Keys

In the previous two posts in this series, we covered FaunaDB schema basics, and basic CRUD operations (create, read, update, and delete). Fauna Shell handles access control for you, so you haven’t had to do any key management. This article will cover key creation and use. The basic operations discussed here can be combined with FaunaDB’s multi-tenancy patterns to automate SaaS application provisioning, user account creation, and other application lifecycle steps.

Access Keys

FaunaDB keys correspond to a database and an access control level. Admin keys can provision and manage nested databases, while server keys can manage the data in a particular database. There are also access tokens, which allow end users to authenticate connections from mobile devices and web clients.

When you are embedding a key into application code for queries or schema management, you’ll use a server key. To manage keys for a particular child database, you must connect with an admin key connection that corresponds to the parent database. In this case, Fauna Shell is currently connected to the root database with an admin key, so we can create a server key for the child database, called "my-database", that we created in an earlier article.

CreateKey({ 
    database: Database("my-database"),
    role: "server" })

The return value contains the key secret:

=> { ref: Ref(id=200669511526908416, class=Ref(id=keys)),
  ts: 1527632209151020,
  database: Ref(id=caledonia, class=Ref(id=databases)),
  role: 'server',
  secret: 'fnACyOvbh9ACAOtRQDQSefokn4iuMf9zn_FakPAx',
  hashed_secret: '$2a$05$dszFRic/tkKzsG0rrUj3Fu7.nSoYuJL7BdnHXepx4XTzfcWtVEZRC' }

Once a key is created, its secret field is what you’ll store and use in your application configuration, and use to connect to the individual database to perform data operations. This server key can create classes and indexes, as well as run queries and modify data. If you want a key with other privileges, you can read about the other options for key roles in the access keys documentation: admin, server, server-readonly, and client. The query to provision those types of keys will look like what you see above, with a different role specified.

Server Connection

By using the server key secret we just provisioned, we can connect to our database. If you are using Fauna Shell, this happens automatically, but you still need to know about it when you are writing code. The secret is used in code that creates client connections to FaunaDB. Since FaunaDB connections are stateless, there is no harm in maintaining multiple client objects in your application—for instance an admin client for provisioning new SaaS tenants, and a server client for each tenant, to interact with the data.

var serverClient = new faunadb.Client({
  secret: process.env.FAUNADB_SERVER_SECRET
});

Where FAUNADB_SERVER_SECRET looks something like the 'fnACyOvbh9ACAOtRQDQSefokn4iuMf9zn_FakPAx' as returned above. It’s up to you and your hosting provider, the best way to manage environment variables, but it is a best practice not to paste secrets into production code.

This server client is what you’ll use for schema changes and data queries within the database my-database. If you are using Fauna Shell, you can connect to the database by running `fauna shell my-database`.

The server client connection we created in the code above is useful for defining indexes and interacting with data in the database. It can’t be used to create new child databases. It should not be distributed to end users or user agents, because it has the capability to read the entire database as well as update or delete arbitrary data.

For more details about working with Access Keys check out the FaunaDB docs.

Client Keys and Public Read Permissions

Sometimes you want to connect to the database as an untrusted user. Client keys connect to a particular database, but only give access to public resources, which must be explicitly tagged by developers. For example you might create an index with public data in the values, and allow anonymous users to query it. For this pattern you’d create an index that is publicly readable, and a client key that can read it.

To create a publicly queryable index, you can set it’s “read” permission to “public” (in this case we’re in a shell session for my-databases, which you can reach with fauna shell my-database):

CreateIndex({
    name: "all_spell_titles",
    permissions: {
        read: "public"
    },
    source: Class("spells"),
    values: [{ field: ["data", "title"] }] })

To create a key that can query this index, we just issue a query to create a client key, and share the resulting key secret with our client applications. Client keys can only read public resources.

CreateKey({ 
    database: Database("my-database"),
    role: "client" })

This pattern allows you to open certain data resources to queries from anonymous clients, without exposing the rest of the database. This can be useful if you plan to have mobile devices or web browsers query the database directly. It can also be helpful if you are offering data to partner companies.

Conclusion

These patterns give developers and administrators fine-grained control over data in FaunaDB. Whether you are provisioning databases for SaaS customers, business units, development teams, or projects, you can give just the right level of access to just those who need it, without worrying about impacts to the rest of the cluster.

Admin keys can be used to manage the database tenancy tree and to provision other keys. Server keys can define the database schema and issue queries. Client keys can query public resources. These capabilities are enough to get started with FaunaDB access keys.

More complex applications may require that users may only view and edit their own data, or perhaps multiple users collaborate on data, with different users working on different parts of the database. In a future article we’ll talk about how to authenticate users with tokens, and how to work with role-based application control. If you’d like to jump ahead, these patterns are illustrated in the application development series Building a Serverless JAMStack app with FaunaDB Cloud.