Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to check permissions and other conditions in GraphQL query?

How could I check if user has permission to see or query something? I have no idea how to do this.

  • In args? How would that even work?
  • In resolve()? See if user has permission and somehow eliminate/change some of the args?

Example:

If user is "visitor", he can only see public posts, "admin" can see everything.

const userRole = 'admin';  // Let's say this could be "admin" or "visitor"

const Query = new GraphQLObjectType({
    name: 'Query',
    fields: () => {
        return {
            posts: {
                type: new GraphQLList(Post),
                args: {
                    id: {
                        type: GraphQLString
                    },
                    title: {
                        type: GraphQLString
                    },
                    content: {
                        type: GraphQLString
                    },
                    status: {
                        type: GraphQLInt  // 0 means "private", 1 means "public"
                    },
                },

                // MongoDB / Mongoose magic happens here
                resolve(root, args) {
                    return PostModel.find(args).exec()
                }
            }
        }
    }
})

Update - Mongoose model looks something like this:

import mongoose from 'mongoose'

const postSchema = new mongoose.Schema({
    title: {
        type: String
    },
    content: {
        type: String
    },
    author: {
        type: mongoose.Schema.Types.ObjectId,  // From user model/collection
        ref: 'User'
    },
    date: {
        type: Date,
        default: Date.now
    },
    status: {
        type: Number,
        default: 0    // 0 -> "private", 1 -> "public"
    },
})

export default mongoose.model('Post', postSchema)
like image 410
Solo Avatar asked May 20 '16 21:05

Solo


People also ask

How do you pass two arguments in GraphQL query?

Multiple arguments can be used together in the same query. For example, you can use the where argument to filter the results and then use the order_by argument to sort them.

How do you pass parameters in GraphQL query?

When you're passing arguments in code, it's generally better to avoid constructing the whole query string yourself. Instead, you can use $ syntax to define variables in your query, and pass the variables as a separate map. . then(data => console.


2 Answers

You can check a user's permission in the resolve function or in the model layer. Here are the steps you have to take:

  1. Authenticate the user before executing the query. This is up to your server and usually happens outside of graphql, for example by looking at the cookie that was sent along with the request. See this Medium post for more details on how to do this using Passport.js.
  2. Add the authenticated user object or user id to the context. In express-graphql you can do it via the context argument:

    app.use('/graphql', (req, res) => {
      graphqlHTTP({ schema: Schema, context: { user: req.user } })(req, res);
    }
    
  3. Use the context inside the resolve function like this:

    resolve(parent, args, context){
      if(!context.user.isAdmin){
        args.isPublic = true;
      }
      return PostModel.find(args).exec();
    }
    

You can do authorization checks directly in resolve functions, but if you have a model layer, I strongly recommend implementing it there by passing the user object to the model layer. That way your code will be more modular, easier to reuse and you don't have to worry about forgetting some checks in a resolver somewhere.

For more background on authorization, check out this post (also written by myself): Auth in GraphQL - part 2

like image 120
helfer Avatar answered Nov 18 '22 14:11

helfer


One approach that has helped us solve authorization at our company is to think about resolvers as a composition of middleware. The above example is great but it will become unruly at scale especially as your authorization mechanisms get more advanced.

An example of a resolver as a composition of middleware might look something like this:

type ResolverMiddlewareFn = 
  (fn: GraphQLFieldResolver) => GraphQLFieldResolver;

A ResolverMiddlewareFn is a function that takes a GraphQLFieldResolver and and returns a GraphQLFieldResolver.

To compose our resolver middleware functions we will use (you guessed it) the compose function! Here is an example of compose implemented in javascript, but you can also find compose functions in ramda and other functional libraries. Compose lets us combine simple functions to make more complicated functions.

Going back to the GraphQL permissions problem lets look at a simple example. Say that we want to log the resolver, authorize the user, and then run the meat and potatoes. Compose lets us combine these three pieces such that we can easily test and re-use them across our application.

const traceResolve =
  (fn: GraphQLFieldResolver) =>
  async (obj: any, args: any, context: any, info: any) => {
    const start = new Date().getTime();
    const result = await fn(obj, args, context, info);
    const end = new Date().getTime();
    console.log(`Resolver took ${end - start} ms`);
    return result;
  };

const isAdminAuthorized =
  (fn: GraphQLFieldResolver) =>
  async (obj: any, args: any, context: any, info: any) => {
    if (!context.user.isAdmin) {
      throw new Error('User lacks admin authorization.');
    }
    return await fn(obj, args, context, info);
  }

const getPost = (obj: any, args: any, context: any, info: any) => {
  return PostModel.find(args).exec();
}

const getUser = (obj: any, args: any, context: any, info: any) => {
  return UserModel.find(args).exec();
}

// You can then define field resolve functions like this:
postResolver: compose(traceResolve, isAdminAuthorized)(getPost)

// And then others like this:
userResolver: compose(traceResolve, isAdminAuthorized)(getUser)
like image 26
mparis Avatar answered Nov 18 '22 14:11

mparis