Configuring CORS
Control access to your server's resources
⚠️ New in Apollo Server 3.7: we highly recommend that all users pass
csrfPrevention: true
tonew ApolloServer()
to protect your server from CSRF and XS-Search attacks. This is especially important if you integrategraphql-upload
into your server. If you enable this feature, any clients that send operations viaGET
requests or multipart upload requests must include a non-emptyApollo-Require-Preflight
(or any other non-special-cased) header in that request. For more information, see Preventing Cross-Site Request Forgery (CSRF).
⚠️
graphql-upload
has a known CSRF vulnerability and is turned on by default in Apollo Server 2, specifically versions< 2.25.4
. If you are using Apollo Server 2 and do not use uploads, you can prevent this by disabling uploads by passinguploads: false
tonew ApolloServer()
or by upgrading to version^2.25.4
. If you do use uploads, you must update to Apollo Server 3.7 or later and enable CSRF prevention in order to use this feature safely.
Cross-Origin Resource Sharing (CORS) is an HTTP-header-based protocol that enables a server to dictate which origins can access its resources. Put another way, your server can specify which websites can tell a user's browser to talk to your server, and precisely which types of HTTP requests are allowed.
Apollo Server's default CORS behavior enables any website on the internet to tell a user's browser to connect to your server.
⚠️ If your app is only visible on a private network and uses network separation for security, the default CORS behavior is not secure. See Specifying origins for more information.
By default, websites running on domains that differ from your server's domain can't pass cookies with their requests. For details on enabling cross-origin cookie passing for authentication, see Passing credentials with CORS.
Why use CORS?
Most developers know about CORS because they run into the all-too-common CORS error. CORS errors usually occur when you set up an API call or try to get your separately hosted server and client to talk to each other. To better understand what CORS is and why we use it, we'll briefly go over some background context.
Internet users should always exercise caution when installing any new software on their devices. But when it comes to browsing the web, we navigate to different sites all the time, letting our browsers load content from those sites along the way. This comes with inherent risks.
As web developers, we don't want a user's browser to do anything fishy to our server while the user is visiting another website. Browser security mechanisms (e.g., CORS or SOP) can give developers peace of mind by enabling a website's server to specify which browser origins can request resources from that server.
The origin of a piece of web content consists of that content's domain, protocol, and port. The same-origin policy (SOP) is a security mechanism that restricts scripts on one origin from interacting with resources from another origin. This means that scripts on websites can interact with resources from the same origin without jumping through any extra hoops.
If two URLs differ in their domain, protocol, or port, then those URLs come from two different origins:
1# Same origin
2http://example.com:8080/ <==> http://example.com:8080/
3
4# Different origin (difference in domain, protocol, and port)
5http://example.com:8080/ =X= https://example1.com:8081/
However, as we all know, the internet is an exciting place full of resources that can make websites better (importing images, extra fonts, making API calls, and so on). Developers needed a new protocol to relax SOP and safely share resources across different origins.
Cross-Origin Resource Sharing is the mechanism that allows a web page to share resources across different origins. CORS provides an extra layer of protection by enabling servers and clients to define HTTP headers that specify which external clients' scripts can access their resources.
Note that both SOP and CORS are related to browser security. Neither prevents other types of software from requesting resources from your server.
Choosing CORS options for your project
When thinking about configuring CORS for your application, there are two main settings to consider:
Which origins can access your server's resources
Whether your server accepts user credentials (i.e., cookies) with requests
Specifying origins
CORS uses specific HTTP response headers as part of its protocol, including Access-Control-Allow-Origin
. The Access-Control-Allow-Origin
header (ACAO) enables a server to dictate which origins can use scripts to access that server's resources.
Depending on what you're building, the origins you specify in your CORS configuration might need to change when you're ready to deploy your application.
⚠️ Applications on private networks
If your browser is running your API on a private network (i.e., not on the public internet) and it relies on the privacy of that network for security, the default CORS behavior of Apollo Server is insecure. We strongly recommend specifying which origins can access your server's resources.
If you don't, while your personal computer is on your private network, a script on any website could potentially make your browser talk to your private API. Some browsers, such as Chrome, have features under development to solve this problem. But in the meantime, all servers on private networks should always specify origins in their CORS configuration.
Federated subgraphs
If you're building a federated graph, we strongly recommend that all of your production subgraph servers disable CORS (or at least specify origins and limit them to tools like the Apollo Explorer). Most subgraphs should allow communication only from your gateways, and that communication doesn't involve a browser at all.
For more information, see Securing your subgraphs.
APIs that require cookies
If your API needs to accept cross-origin cookies with requests, you must specify origins in your CORS configuration. Otherwise, cross-origin cookies are automatically disabled. This is not a security vulnerability, but it does prevent your API from successfully providing cookies.
For examples, see Passing credentials with CORS.
APIs with known consumers
If you create an API on the public internet to serve resources to your own websites or apps, you might want to specify which origins can access your server's resources. Explicitly specifying origins can provide an extra level of security.
Public or embedded APIs
If you create a public API or an API to embed in websites you don't control yourself, you probably want to allow all origins to access your server's resources. You can use Apollo Server's default ACAO value (the wildcard *
) in these situations, allowing requests from any origin.
Using the wildcard (
*
) value for the ACAO header enables any website to tell a user's browser to send an arbitrary request (without cookies or other credentials) to your server and read that server's response.
None of the above
If your application doesn't fit into any of the above categories, the default Apollo Server CORS behavior should suit your use case. You can always specify origins in your CORS configuration later or disable cross-origin requests by disabling CORS for your server.
Configuring CORS options for Apollo Server
By default, the batteries-included apollo-server
package serves the Access-Control-Allow-Origin
HTTP header with the wildcard value (*
). This allows scripts on any origin to make requests, without cookies, to the server and read its responses.
If you need to pass credentials to your server (e.g., via cookies), you can't use the wildcard value (
*
) for your origin. You must provide specific origins. For examples, see Passing credentials with CORS.
The batteries-included apollo-server
has a cors
option, which you can use to change your server's default CORS behavior:
1const server = new ApolloServer({
2 typeDefs,
3 resolvers,
4 csrfPrevention: true, // see below for more about this
5 cache: "bounded",
6 cors: {
7 origin: ["https://www.your-app.example", "https://studio.apollographql.com"]
8 },
9 plugins: [
10 ApolloServerPluginLandingPageLocalDefault({ embed: true }),
11 ],
12});
Under the hood, the batteries-included apollo-server
wraps around middleware provided by the cors
npm package, enabling you to configure the Access-Control-Allow-Origin
header via the origin
option. The example above enables CORS requests from https://www.your-app.example
, along with https://studio.apollographql.com
.
Note if you plan to use Apollo Studio Explorer as a GraphQL web IDE you should include
https://studio.apollographql.com
in your list of valid origins. However, if you choose to embed the Apollo Studio Explorer or Sandbox, you don't need to add Studio to your CORS configuration at all. Requests will go through the page embedding Studio.
If you're using another integration of Apollo Server, you can add the cors
configuration option to your framework-specific middleware function to change your server's CORS behavior. To learn more about the CORS defaults and options for your integration of Apollo Server, see CORS configuration options.
Below is an example of setting up CORS with the apollo-server-express
package:
1// ... set up the server
2await server.start();
3
4const corsOptions = {
5 origin: ["https://www.your-app.example", "https://studio.apollographql.com"]
6};
7
8server.applyMiddleware({
9 app,
10 cors: corsOptions,
11 path: "/graphql",
12});
You can also remove CORS middleware entirely to disable cross-origin requests. In the batteries-included apollo-server
and apollo-server-express
, you do this by passing cors: false
. This is recommended for subgraphs in a federated graph.
Passing credentials with CORS
If your server requires requests to include a user's credentials (e.g., cookies), you need to modify your CORS configuration to tell the browser those credentials are allowed.
You can enable credentials with CORS by setting the Access-Control-Allow-Credentials
HTTP header to true
.
You must specify an origin to enable credentialed requests. If your server sends the
*
wildcard value for theAccess-Control-Allow-Origin
HTTP header, your browser will refuse to send credentials.
To enable your browser to pass credentials with the batteries-included apollo-server
you can use the cors
option to specify origins
and set credentials
to true
:
1const server = new ApolloServer({
2 typeDefs,
3 resolvers,
4 csrfPrevention: true, // see below for more about this
5 cache: "bounded",
6 cors: {
7 origin: yourOrigin,
8 credentials: true
9 },
10 plugins: [
11 ApolloServerPluginLandingPageLocalDefault({ embed: true }),
12 ],
13});
Using cors
middleware with an integration of Apollo Server, you can set the credentials
option to true
before passing it to your framework-specific middleware function:
1// The example below is using the apollo-server-express package.
2
3// ... set up the server
4await server.start();
5
6const corsOptions = { origin: yourOrigin, credentials: true };
7
8server.applyMiddleware({
9 app,
10 cors: corsOptions,
11 path: "/graphql",
12});
The
cors
middlewareorigin
option also accepts a boolean value, which means you can technically configure CORS to allow all cross-origin requests with credentials (i.e.,{origin: true, credentials: true}
). This is almost certainly insecure, because any website could read information previously protected by a user's cookies. Instead, you should specify origins in your CORS configuration whenever you enable credentials.
For examples of sending cookies and authorization headers from Apollo Client, see Authentication. For more guidance on adding authentication logic to Apollo Server, see Authentication and authorization.
Preventing Cross-Site Request Forgery (CSRF)
Your server's CORS policy enables you to control which websites can talk to your server. In most cases, the browser checks your server's CORS policy by sending a preflight request before sending the actual operation. This is a separate HTTP request. Unlike most HTTP requests (which use the GET
or POST
method), this request uses a method called OPTIONS
. The browser sends an Origin
header, along with some other headers that start with Access-Control-
. These headers describe the kind of request that the potentially untrusted JavaScript wants to make. Your server returns a response with Access-Control-*
headers describing its policies (as described above), and the browser uses that response to decide whether it's OK to send the real request. Processing the OPTIONS
preflight request never actually executes GraphQL operations.
However, in some circumstances, the browser will not send this preflight request. If the request is considered "simple", then the browser just sends the request without sending a preflight first. Your server's response can still contain Access-Control-*
headers, and if they indicate that the origin that sent the request shouldn't be able to access the site, the browser will hide your server's response from the problematic JavaScript code.
Unfortunately, this means that your server might execute GraphQL operations from "simple" requests sent by sites that shouldn't be allowed to communicate with your server. And these requests can even contain cookies! Although the browser will hide your server's response data from the malicious code, that might not be sufficient. If running the operation has side effects, then the attacker might not care if it can read the response or not, as long as it can use an unsuspecting user's browser (and cookies!) to trigger those side effects. Even with a read-only query, the malicious code might be able to figure out something about the response based entirely on how long the query takes to execute.
Attacks that use simple requests for their side effects are called "cross-site request forgery" attacks, or CSRF. Attacks that measure the timing of simple requests are called "cross-site search" attacks, or XS-Search.
To avoid CSRF and XS-Search attacks, GraphQL servers should refuse to execute any operation coming from a browser that has not "preflighted" that operation. There's no reliable way to detect whether a request came from a browser, so GraphQL servers should not execute any operation in a "simple request".
The most important rule for whether or not a request is "simple" is whether it tries to set arbitrary HTTP request headers. Any request that sets the Content-Type
header to application/json
(or anything other than a list of three particular values) cannot be a simple request, and thus it must be preflighted. Because all POST
requests recognized by Apollo Server must contain a Content-Type
header specifying application/json
, we can be confident that they are not simple requests and that if they come from a browser, they have been preflighted.
However, Apollo Server also handles GET
requests. GET
requests do not require a Content-Type
header, so they can potentially be simple requests. So how can we ensure that we only execute GET
requests that are not simple requests? If we require the request to include an HTTP header that is never set automatically by the browser, then that is sufficient: requests that set HTTP headers other than the handful defined in the spec must be preflighted.
Apollo Server 3.7 introduced a CSRF prevention feature, which you can enable by passing csrfPrevention: true
to new ApolloServer()
. When this feature is enabled, Apollo Server only executes GraphQL operations if at least one of the following conditions is true:
The incoming request includes a
Content-Type
header that specifies a type other thantext/plain
,application/x-www-form-urlencoded
, ormultipart/form-data
. Notably, aContent-Type
ofapplication/json
(including any suffix likeapplication/json; charset=utf-8
) is sufficient. This means that allPOST
requests (which must useContent-Type: application/json
) will be executed. Additionally, all versions of Apollo Client Web that supportGET
requests do includeContent-Type: application/json
headers, so any request from Apollo Client Web (POST
orGET
) will be executed.There is a non-empty
X-Apollo-Operation-Name
header. This header is sent with all operations (POST
orGET
) by Apollo iOS (v0.13.0+) and Apollo Kotlin (all versions, including its former name "Apollo Android"), so any request from Apollo iOS or Apollo Kotlin will be executed.There is a non-empty
Apollo-Require-Preflight
header.
Note that all HTTP header names are case-insensitive.
CSRF prevention is only applied to requests that will execute GraphQL operations, not to requests that would load landing pages or run health checks.)
HTTP requests that satisfy none of the conditions above will be rejected with a 400 status code and a message clearly explaining which headers need to be added in order to make the request succeed.
We highly recommend that you enable CSRF prevention by passing csrfPrevention: true
to new ApolloServer()
. This feature will be enabled by default in Apollo Server 4.
It should have no impact on legitimate use of your graph except in these two cases:
you have clients that send
GET
requests and are not Apollo Client Web, Apollo iOS, or Apollo Kotlinyou have enabled file uploads with
graphql-upload
If either of these apply to you, you should configure the relevant clients to send a non-empty Apollo-Require-Preflight
header along with all requests.
You can also configure the set of headers that allow execution. For example, if you use a GraphQL client that performs GET
requests without sending Content-Type
, X-Apollo-Operation-Name
, or Apollo-Require-Preflight
headers, but it does send a Some-Special-Header
header, you can pass csrfPrevention: { requestHeaders: ['Some-Special-Header'] }
to new ApolloServer()
. This option replaces checking for the two headers X-Apollo-Operation-Name
and Apollo-Require-Preflight
in the CSRF prevention logic. The check for Content-Type
remains the same.
Note that Apollo Server does not permit executing mutations in GET
requests (just queries). As long as you ensure that only mutations can have side effects, you are somewhat protected from the "side effects" aspect of CSRFs even without enabling CSRF protection. However, you would still be vulnerable to XS-Search timing attacks. In addition, file uploads as implemented by the third-party graphql-upload
package adds a special middleware that parses POST
requests with a Content-Type
of multipart/form-data
. This is one of the three special Content-Type
s that can be set on simple requests, so this lets your server process mutations sent in simple requests! All users of graphql-upload
should enable csrfPrevention
and configure their upload clients to send a non-empty Apollo-Require-Preflight
header.