User authentication with Graph QL?

I want to do a login from GraphQL before a data collection can be accessed.

I created a users collection with permissions public and create a UDF for login by following this article:

But when doing a login from GraphQL, I’m not able to use a Client Key as in regular FQL. It works only if I use an authorization header with server key. But then the login is meaningless. What am I missing here?

Thanks.

It’s true that this example is missing a very important concept, a bootstrap key to actually call your login/register functions. This workshop I gave recently explains that, you can follow the workshop yourself via the gdoc (answers are written in white to make sure not to spoil) .
You can follow one of the vids where I go through the workshop (after a small introduction):
Or you can look at the repository, the final branch contains the full example. The part that I assume you are missing is the creation of a custom bootstrap role that gives access to your ‘public’ data. In this case, the public ‘data’ is the ability to call a login and register function.

const createBootstrapRole = CreateRole({
  name: 'keyrole_calludfs',
  privileges: [
    {
      resource: faunadb.query.Function('login'),
      actions: {
        call: true
      }
    },
    {
      resource: faunadb.query.Function('register'),
      actions: {
        call: true
      }
    }
  ]
})

Create a key for that role and that key is good to place in your frontend.

I’m not sure what you mean by ‘Client key’ though.

Disclaimer: this is frontend only auth with tokens stored in memory. There are better solutions with a partial backend for your auth flow that allow you to use httpOnly cookies etc and provide a better experience (refreshing tokens on refreshing the browser).

Thank you! I’m sure that will solve my problem. Will post here when I get to that again.

I picked up the client key term from the tutorial of FaunaDb: (Create a client key)

Perhaps, that doc is old and what you say makes more sense. Make a key for the role that can use login UDF and use that in the client.

Once again, thanks for the prompt solution.

Ahh thank you for pointing that out. That’s indeed legacy, I’ll log a ticket to correct that. Instead of creating public keys (as before) we thought that it was a better approach to make people think of what they are giving their frontend access to immediately. At the same time it gives them a first introduction to ABAC :hugs:

To follow up on this, I created a role “LoginUser” with permission to call login function and it worked. But when I used the secret returned as a bearer token in GraphQL playground and entered a query to get a list of objects from my collection, it gave insufficient privileges error.

So the question is, once Login is successful, what determines the role that takes effect after the login secret is used as an authorization header (Bearer) in GraphQL. I’m not able to make that connection from the authorization to the role that it is related to.

For example, I have a role “LoggedinUser” that has access to the collection that I want to list. How do make that role come into action after using the secret from Login output?

Membership of the role.
For example in the example I shared:
image


At the end you find

image

This membership statements just says that the membership is the whole account collection in this case. That means that each token that is linked to an account gets access to the permissions of the role. A token is what you get from Login() which you are doing iirc, or from creating a token manually with Create(Token(),…)

Update: I think I solved this. But I’m confused. Please see and confirm note at the end.

Thanks. Very well explained. This confirms what I thought about roles. But here is the role that does not work. I’m pasting its FQL tab contents.

{
  ref: Role("loggedInUser"),
  ts: 1597726876959400,
  name: "loggedInUser",
  membership: [
    {
      resource: Collection("users")
    }
  ],
  privileges: [
    {
      resource: Collection("Task"),
      actions: {
        read: true,
        write: true,
        create: true,
        delete: true
      }
    },
    {
      resource: Index("allTasks"),
      actions: {
        unrestricted_read: false,
        read: true
      }
    }
  ]
}

Now, here is how I pass back the secret that I receive from Login call after success. I put this in GraphQL Header pane.

{
  "authorization": "Bearer fnADziABYoACBw-Zb9iF5YEByvdUu5DD6U0HOsg-"
}

After that, when I issue a GraphQL query:

query ListAllTasks {
  allTasks {
    data {
      _id
      description
    }
  }
}

I get this:

{
  "errors": [
    {
      "message": "Insufficient privileges to perform the action.",
      "extensions": {
        "code": "permission denied"
      }
    }
  ]
}

Is it some kind of limitation of GraphQL API of FaunaDB that it can’t use a token returned by Login?

Update: I think I solved it. I had to add resource “Collections” to the Role with a “Read” permission.

FQL dump for insert:

    {
      resource: Ref("collections"),
      actions: {
        read: true,
        write: false,
        create: false,
        delete: false,
        history_read: false,
        history_write: false
      }
    }

How is this different from permission on Resource Collection Tasks with all operations allowed which was already there? Shouldn’t that permission to read collections be implicit with the earlier resource statement for Tasks collection?

Did you try query same scenario but with FQL?
Not sure but it maybe a bit of help in debugging.

Ehh whow… where did you get that from?
That should definitely not be necessary.

There might be something else going on here though.
You just recreated this role or updated it. Could it be that you have overridden your GraphQL schema at a certain point? This might have created different collections/indexes and if your roles are not updated, these roles might be pointing to old collections.

You’re right. It seems to be some sort of cache problem. I did everything from scratch, without adding Collections this time. And it worked. Thanks!

About cache, I have also seen problems where say if I run FQL Create collection and then an index immediately, creation of index fails with invalid data. I need to wait before creating the index. It would be nice if at least the FQL interpreter waited for previous actions to complete before running the next. Otherwise, all FQL scripts have to be broken into steps and run. One can’t have a complete FQL script that sets up everything in one shot.

1 Like