Authentication and Authorization with GraphQL and Parse Server

June 26, 2017

In a previous article, we saw how to use GraphQL with Parse Server. Now we will see how to restrict access for unauthenticated users and how to limit query results to the data they are allowed to access.

Our example will work as follows: the API will create users, and once authenticated, they will be able to post messages and retrieve all of their own messages.

Let's start by adding our object types to the schema:

/********* Object Types *********/
 
const userType = new GraphQLObjectType({
  name: 'User',
  description: 'A simple user',
  fields: () => ({
    id: {
      type: GraphQLID,
      resolve: (obj) => obj.id,
    },
    username: {
      type: GraphQLString,
      resolve: (obj) => obj.get('username'),
    },
    sessionToken: {
      type: GraphQLString,
      resolve: (obj) => obj.getSessionToken(),
    },
  }),
});
 
const postType = new GraphQLObjectType({
  name: 'Post',
  description: 'A simple post message',
  fields: () => ({
    id: {
      type: GraphQLID,
      resolve: (obj) => obj.id,
    },
    message: {
      type: GraphQLString,
      resolve: (obj) => obj.get('message'),
    },
    author: {
      type: userType,
      resolve: (obj) => obj.get('author'),
    },
  }),
});

We now have our two object types, User and Post. One note about the User type: the sessionToken field is only available when the user is returned after creation or login. After that, this field is no longer available and its value will always be null. We will need to store it on the client side so we can authenticate our requests. The author field on the Post type is a pointer to the User type.

Creating and logging in users

Before moving on to request authentication, we will first create two mutations that let us create a new user and let that user log in.

/********* Mutation Types *********/
const signUp = {
  type: userType,
  description: 'Create a new user',
  args: {
    username: {
      type: GraphQLString,
    },
    email: {
      type: GraphQLString,
    },
    password: {
      type: GraphQLString,
    },
  },
  resolve: (value, { username, email, password }) => {
    var user = new Parse.User();
    user.set('username', username);
    user.set('password', password);
    user.set('email', email);
    return user.signUp();
  },
};
 
const login = {
  type: userType,
  description: 'Connects the user',
  args: {
    username: {
      type: GraphQLString,
    },
    password: {
      type: GraphQLString,
    },
  },
  resolve: (value, { username, password }) => {
    var user = new Parse.User();
    user.set('username', username);
    user.set('password', password);
    return Parse.User.logIn(username, password);
  },
};

Authentication

To authenticate users, we will use the sessionToken returned during account creation or login.

We have two different ways to retrieve this token:

  • By request: in this case, we pass the sessionToken as a query argument.
  • By context: in this case, we add the token to the HTTP request header and then retrieve it in our resolvers through the GraphQL context.

Example: retrieve it through a request argument

const getPosts = {
  type: new GraphQLList(postType),
  args: {
    sessionToken: {
      type: GraphQLString,
    },
  },
  description: 'list of posts',
  resolve: (value, { sessionToken }, context) => {
    /* validate sessionToken */
    /* make query */
  },
};

Example: retrieve it through context

The first thing to do in this case is retrieve the token value from the header. We do this when defining the GraphQL server:

//GraphQL
app.use(
  '/graphql',
  GraphQLHTTP((request) => {
    return {
      graphiql: true,
      pretty: true,
      schema: schema,
      context: { sessionToken: request.headers['x-parse-session-token'] },
    };
  })
);

Then we retrieve the sessionToken in the query like this:

const getPosts = {
  type: new GraphQLList(postType),
  args: {
    sessionToken: {
      type: GraphQLString,
    },
  },
  description: 'list of posts',
  resolve: (value, args, { sessionToken }) => {
    /* validate sessionToken */
    /* make query */
  },
};

Tip: if you test this system with GraphiQL, you cannot add the sessionToken to the request header. The solution is to use this Chrome extension, which lets you add parameters to your request headers.

Access restriction

Now we will see how to limit a query to logged-in users only.

For that, we will add a function named isAuthorized:

const isAuthorized = async (token) => {
  const q = new Parse.Query(Parse.Session)
    .include('user')
    .equalTo('sessionToken', token);
  const session = await q.first({ useMasterKey: true });
  if (typeof session === 'undefined') {
    throw new Error('Unauthorized');
  }
  return session;
};

To determine whether the user is authorized to run the query, we pass the sessionToken to the function. Then we search (Parse.Query) for a session whose sessionToken matches the token passed as a parameter. Since a Parse session has a pointer to the session user, we include the user's data in the query. Finally, we test whether a session was found: if so, we return the session; otherwise, we throw an error.

All that remains is to add this function to every query or mutation resolver that we want to restrict.

Data restriction

To finish this article, we will add a constraint to data retrieval.

First, we will add a mutation that lets users add messages.

const createPost = {
  type: postType,
  description: 'add new post',
  args: {
    message: {
      type: GraphQLString,
    },
  },
  resolve: async (value, { message }, { sessionToken }) => {
    const session = await isAuthorized(sessionToken);
    var Post = Parse.Object.extend('Post');
    var post = new Post();
    post.set('message', message);
    post.set('author', session.get('user'));
    post.setACL(new Parse.ACL(session.get('user')));
    return post.save();
  },
};

The mutation has one argument, the message value. Then we restrict access to this mutation to authenticated users with the isAuthorized function. Finally, we create the new post and restrict read and write access to the user who creates the post using an ACL.

Now we modify the query that returns posts so it only returns posts created by the user.

const getPosts = {
  type: new GraphQLList(postType),
  description: 'list of posts',
  resolve: async (value, args, { sessionToken }) => {
    const session = await isAuthorized(sessionToken);
    var Post = Parse.Object.extend('Post');
    return new Parse.Query(Post).include('author').find({ sessionToken });
  },
};

Here, first we restrict access to logged-in users, then we run the query that retrieves the posts. To limit the results, we only need to pass the sessionToken to the find method, and Parse will take care of returning only the posts the user has access to.

And that's it: we are done with authentication and authorization with GraphQL and Parse Server.