{"id":304202,"date":"2020-02-27T10:31:34","date_gmt":"2020-02-27T17:31:34","guid":{"rendered":"https:\/\/css-tricks.com\/?p=304202"},"modified":"2020-02-27T15:58:58","modified_gmt":"2020-02-27T22:58:58","slug":"instant-graphql-backend-using-faunadb","status":"publish","type":"post","link":"https:\/\/css-tricks.com\/instant-graphql-backend-using-faunadb\/","title":{"rendered":"Instant GraphQL Backend with Fine-grained Security Using FaunaDB"},"content":{"rendered":"\n

GraphQL is becoming popular and developers are constantly looking for frameworks that make it easy to set up a fast, secure and scalable GraphQL API. In this article, we will learn how to create a scalable and fast GraphQL API with authentication and fine-grained data-access control (authorization). As an example, we\u2019ll build an API with register and login functionality. The API will be about users and confidential files so we\u2019ll define advanced authorization rules that specify whether a logged-in user can access certain files. <\/p>\n\n\n\n

By using FaunaDB\u2019s native GraphQL and security layer, we receive all the necessary tools to set up such an API in minutes. FaunaDB has a free tier<\/a> so you can easily follow along by creating an account at https:\/\/dashboard.fauna.com<\/a>\/<\/a>. Since FaunaDB automatically provides the necessary indexes and translates each GraphQL query to one FaunaDB query, your API is also as fast as it can be (no n+1 problems!<\/a>).<\/p>\n\n\n\n

Setting up the API is simple: drop in a schema and we are ready to start. So let\u2019s get started!  <\/p>\n\n\n\n\n\n\n

The use-case: users and confidential files<\/h1>\n\n\n

We need an example use-case that demonstrates how security and GraphQL API features can work together. In this example, there are users<\/strong> and files.<\/strong> Some files can be accessed by all users, and some are only meant to be accessed by managers. The following GraphQL schema will define our model:<\/p>\n\n\n\n

type User {\n  username: String! @unique\n  role: UserRole!\n}\n\nenum UserRole {\n  MANAGER\n  EMPLOYEE\n}\n\ntype File {\n  content: String!\n  confidential: Boolean!\n}\n\ninput CreateUserInput {\n  username: String!\n  password: String!\n  role: UserRole!\n}\n\ninput LoginUserInput {\n  username: String!\n  password: String!\n}\n\ntype Query {\n  allFiles: [File!]!\n}\n\ntype Mutation {\n  createUser(input: CreateUserInput): User! @resolver(name: \"create_user\")\n  loginUser(input: LoginUserInput): String! @resolver(name: \"login_user\")\n}<\/code><\/pre>\n\n\n\n

When looking at the schema, you might notice that the createUser<\/code> and loginUser<\/code> Mutation fields have been annotated with a special directive named @resolver<\/code><\/a>. This is a directive provided by the FaunaDB<\/a> GraphQL API, which allows us to define a custom behavior for a given Query or Mutation field. Since we\u2019ll be using FaunaDB\u2019s built-in authentication mechanisms, we will need to define this logic in FaunaDB<\/a> after we import the schema. <\/p>\n\n\n

Importing the schema<\/h3>\n\n\n

First, let\u2019s import the example schema into a new database. Log into the FaunaDB Cloud Console<\/a> with your credentials. If you don\u2019t have an account yet, you can sign up for free in a few seconds.<\/p>\n\n\n\n

Once logged in, click the “New Database” button from the home page:<\/p>\n\n\n\n

\"\"<\/figure>\n\n\n\n

Choose a name for the new database, and click the “Save” button: <\/p>\n\n\n\n

\"\"<\/figure>\n\n\n\n

Next, we will import the GraphQL schema listed above into the database we just created. To do so, create a file named schema.gql<\/code> containing the schema definition. Then, select the GRAPHQL tab from the left sidebar, click the “Import Schema” button, and select the newly-created file: <\/p>\n\n\n\n

\"\"<\/figure>\n\n\n\n

The import process creates all of the necessary database elements, including collections and indexes, for backing up all of the types defined in the schema. It automatically creates everything your GraphQL API needs to run efficiently. <\/p>\n\n\n\n

You now have a fully functional GraphQL API which you can start testing out in the GraphQL playground. But we do not have data yet. More specifically, we would like to create some users to start testing our GraphQL API. However, since users will be part of our authentication, they are special: they have credentials and can be impersonated. Let\u2019s see how we can create some users with secure credentials!<\/p>\n\n\n

Custom resolvers for authentication<\/h3>\n\n\n

Remember the createUser<\/code> and loginUser<\/code> mutation fields that have been annotated with a special directive named @resolver<\/code>. createUser<\/code> is exactly what we need to start creating users, however the schema did not really define how a user needs to created; instead, it was tagged with a @resolver<\/code> tag.<\/p>\n\n\n\n

By tagging a specific mutation with a custom resolver such as @resolver(name: \"create_user\")<\/code> we are informing FaunaDB that this mutation is not implemented yet but will be implemented by a User-defined function<\/a> (UDF). Since our GraphQL schema does not know how to express this, the import process will only create a function template which we still have to fill in.<\/p>\n\n\n\n

A UDF is a custom FaunaDB function, similar to a stored procedure<\/em>, that enables users to define a tailor-made operation in Fauna\u2019s Query Language (FQL<\/a>). This function is then used as the resolver of the annotated field. <\/p>\n\n\n\n

We will need a custom resolver since we will take advantage of the built-in authentication capabilities which can not be expressed in standard GraphQL. FaunaDB allows you to set a password on any database entity. This password can then be used to impersonate this database entity with the Login<\/code> function which returns a token with certain permissions. The permissions that this token holds depend on the access rules that we will write.<\/p>\n\n\n\n

Let\u2019s continue to implement the UDF for the createUser<\/code> field resolver so that we can create some test users. First, select the Shell tab from the left sidebar:<\/p>\n\n\n\n

\"\"<\/figure>\n\n\n\n

As explained before, a template UDF has already been created during the import process. When called, this template UDF prints an error message stating that it needs to be updated with a proper implementation. In order to update it with the intended behavior, we are going to use FQL’s Update<\/a> function.<\/p>\n\n\n\n

So, let\u2019s copy the following FQL query into the web-based shell, and click the “Run Query” button:<\/p>\n\n\n\n

Update(Function(\"create_user\"), {\n  \"body\": Query(\n    Lambda([\"input\"],\n      Create(Collection(\"User\"), {\n        data: {\n          username: Select(\"username\", Var(\"input\")),\n          role: Select(\"role\", Var(\"input\")),\n        },\n        credentials: {\n          password: Select(\"password\", Var(\"input\"))\n        }\n      })  \n    )\n  )\n});<\/code><\/pre>\n\n\n\n

Your screen should look similar to:<\/p>\n\n\n\n

\"\"<\/figure>\n\n\n\n

The create_user<\/code> UDF will be in charge of properly creating a User document along with a password value. The password is stored in the document within a special object named credentials<\/em> that is encrypted and cannot be retrieved back by any FQL function. As a result, the password is securely saved in the database making it impossible to read from either the FQL or the GraphQL APIs. The password will be used later for authenticating a User through a dedicated FQL function named Login<\/code><\/a>, as explained next.<\/p>\n\n\n\n

Now, let\u2019s add the proper implementation for the UDF backing up the loginUser<\/code> field resolver through the following FQL query:<\/p>\n\n\n\n

Update(Function(\"login_user\"), {\n  \"body\": Query(\n    Lambda([\"input\"],\n      Select(\n        \"secret\",\n        Login(\n          Match(Index(\"unique_User_username\"), Select(\"username\", Var(\"input\"))), \n          { password: Select(\"password\", Var(\"input\")) }\n        )\n      )\n    )\n  )\n});<\/code><\/pre>\n\n\n\n

Copy the query listed above and paste it into the shell\u2019s command panel, and click the “Run Query” button:<\/p>\n\n\n\n

\"\"<\/figure>\n\n\n\n

The login_user<\/code> UDF will attempt to authenticate a User with the given username and password credentials. As mentioned before, it does so via the Login<\/code><\/a> function. The Login<\/code> function verifies that the given password matches the one stored along with the User document being authenticated. Note that the password stored in the database is not output at any point during the login process. Finally, in case the credentials are valid, the login_user<\/code> UDF returns an authorization token called a secret<\/em> which can be used in subsequent requests for validating the User\u2019s identity.<\/p>\n\n\n\n

With the resolvers in place, we will continue with creating some sample data. This will let us try out our use case and help us better understand how the access rules are defined later on.<\/p>\n\n\n

Creating sample data<\/h3>\n\n\n

First, we are going to create a manager<\/em> user. Select the GraphQL tab from the left sidebar, copy the following mutation into the GraphQL Playground, and click the “Play” button:<\/p>\n\n\n\n

mutation CreateManagerUser {\n  createUser(input: {\n    username: \"bill.lumbergh\"\n    password: \"123456\"\n    role: MANAGER\n  }) {\n    username\n    role\n  }\n}<\/code><\/pre>\n\n\n\n

Your screen should look like this:<\/p>\n\n\n\n

\"\"<\/figure>\n\n\n\n

Next, let\u2019s create an employee<\/em> user by running the following mutation through the GraphQL Playground editor:<\/p>\n\n\n\n

mutation CreateEmployeeUser {\n  createUser(input: {\n    username: \"peter.gibbons\"\n    password: \"abcdef\"\n    role: EMPLOYEE\n  }) {\n    username\n    role\n  }\n}<\/code><\/pre>\n\n\n\n

You should see the following response:<\/p>\n\n\n\n

\"\"<\/figure>\n\n\n\n

Now, let\u2019s create a confidential<\/em> file by running the following mutation:<\/p>\n\n\n\n

mutation CreateConfidentialFile {\n  createFile(data: {\n    content: \"This is a confidential file!\"\n    confidential: true\n  }) {\n    content\n    confidential\n  }\n}<\/code><\/pre>\n\n\n\n

As a response, you should get the following:<\/p>\n\n\n\n

\"\"<\/figure>\n\n\n\n

And lastly, create a public<\/em> file with the following mutation:<\/p>\n\n\n\n

mutation CreatePublicFile {\n  createFile(data: {\n    content: \"This is a public file!\"\n    confidential: false\n  }) {\n    content\n    confidential\n  }\n}<\/code><\/pre>\n\n\n\n

If successful, it should prompt the following response:<\/p>\n\n\n\n

\"\"<\/figure>\n\n\n\n

Now that all the sample data is in place, we need access rules since this article is about securing a GraphQL API. The access rules determine how the sample data we just created can be accessed, since by default a user can only access his own user entity. In this case, we are going to implement the following access rules: <\/p>\n\n\n\n

  1. Allow employee users to read public files only.<\/li>
  2. Allow manager users to read both public files and, only during weekdays, confidential files.<\/li><\/ol>\n\n\n\n

    As you might have already noticed, these access rules are highly specific. We will see however that the ABAC system is powerful enough to express very complex rules without getting in the way of the design of your GraphQL API.<\/p>\n\n\n\n

    Such access rules are not part of the GraphQL specification so we will define the access rules in the Fauna Query Language (FQL), and then verify that they are working as expected by executing some queries from the GraphQL API. <\/p>\n\n\n\n

    But what is this “ABAC” system that we just mentioned? What does it stand for, and what can it do?<\/p>\n\n\n

    What is ABAC?<\/h3>\n\n\n

    ABAC stands for Attribute-Based Access Control<\/em>. As its name indicates, it\u2019s an authorization model that establishes access policies based on attributes<\/em>. In simple words, it means that you can write security rules that involve any of the attributes of your data. If our data contains users we could use the role, department, and clearance level to grant or refuse access to specific data. Or we could use the current time, day of the week, or location of the user to decide whether he can access a specific resource. <\/p>\n\n\n\n

    In essence, ABAC allows the definition of fine-grained<\/em> access control policies based on environmental properties and your data. Now that we know what it can do, let\u2019s define some access rules to give you concrete examples.<\/p>\n\n\n

    Defining the access rules<\/h3>\n\n\n

    In FaunaDB, access rules are defined in the form of roles. A role consists of the following data:<\/p>\n\n\n\n