Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 32 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,27 @@ http://localhost:5000/graphql?query={hero}
{"data":{"hero":"Luke Skywalker"}}
```

### Basic options

By default, the middleware will be installed with these configurable options:
- GET, POST, and WebSocket requests are all enabled
- Form content types are disabled, and cross-site request forgery (CSRF)
protection is enabled
- There are no authentication or authorization requirements
- The default response content type is `application/graphql-response+json`
- The middleware will use the default schema instance

To configure these options, pass a confiuguration delegate to the `UseGraphQL`
method as demonstrated below:

```csharp
app.UseGraphQL("/graphql", opts => {
opts.ReadFormOnPost = true;
});
```

Configuration of these options and more are further described below in this document.

### Configuration with endpoint routing

To use endpoint routing, call `MapGraphQL` from inside the endpoint configuration
Expand Down Expand Up @@ -576,7 +597,7 @@ will reject requests that do not meet one of the following criteria:
`application/x-www-form-urlencoded`, `multipart/form-data`, or `text/plain`.
- The request includes a non-empty `GraphQL-Require-Preflight` header.

To disable this behavior, set the `CsrfProtectionEnabled` option to `false` in the `GraphQLServerOptions`.
To disable this behavior, set the `CsrfProtectionEnabled` option to `false`.

```csharp
app.UseGraphQL("/graphql", config =>
Expand Down Expand Up @@ -696,7 +717,7 @@ methods allowing for different options for each configured endpoint.
| `MaximumFileSize` | Sets the maximum file size allowed for GraphQL multipart requests. | unlimited |
| `MaximumFileCount` | Sets the maximum number of files allowed for GraphQL multipart requests. | unlimited |
| `ReadExtensionsFromQueryString` | Enables reading extensions from the query string. | True |
| `ReadFormOnPost` | Enables parsing of form data for POST requests (may have security implications). | True |
| `ReadFormOnPost` | Enables parsing of form data for POST requests (may have security implications). | False |
| `ReadQueryStringOnPost` | Enables parsing the query string on POST requests. | True |
| `ReadVariablesFromQueryString` | Enables reading variables from the query string. | True |
| `ValidationErrorsReturnBadRequest` | When enabled, GraphQL requests with validation errors have the HTTP status code set to 400 Bad Request. | True |
Expand Down Expand Up @@ -948,16 +969,13 @@ even when the CORS policy prohibits it, regardless of whether the sender has acc
This situation exposes the system to security vulnerabilities, which should be carefully evaluated and
mitigated to ensure the safe handling of GraphQL requests and maintain the integrity of the data.

This functionality is activated by default to maintain backward compatibility, but it can be turned off by
setting the `ReadFormOnPost` value to `false`. The next major version of GraphQL.NET Server will have this
feature disabled by default, enhancing security measures.

Keep in mind that CORS pre-flight requests are also not executed for GET requests, potentially presenting a
security risk. However, GraphQL query operations usually do not alter data, and mutations are refused.
Additionally, the response is not expected to be readable in the browser (unless CORS checks are successful),
which helps alleviate this concern.
To mitigate this potential security vulnerability, CSRF protection is enabled by default, requiring a
`GraphQL-Require-Preflight` header to be sent with form data requests, which will trigger a CORS preflight
request. In addition, form data requests are disabled by default, as they are not recommended for typical
use.

GraphQL.NET Server supports two formats of `application/x-www-form-urlencoded` or `multipart/form-data` requests:
To enable form data for POST request, set the `ReadFormOnPost` setting to `true`. GraphQL.NET Server supports
two formats of `application/x-www-form-urlencoded` or `multipart/form-data` requests:

1. The following keys are read from the form data and used to populate the GraphQL request:
- `query`: The GraphQL query string.
Expand Down Expand Up @@ -1032,8 +1050,9 @@ Please see the 'Upload' sample for a demonstration of this technique, which also
demonstrates the use of the `MediaTypeAttribute` to restrict the allowable media
types that will be accepted. Note that using the `FormFileGraphType` scalar requires
that the uploaded files be sent only via the `multipart/form-data` content type as
attached files. If you also wish to allow clients to send files as base-64 encoded
strings, you can write a custom scalar better suited to your needs.
attached files, with the `ReadFormOnPost` option enabled. If you also wish to allow
clients to send files as base-64 encoded strings, you can write a custom scalar
better suited to your needs.

### Native AOT support

Expand Down
2 changes: 2 additions & 0 deletions docs/migration/migration8.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@
all form-POST requests. To disable this feature, set the `CsrfProtectionEnabled` property on the
`GraphQLMiddlewareOptions` class to `false`. You may also configure the headers list by modifying
the `CsrfProtectionHeaders` property on the same class. See the readme for more details.
- Form POST requests are disabled by default; to enable them, set the `ReadFormOnPost` setting
to `true`.

## Other changes

Expand Down
5 changes: 4 additions & 1 deletion samples/Samples.Complex/Startup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,10 @@ public void Configure(IApplicationBuilder app)

app.UseWebSockets();

app.UseGraphQL<GraphQLHttpMiddlewareWithLogs<ChatSchema>>("/graphql", new GraphQLHttpMiddlewareOptions());
app.UseGraphQL<GraphQLHttpMiddlewareWithLogs<ChatSchema>>("/graphql", new GraphQLHttpMiddlewareOptions()
{
ReadFormOnPost = true,
});

app.UseGraphQLPlayground(options: new PlaygroundOptions
{
Expand Down
5 changes: 4 additions & 1 deletion samples/Samples.Complex/StartupWithRouting.cs
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,10 @@ public void Configure(IApplicationBuilder app)

app.UseEndpoints(endpoints =>
{
endpoints.MapGraphQL<GraphQLHttpMiddlewareWithLogs<ChatSchema>>("/graphql", new GraphQLHttpMiddlewareOptions());
endpoints.MapGraphQL<GraphQLHttpMiddlewareWithLogs<ChatSchema>>("/graphql", new GraphQLHttpMiddlewareOptions()
{
ReadFormOnPost = true,
});

endpoints.MapGraphQLPlayground(options: new PlaygroundOptions
{
Expand Down
2 changes: 1 addition & 1 deletion samples/Samples.Upload/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@

var app = builder.Build();
app.UseDeveloperExceptionPage();
app.UseGraphQL();
app.UseGraphQL(configureMiddleware: opts => opts.ReadFormOnPost = true);
app.UseRouting();
app.MapRazorPages();

Expand Down
6 changes: 4 additions & 2 deletions src/Transports.AspNetCore/GraphQLHttpMiddlewareOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -66,9 +66,11 @@ public class GraphQLHttpMiddlewareOptions : IAuthorizationOptions
/// alongside the request does not initiate a pre-flight CORS request.
/// As a result, GraphQL.NET carries out the request and potentially modifies data,
/// even if the CORS policy forbids it, irrespective of the sender's ability to access
/// the response.
/// the response. With <see cref="CsrfProtectionEnabled"/> enabled, these requests
/// are blocked the request contains a non-empty header from the
/// <see cref="CsrfProtectionHeaders"/> list, providing a measure of protection.
/// </remarks>
public bool ReadFormOnPost { get; set; } = true; // TODO: change to false for v9
public bool ReadFormOnPost { get; set; }

/// <summary>
/// Enables cross-site request forgery (CSRF) protection for both GET and POST requests.
Expand Down
6 changes: 6 additions & 0 deletions tests/Transports.AspNetCore.Tests/Middleware/PostTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,7 @@ public async Task AltCharset_Invalid()
[InlineData(false, false)]
public async Task FormMultipart_Legacy(bool requireCsrf, bool supplyCsrf)
{
_options2.ReadFormOnPost = true;
if (!requireCsrf)
_options2.CsrfProtectionEnabled = false;
var client = _server.CreateClient();
Expand Down Expand Up @@ -187,6 +188,7 @@ public async Task FormMultipart_Legacy(bool requireCsrf, bool supplyCsrf)
[InlineData(false, false)]
public async Task FormMultipart_Upload(bool requireCsrf, bool supplyCsrf)
{
_options2.ReadFormOnPost = true;
if (!requireCsrf)
_options2.CsrfProtectionEnabled = false;
var client = _server.CreateClient();
Expand Down Expand Up @@ -357,6 +359,7 @@ public async Task FormMultipart_Upload(bool requireCsrf, bool supplyCsrf)
[Theory]
public async Task FormMultipart_Upload_Matrix(int testIndex, string? operations, string? map, bool file0, bool file1, int expectedStatusCode, string expectedResponse)
{
_options2.ReadFormOnPost = true;
_ = testIndex;
operations ??= "{\"query\":\"query($arg:FormFile){file(file:$arg){content}}\",\"variables\":{\"arg\":null}}";
var client = _server.CreateClient();
Expand Down Expand Up @@ -385,6 +388,7 @@ public async Task FormMultipart_Upload_Validation(int? maxFileCount, int? maxFil
var client = _server.CreateClient();
_options2.MaximumFileCount = maxFileCount;
_options2.MaximumFileSize = maxFileLength;
_options2.ReadFormOnPost = true;
using var content = new MultipartFormDataContent
{
{ new StringContent(operations, Encoding.UTF8, "application/json"), "operations" },
Expand All @@ -405,6 +409,7 @@ public async Task FormMultipart_Upload_Validation(int? maxFileCount, int? maxFil
[InlineData(false, false)]
public async Task FormUrlEncoded(bool requireCsrf, bool supplyCsrf)
{
_options2.ReadFormOnPost = true;
if (!requireCsrf)
_options2.CsrfProtectionEnabled = false;
var client = _server.CreateClient();
Expand All @@ -430,6 +435,7 @@ public async Task FormUrlEncoded(bool requireCsrf, bool supplyCsrf)
public async Task FormUrlEncoded_DeserializationError(bool badRequest)
{
_options.ValidationErrorsReturnBadRequest = badRequest;
_options2.ReadFormOnPost = true;
var client = _server.CreateClient();
var content = new FormUrlEncodedContent(new[] {
new KeyValuePair<string?, string?>("query", "{ext}"),
Expand Down