Search code examples
springspring-securitygraphqlgraphql-javaspring-graphql

Secure a Spring GraphQL API from traversal attacks


If I have a GraphQL API looking like this:

type Query {
  userById (id: ID): User
}

type User {
  id: ID
  name: String
  secret: String
  supervisor: Supervisor
}

type Supervisor {
  id: ID
  name: String
  users: [User]
}

User X is logged in and triggers the legitimate query:

query {
  userById (id: "X") {
    name
    secret
    supervisor: {
      name
    }
  }
}

He is authorized since he has access to his own User object.

But what if the user modifies the query to this:

query {
  userById (id: "X") {
    name
    secret
    supervisor: {
      name
      users {
        secret
      }
    }
  }
}

How can I secure users from fetching other users by traversing the graph. Specifically using Spring for GraphQL, https://spring.io/projects/spring-graphql. I also use Spring Security.


Solution

  • I managed to do what I want by configuring like this (simplified here - in reality I delegate the logic to a Spring bean implementing the DataFetcher interface which in turn uses @Service:s which handles security). This way the default PropertyDataFetcher is replaced by my own implementation, where security logic may be performed.

    @Configuration
    public class GraphQLConfig implements RuntimeWiringConfigurer {
    
        @Autowired
        private SupervisorRepository supervisorRepository;
    
        @Override
        public void configure(RuntimeWiring.Builder builder) {
            GraphQLCodeRegistry codeRegistry = GraphQLCodeRegistry.newCodeRegistry()
                    .dataFetcher(
                            coordinates("User", "supervisor"),
                            (DataFetcher<Supervisor>) environment -> {
                                User user = environment.getSource();
    
                                Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
    
                                // Do security stuff and find supervisor by the user entity
                                supervisorRepository.find...
                            }
                    ).build();
    
    
            builder.codeRegistry(codeRegistry);
        }
    }