Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I structure authenticated queries with GraphQL?

I was thinking of writing an API that does the following things:

  • Sign-up and sign-in users which provide the user with an authentication token
  • Create maps (data example: { name: “Quotes”, attributes: [“quote”, “author"] })
  • Create map items (data example: { quote: "...", author: "..." })

I would build the queries somewhat like this:

// return the name and id of all the user's maps
maps(authToken="…") {
  name,
  id
}

// return all the items of a single map
maps(authToken="…") {
  map(name=“Quotes") {
    items
  }
}

// OR by using the map_id
maps(authToken="…") {
  map(id=“…") {
    items
  }
}

So, my question is, is this correct or would I need to structure it differently?

like image 638
Icid Avatar asked Jan 22 '16 17:01

Icid


2 Answers

I'd recommend constructing authentication outside of GraphQL itself, and letting your schema logic handle authorization. For example, if you are using the express-graphql NPM module, you can check your cookies or HTTP Basic Auth or whatever mechanism you want to use to get your auth token, and then pass your authenticated viewer object down through the schema via the rootValue, which is available at every level during query resolution:

app.use('/graphql', (request, response, next) => {
  const viewer = getViewerFromRequest(); // You provide this.
  const options = {
    rootValue: {
      viewer,
    },
    schema,
  };

  return graphqlHTTP(request => options)(request, response, next);
});

And then inside the schema you have access to your rootValue and can use that for the purposes of access control and authorization:

resolve: (parent, args, {rootValue}) => {
  const viewer = {rootValue};

  // Code that uses viewer here...
}

Note that as of graphql v0.5.0, the resolve signature has changed and a third, "context" parameter has been inserted at position 3 in the argument list. This parameter is suitable for passing down an auth token or similar:

resolve: (parent, args, authToken, {rootValue}) => {
  // Code that uses the auth token here...
}
like image 89
Greg Hurrell Avatar answered Oct 11 '22 05:10

Greg Hurrell


I offered an approach that structures resolvers as a composition of smaller functions to help in solving this exact problem. You can see the full answer here: How to check permissions and other conditions in GraphQL query?.

The basic concept is that if you structure your resolvers as small functions that are composed together you can layer different authorization/authenication mechanisms on top of one another and throw an error in the first one that is not satisfied. This will help keep your code clean, testable, and re-usable as well :)

Also yes, the resolver context is a great place to store authentication information as well as other goodies that might need to be used throughout your resolvers.

Happy Hacking!

like image 45
mparis Avatar answered Oct 11 '22 04:10

mparis