GraphQL: How to restrict fields server-side across

2019-04-07 20:39发布

问题:

Let's say I have a User type that contains the following fields:

type User {
  name: String
  username: String
  someOtherField1: String
  someOtherField2: String
  someOtherField3: String
  someOtherField4: String
  creditCardNumber: String
}

If I am querying for myself, it's okay to return all fields, because the information is mine. So returning creditCardNumber to the client is no biggie. But if I am querying for someone else, I should only be able to access the public info on the returned user. Returning creditCardNumber would be terrible. And even if I don't code the query on the client to do so, what would prevent a malicious user from digging into the code, updating the client-side query to include creditCardNumber, and executing it?

What is the best way to achieve this level of field restriction across queries in GraphQL? My only thought on this so far is to create a separate UserSearch type, i.e.

type UserSearch {
  name: String
  username: String
  someOtherField1: String
  someOtherField2: String
  someOtherField3: String
  someOtherField4: String
}

which excludes the private fields, however this does not feel DRY as you'd be creating many types that are 90% similar in structure form each other.

Is there a cleaner way to implement this, that doesn't create unnecessary types or duplicate fields?

回答1:

By default, GraphQL types are maybe types, which means you can return null to any field.

Which means, inside the resolve function of your creditCardNumber field, you could check if the current user is the user being fetched, and if so, you return the number. Otherwise, you return null.

To access "contexted" data such as the current user, GraphQL let you pass an object context when executing the graphql function:

graphql(schema, query, rootValue, context) // <-- the last parameter

And you will find this object in each field resolve function signature:

...
resolve: (object, args, context) => {
  // Third argument is your object
},
....

So what you can do, is pass the current logged user in the context:

graphql(schema, query, rootValue, { user: getCurrentLoggedUser() })

And in your User type, inside the creditCardNumber field resolve function:

creditCardNumber: {
  ...
  resolve: (user, args, context) => {
    if (user.id === context.user.id)
      return user.creditCardNumber;

    return null;
  },
  ...
}

If you are using graphql-express, the context is by default the request, and you can customize it.



标签: graphql