CodeGym /Courses /Module 5. Spring /Security in GraphQL

Security in GraphQL

Module 5. Spring
Level 16 , Lesson 5
Available

GraphQL, as a unified query mechanism, gives the client access to data through a single entry point — the GraphQL API. That simplifies the architecture, but it also makes security trickier:

  • Single point of failure. If that point is compromised, an attacker can get access to the whole system.
  • Query flexibility. A user can request too much data (or too deep nesting) in a single request.
  • Unbounded resources. GraphQL lets clients choose how much data they want, which can lead to abusing server resources.

So, we need to protect our data from unauthorized access, prevent server overloads, and enforce access policies.


Potential threats in GraphQL

  1. Denial-type attacks (DoS): for example, a performance attack where a client requests huge nested data (or cyclic queries).
  2. Privacy breaches: if users can access data they shouldn't be allowed to see.
  3. Unauthorized mutations: an attacker might try to change data without proper authorization.
  4. Injections via GraphQL queries: while GraphQL by nature mitigates SQL injection, dynamic resolvers can still mess things up.

Core security aspects in GraphQL

  1. Authentication. Who are you? It's the process of verifying a user's identity.
  2. Authorization. What can you do? It's checking permissions for performing certain actions.
  3. Query limits. Tools that protect the server from overload.
  4. Schema protection. Prevent leaking information about the API architecture.

Access control in GraphQL

The main approach is using the context (context) of the request. The context is an object passed to every request. It can hold info about the current user, their role, access token, and other params.

Example of setting up context for GraphQL:


@Bean
public GraphQL graphQL(GraphQLSchema schema) {
    return GraphQL.newGraphQL(schema)
            .defaultDataFetcherExceptionHandler(new CustomExceptionHandler())
            .instrumentation(new ContextSettingInstrumentation()) // Context for requests
            .build();
}

public class ContextSettingInstrumentation extends SimpleInstrumentation {
    @Override
    public InstrumentationContext<ExecutionInput> beginExecution(InstrumentationExecutionParameters parameters) {
        ExecutionInput executionInput = parameters.getExecutionInput();
        Map<String, Object> context = new HashMap<>();
        context.put("currentUser", fetchCurrentUserFromSecurityContext()); // Adding the user
        executionInput.transform(builder -> builder.context(context));
        return super.beginExecution(parameters);
    }
}

Authentication via JWT

Using JWT (JSON Web Token) lets you verify that the user actually has access rights.

1. Set up SecurityContext in Spring Security:


@Override
protected void configure(HttpSecurity http) throws Exception {
    http
      .authorizeRequests()
      .anyRequest().authenticated()
      .and()
      .oauth2ResourceServer()
      .jwt();
}

2. Check the user in a GraphQL DataFetcher:


public DataFetcher<String> getSecretInfo() {
    return environment -> {
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        if (authentication == null || !isUserAuthenticated(authentication)) {
            throw new AccessDeniedException("Unauthorized");
        }
        return "Here is your secret content!";
    };
}

Protecting data and the schema

Limiting query depth

One of the simplest attack vectors on a GraphQL API is crafting requests with high nesting, for example:


query {
  user {
    friends {
      friends {
        friends {
          friends {
            name
          }
        }
      }
    }
  }
}

To prevent that, use the graphql-java-servlet library, which lets you configure query depth limits.

Example of adding a depth limit:


GraphQLSchema schema = SchemaGenerator.newBuilder()
        .query(queryType)
        .additionalDirective(graphql.schema.idl.SchemaPrinter.Options.defaultOptions().maxQueryDepth(10))
        .build();

Limiting query complexity

Query complexity is the sum of "weights" of all requested fields. The more nested data requested, the higher the complexity. You can assign weights to fields manually:


public class CustomFieldComplexityCalculator implements FieldComplexityCalculator {
    @Override
    public int calculate(FieldComplexityEnvironment environment, int childComplexity) {
        if ("user".equals(environment.getField().getName())) {
            return 5 + childComplexity; // Weight of the "user" field - 5
        }
        return 1 + childComplexity;
    }
}

Then configure GraphQL like this:


GraphQL.newGraphQL(schema)
       .instrumentation(new MaxQueryComplexityInstrumentation(50)) // Query complexity limit
       .build();

Applying authorization at the schema level

Use annotations to restrict access to certain fields:


@GraphQLField
@PreAuthorize("hasRole('ADMIN')")
public String getAdminData() {
    return "Admins only!";
}

Spring Security will automatically check the user's role before the method executes.


Practice: configuring GraphQL API security

Step 1: Set up Spring Security.

Add dependencies to pom.xml:


<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
</dependency>

Step 2: Configure JWT in Spring Boot.

Add settings to application.yml:


spring:
  security:
    oauth2:
      resourceserver:
        jwt:
          issuer-uri: https://your-issuer.com

Step 3: Add access checks in Data Fetchers.

Integrate user permission checks into GraphQL request handlers:


public DataFetcher<String> getSecureInfo() {
    return environment -> {
        if (!environment.getContext().get("isAuthenticated")) {
            throw new AccessDeniedException("Access Denied");
        }
        return "Secret data for authenticated users";
    };
}

Step 4: Limit query depth and complexity.

Add depth and complexity limits to your GraphQL configuration:


GraphQL.newGraphQL(schema)
       .instrumentation(new MaxQueryDepthInstrumentation(10))
       .instrumentation(new MaxQueryComplexityInstrumentation(50))
       .build();

Conclusion

If security were a pie, we've just added enough ingredients to bake the recipe! Now your GraphQL API is protected from excessive queries, unauthorized access, and common attacks. But remember, security isn't a "set it and forget it" thing. Regularly review your configs, keep libraries up to date, and test for vulnerabilities.

On to the next step!

Comments
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION