GraphQL and Authentication

Folks ask about how GraphQL works with authentication and authorization. Ultimately, the answer is that GraphQL just a query language and has no opinion on the matter —it’s up to each application to figure out the auth story.

But instead of leaving it at that and going home, let’s explore some possibilities.

High-Level

Broadly, I think there are three types of setups:

  1. Logged in users can make GraphQL queries, logged out users cannot, authentication happens via non-GraphQL endpoints and transports
  2. All users can make GraphQL queries, logged out users are authorized for a subset, authentication happens via non-GraphQL endpoints and transports
  3. All users can make GraphQL queries and authentication happens via GraphQL itself

#1 is how most existing apps will migrate to GraphQL. Your app probably already has some REST or RPC endpoints that only authenticated requests can access, and adding /graphql to the mix is simple. The downside is that your front-end has to speak both GraphQL and non-GraphQL, which feels like the beginnings of tech debt.

#2 might be the eventual state of a project that started with #1. Eventually your front-end code will want to use only GraphQL, except the legacy-but-stable authentication endpoints.

#3 is what brand new apps might explore, to avoid accumulating non-GraphQL flows. It’s not something I’ve seen discussed or done in production, but we’ll go over how it could work.

Non-GraphQL Mechanics

By “non-GraphQL endpoints and transports”, I mean things like cookies, JSON web tokens, or even HTTP Basic auth. Basically whatever way your server is able to identify a request to a user and pass that information through to your GraphQL resolver.

Here’s an example using express-graphql and cookies (borrowed from their example):

var session = require('express-session');
var graphqlHTTP = require('express-graphql');
var MySchema = require('./MySchema');
var app = express();

app.use(session({ secret: 'secret', cookie: { maxAge: 60000 }}));

app.use('/graphql', graphqlHTTP((request) => ({
schema: MySchema,
rootValue: { session: request.session },
graphiql: true
})));

In Express-terms, the request is authenticated in an earlier middleware than the GraphQL route; by the time it hits our GraphQL code, we know who the request is coming from. We might have even redirected them to login before we get to the GraphQL part. This uses express-session, but the principle applies to other tools like express-jwt.

At the GraphQL-level, your schema code looks something like:

new GraphQLObjectType({
name: 'Secrets',
fields: {
bigSecret: {
type: GraphQLString,
resolve(parentValue, _, { rootValue: { session } }) {
return getBigSecret(session);
}
}
}
});

Since we have access to session, all of our code can now be relative to the user accessing it. Or if session doesn’t exist (i.e. the user is logged out), then you can gracefully degrade or even throw errors.

The key with this trick is that the rootValue is not defined in our GraphQL schema as a public field or argument. We don’t trust the client to send it directly (else we risk spoofing attacks), so it gets injected via a special code path on the server.

GraphQL Mechanics

But what if we wanted to use pure GraphQL? The above method works if you’re using express-graphql, but it may not be portable to other implementations.

In a few examples and talks, Facebook has mentioned the concept of a viewer field. The idea here is that your application’s data is fundamentally relative to who is viewing it, so all of your other fields nest within it. In practice not all of your data may change depending on the viewer, but doing so gives you the ability to change your mind later.

{
viewer {
name
friends {
name
}
getProfile(id: String!) {
name
}
}
}

Note that even a field like getProfile, which isn’t obviously relative to the viewer, now has to the ability to reference the viewer in case we need restrictions on who can see certain profiles or fields.

An app like Facebook has lots of logic around who can see what data for privacy reasons, but even simple apps usually disallow users from seeing data they didn’t create. The canonical “exploit” is changing IDs in the URL to see private data when the server doesn’t confirm ownership; using a single viewer field makes implementing these ownership checks intuitive.

The schema above could work with the non-GraphQL authentication methods we mentioned earlier. But instead imagine we did something like:

{
viewer(token: String) {
name
}
}

Instead of passing in the token via header or query parameter (like JWT, OAuth, etc), we make it part of the GraphQL query. Your schema code can parse the token directly using the JWT library itself or another tool.

Remember to always use HTTPS when passing sensitive information :)

To issue new tokens, a mutation fits nicely:

mutation {
createToken(username: String!, password: String!) {
token
error
}
}

We would pass the credentials into the mutation, and return either the valid token or some type of error object. The client would store the token like any other token (localStorage, native iOS storage, etc) and send it back upon repeat requests.


At the end of the day, it’s up to your application to figure out how auth works. But don’t re-invent the wheel, especially when it comes to security — build on top of understood technologies like JWT and OAuth.

Leave you thoughts as responses or notes!