Skip to content

Commit 20317e3

Browse files
committed
Add security best practices page.
1 parent 487845d commit 20317e3

File tree

2 files changed

+198
-0
lines changed

2 files changed

+198
-0
lines changed

src/pages/learn/_meta.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,4 +20,5 @@ export default {
2020
pagination: "",
2121
"global-object-identification": "",
2222
caching: "",
23+
security: "",
2324
}

src/pages/learn/security.mdx

Lines changed: 197 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,197 @@
1+
# Security
2+
3+
<p className="learn-subtitle">Protect GraphQL APIs from malicious operations</p>
4+
5+
As with any type of API, you will need to consider what security measures should be used to protect a GraphQL implementation's server resources and underlying data sources during request execution.
6+
7+
Many generalizable API security best practices apply to GraphQL, especially in relation to a given implementation's selected transport protocol. However, there are GraphQL-specific security considerations that will make the API layer more resilient to potential attacks too.
8+
9+
On this page, we'll survey potential attack vectors for GraphQL—many of which are denial of service attacks—along with how a layered security posture can help protect a GraphQL API from malicious operations.
10+
11+
## Transport layer security
12+
13+
The GraphQL specification does not require a specific transport protocol for requests, but [HTTP is a popular choice](/learn/serving-over-http/) for stateless query and mutation operations. For longer-lived subscription operations, WebSockets or server-sent events are often used. Whatever the chosen protocol, security considerations in the transport layer can provide an important first line of defense for a GraphQL API.
14+
15+
The same security measures that would be used for any type of API served with a given transport protocol should typically be used for GraphQL as well. For example, when using HTTP for queries and mutations, you should use HTTPS to encrypt data, set appropriate timeout durations for requests, and, if using HTTP caching, ensure sensitive data is cached privately (or not at all).
16+
17+
## Demand control
18+
19+
### Paginated fields
20+
21+
A first step toward implementing demand control for a GraphQL API is limiting the amount of data that can be queried from a single field. When a field outputs a List type and the resulting list could potentially return a lot of data, it should be paginated to limit the maximum number of items that may be returned in a single request.
22+
23+
For example, compare the unbounded output of the `friends` field to the limited data returned by the `friendsConnection` field in this operation:
24+
25+
```graphql
26+
# { "graphiql": true }
27+
query {
28+
hero {
29+
name
30+
friends {
31+
name
32+
}
33+
friendsConnection(first: 1) {
34+
edges {
35+
node {
36+
name
37+
}
38+
}
39+
}
40+
}
41+
}
42+
```
43+
44+
Read more about the connection-based model for paginating fields on the [Pagination page](/learn/pagination/).
45+
46+
### Depth limiting
47+
48+
One of GraphQL's strengths is that clients can write expressive operations that reflect the relationships between the data exposed in the API. However, some queries may become cyclical when the fields in the selection set are deeply nested:
49+
50+
```graphql
51+
query {
52+
hero {
53+
name
54+
friends {
55+
name
56+
friends {
57+
name
58+
friends {
59+
name
60+
friends {
61+
name
62+
}
63+
}
64+
}
65+
}
66+
}
67+
}
68+
```
69+
70+
Even when [the N+1 problem](/learn/performance/#the-n1-problem) has been remediated through batched requests to underlying data sources, overly nested fields may still place excessive load on server resources and impact API performance.
71+
72+
For this reason, it's a good idea to limit the maximum depth of fields that a single operation can have. Many GraphQL implementations expose configuration options that allow you to specify a maximum depth for a GraphQL document and return an error to the client if a request exceeds this limit before execution begins.
73+
74+
For cases where a client may have a legitimate use case for a deeply nested query and it's impractical to set a blanket limit on all queries, you may instead opt for applying depth limits specifically to list fields instead, or using [rate limiting](/#rate-limiting) instead.
75+
76+
### Breadth and batch limiting
77+
78+
In addition to limiting operation depth, there should also be guardrails in place to limit the number of top-level fields and field aliases included in a single operation.
79+
80+
Consider what would happen during the execution of the following query operation:
81+
82+
```graphql
83+
query {
84+
hero1: hero(episode: NEWHOPE) {
85+
name
86+
}
87+
hero2: hero(episode: EMPIRE) {
88+
name
89+
}
90+
### ...
91+
hero100: hero(episode: JEDI) {
92+
name
93+
}
94+
}
95+
```
96+
97+
Even though the overall depth of this query is shallow, the underlying data source for the API will still have to handle a large number of requests to resolve data for the aliased `hero` field.
98+
99+
Similarly, a client may send a GraphQL document with many batched operations in a request:
100+
101+
```graphql
102+
query NewHopeHero {
103+
hero(episode: NEWHOPE) {
104+
name
105+
}
106+
}
107+
query EmpireHero {
108+
hero(episode: EMPIRE) {
109+
name
110+
}
111+
}
112+
### ...
113+
query JediHero {
114+
hero(episode: JEDI) {
115+
name
116+
}
117+
}
118+
```
119+
120+
Depending on the client implementation, query batching can be a useful strategy for limiting the number of round trips to a server to fetch all of the required data to render a user interface. However, there should be an upper limit on the total number of queries allowed in a single batch.
121+
122+
As with depth limiting, a GraphQL implementation may have configuration options to restrict operation breadth, field alias usage, and batching.
123+
124+
### Rate limiting
125+
126+
Depth, breadth, and batch limiting help prevent broad categories of malicious operations such as cyclic queries and batching attacks, but they don't provide a way to declare that a particular field is computationally expensive to resolve. So for more advanced demand control requirements, you may wish to implement rate limiting.
127+
128+
Rate limiting may take place in different layers of an application, for example, in the network layer or the business logic layer. Because GraphQL allows clients to specify exactly what data they need in their queries, a server may not be able to know in advance if a request includes fields that will place a higher load on its resources during execution. As a result, applying useful rate limits for a GraphQL API typically requires a different approach than simply keeping track of the total number of incoming requests over a time period in the network layer.
129+
130+
_Query complexity analysis_ is one method that can be used to rate limit GraphQL requests by applying weights to the types and fields in a schema and then analyzing an incoming document to determine if the combination of fields included in its selection set exceeds a maximum allowable cost per request. If the request proceeds, the total cost of the request can be deducted from the overall query budget allocated for a specific period.
131+
132+
While the GraphQL specification doesn't provide any guidelines on implementing query complexity analysis or rate limits for an API, there is [a community-maintained draft specification]((https://ibm.github.io/graphql-specs/cost-spec.html)) for implementing custom type system directives that support these calculations.
133+
134+
### Trusted documents
135+
136+
For GraphQL APIs that only serve first-party clients, using _trusted documents_ will allow you to create an allowlist of operations that can be executed against a schema.
137+
138+
As a build step during development, clients may submit their GraphQL documents to the server's allowlist to be stored using the hashed document string as an ID. At runtime, the client can send the hashed document ID instead of the full GraphQL document, and the server will only execute the request for known document IDs.
139+
140+
The GraphQL specification doesn't require the use of trusted documents or indicate how they should be implemented, but there are an increasing number of GraphQL implementations that support them.
141+
142+
## Schema considerations
143+
144+
### Validating and sanitizing argument values
145+
146+
GraphQL is strongly typed, and the [validation stage](/learn/validation/) of a request will check a parsed document against the type system to determine its validity. However, for certain cases you may find that the built-in Scalar types aren't enough to make sure that the argument value a client provides is appropriate to execute a field resolver safely.
147+
148+
Consider the following `ReviewInput` type that is used with the `createReview` mutation:
149+
150+
```graphql
151+
input ReviewInput {
152+
stars: Int!
153+
commentary: String
154+
}
155+
156+
type Mutation {
157+
createReview(episode: Episode, review: ReviewInput!): Review
158+
}
159+
```
160+
161+
Depending on what kind of client application will submit and display the review data, it may be necessary to sanitize the string provided for the `commentary` field in case a user submits unsafe HTML in it.
162+
163+
The sanitization step should take place in the business logic layer that is called during execution of the `createReview` resolver function. To surface this to frontend developers, it could also be expressed as a part of the schema using a custom [Scalar type](/learn/schema/#scalar-types).
164+
165+
### Introspection
166+
167+
[Introspection](/learn/introspection/) is a powerful feature of GraphQL that allows you to query a GraphQL API for information about its schema. However, GraphQL APIs that only serve first-party clients may forbid introspection queries in non-development environments.
168+
169+
Note that disabling introspection is a form of "security through obscurity" and will not be sufficient to protect a GraphQL API by itself, but it may be used as a part of a broader security strategy to limit the discoverability of API information from potential attackers. For more comprehensive protection of sensitive schema details and user data, trusted documents and authorization should be used.
170+
171+
### Error messages
172+
173+
On the [Response page](/learn/response/), we saw that a GraphQL implementation may provide developers with useful information about validation errors in a request, along with suggestions for how these errors may be addressed. For example:
174+
175+
```graphql
176+
# { "graphiql": true }
177+
query {
178+
starship(id: 3000) {
179+
width
180+
}
181+
}
182+
```
183+
184+
These hints can be helpful when debugging client-side errors, but they may provide more information about a schema in a production environment than we would like to reveal. Hiding detailed error information in GraphQL responses outside of development environments is important because, even with introspection disabled, an attacker could ultimately infer the shape of an entire schema by running numerous operations with incorrect field names.
185+
186+
## Authentication and authorization
187+
188+
Auth-related considerations for GraphQL APIs are discussed in-depth on the [Authorization page](/learn/authorization/).
189+
190+
## Recap
191+
192+
To recap these recommendations for securing a GraphQL API:
193+
194+
- General security considerations for a chosen transport protocol will usually apply to a GraphQL API as well
195+
- Depending on your requirements, demand control can be implemented in many ways in GraphQL, including pagination of list fields, depth limiting, breadth/batch limiting, rate limiting, and using trusted documents
196+
- A GraphQL schema can help support validation and sanitization of client-provided values
197+
- Disabling introspection and obscuring error information can make an API's schema less discoverable, but they will typically not be sufficient when used on their own (a document allowlist is likely to be a more effective solution)

0 commit comments

Comments
 (0)