Rails Kitchen

It's a place to write on stuff I learned recently.

GraphQl Security Issues and Solutions

| Comments

One of the main highlights of GraphQl is declarative fetching, the client can define the shape of the data he needed and GraphQL server will provide data in the same shape. But an evil client can exploit this advantage to make deeper nesting and complex queries and can do DDoS attack.

Example for a deeper nesting query:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
query {
  articles{
    title
    body
    comments{
      comment
      user{
        comments{
          user{
            comments
            {
              comment
              user{
                comments{
                  comment
                  user{
                    comments{
                      comment
                      user{
                        name
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    }
  }
}
GraphQL provide three confirations to avoid this issue.

Timeout: We can avoid queries utilizing server more than specified time by setting a timeout. If a query takes more time for execute, then after time out GraphQL will return the result of query up to that time and simply reject nonexcecuted part of the query.
app/graphql/graphql_ruby_sample_schema.rb
1
2
3
4
5
6
7
8
GraphqlRubySampleSchema = GraphQL::Schema.define do
  query QueryType
  mutation MutationType
end

GraphqlRubySampleSchema.middleware << GraphQL::Schema::TimeoutMiddleware.new(max_seconds: 10) do |err, query|
  Rails.logger.info("GraphQL Timeout: #{query.query_string}")
end
Maximum Depth: Another solution for prevent deeply-nested queries is setting max_deplth. If our query depth is greater than max_depth we set, the server will simply reject full query and return an error message.
app/graphql/graphql_ruby_sample_schema.rb
1
2
3
4
5
6
GraphqlRubySampleSchema = GraphQL::Schema.define do
  query QueryType
  mutation MutationType

  max_depth 12
end
Query Complexity: We can prevent complex queries by setting max_complexity in GraphQL schema. Each field in our type system has a complexity number so if our query complexity exceeds max_complexity, the server will reject query just like in the case of max_depth.
app/graphql/graphql_ruby_sample_schema.rb
1
2
3
4
5
6
7
GraphqlRubySampleSchema = GraphQL::Schema.define do
  query QueryType
  mutation MutationType

  max_depth 12
  max_complexity 100
end

By default complexity of field is 1, but we can configure constant complexity for fields in type system and also can set complexity dynamically by passing conditions

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# Constant complexity:
field :comments, types[CommentType], complexity: 10

# Dynamic complexity:
field :top_comments, types[CommentType] do
  argument :limit, types.Int, default_value: 5
  complexity ->(ctx, args, child_complexity) {
    if ctx[:current_user].admin?
      # no limit for admin users
      0
    else
      # `child_complexity` is the value for selections
      # which were made on the items of this list.
      #
      # We don't know how many items will be fetched because
      # we haven't run the query yet, but we can estimate by
      # using the `limit` argument which we defined above.
      args[:limit] * child_complexity
    end
  }
end
You can see sample code here.

Comments