Back to Blog
Lesson 53 of the GraphQL: Your First GraphQL Schema & Server course
API ArchitectureSeptember 9, 20263 min read

Securing the Schema: Field-Level Authorization in GraphQL

Learn how to secure your GraphQL schema by implementing field-level authorization. Protect sensitive data and control access with robust resolver logic.

GraphQLSecurityAuthorizationBackendAPI Architecture
Misty airport runway blocked by safety barriers for authorized access only.

Previously in this course, we covered monitoring GraphQL performance to identify bottlenecks in our resolvers. Now that our server is performant, we must ensure it is secure. In this lesson, we move beyond basic authentication to implement field-level authorization, ensuring that sensitive data is only visible to those with the proper permissions.

The Principle of Least Privilege in GraphQL

In a standard REST API, you might have separate endpoints like /users/me and /users/public. In GraphQL, we often expose a single User object. If your User type includes an email or phoneNumber field, a simple query could expose that data to any client that provides a valid token.

Securing the schema means we don't just check if a user is logged in; we check if they are authorized to see specific fields within a requested object. By moving this logic into the resolver, we treat data access as a granular concern rather than a global one.

Implementing Field-Level Authorization

The most effective way to secure a schema is to leverage the context object—which we explored in using the context object—to inject the current user's session.

When a resolver executes, it can check the user's role or ownership status before returning the value. If the criteria aren't met, you return null or throw a GraphQLError.

Worked Example: Protecting Sensitive Fields

Suppose our User object has a privateEmail field. Only the owner of the account should be able to see it.

JAVASCRIPT
const resolvers = {
  User: {
    // Only the owner can see their own private email
    privateEmail: (parent, args, context) => {
      // CE9178">'parent' is the user object being resolved
      // CE9178">'context.user' is the authenticated user from our middleware
      if (!context.user) {
        throw new Error("Not authenticated");
      }

      if (context.user.id !== parent.id) {
        return null; // Or throw a Forbidden error
      }

      return parent.email;
    },
  },
};

By returning null for unauthorized users, we satisfy the schema contract while maintaining security. If your schema defines the field as non-nullable, you should throw an error instead, as returning null would violate the schema contract and cause the entire query to fail.

Hands-on Exercise: Restricting Admin Fields

In your current project, identify a field in your User or Post type that contains sensitive information (e.g., internalNotes or createdAt timestamps that should be hidden).

  1. Update your schema to include this sensitive field.
  2. In your resolver map, create a specific resolver for that field.
  3. Check context.user to verify if the user is an admin or the owner.
  4. If unauthorized, return null. Test this by running a query in Apollo Sandbox with and without a mock user in the context.

Common Pitfalls

  • Forgetting the Nullable Constraint: If you define a field as String! in your schema, returning null in your resolver will trigger a GraphQL execution error. Ensure your schema definitions match your security logic.
  • Over-reliance on Frontend Hiding: Never assume the client will hide a field. The schema is the source of truth; if the field exists in the SDL, the server must authorize it.
  • Complex Authorization in Root Resolvers: Don't put all your logic in the top-level query. Use the "field-level" approach described above to keep your code DRY and maintainable.

FAQ

Q: Should I use directives for security? A: You can use custom schema directives to abstract authorization, but for beginners, explicit resolver logic is easier to debug and reason about.

Q: Is throwing an error better than returning null? A: Returning null is better for partial data responses (e.g., showing a user profile where some fields are hidden). Throwing an error is better for sensitive data where the user shouldn't even know the field exists.

Recap

Securing the schema is about granular control. By checking context within individual field resolvers, you ensure that sensitive data is protected regardless of how the client constructs their query. This approach is more flexible than blocking entire objects and aligns with the philosophy of evolving your schema over time.

Up next: We will explore how to implement pagination to keep your data payloads lean and efficient.

Similar Posts