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
18 changes: 18 additions & 0 deletions core/graphql.md
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,24 @@ Route::post('/docs/graphiql', GraphiQlAction::class)
->name('graphiql');
```

### Serving GraphiQL Under a Content Security Policy

> [!NOTE] This feature is only available with Symfony. Laravel's GraphiQL page is served from its
> own Blade template and controller, which this feature doesn't cover yet. You're welcome to
> contribute the Laravel implementation [on GitHub](https://github.com/api-platform/core).

GraphiQL renders its data and loads its scripts through several inline and external `<script>` tags.
A strict
[Content-Security-Policy](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy)
that forbids `unsafe-inline` blocks every one of them, unless each tag carries the `nonce` your
policy expects. API Platform doesn't generate or manage that nonce itself: it looks it up and, when
found, adds the matching `nonce` attribute to every `<script>` tag it renders, exactly as it does
for Swagger UI; see
[Serving Swagger UI Under a Content Security Policy](openapi.md#serving-swagger-ui-under-a-content-security-policy)
for the full precedence rules (the `_csp_nonce` request attribute, falling back to a Twig
`csp_nonce()` function such as the one provided by
[NelmioSecurityBundle](https://github.com/nelmio/NelmioSecurityBundle)).

## GraphQL Playground

Another IDE is by default included in API Platform: GraphQL Playground.
Expand Down
39 changes: 39 additions & 0 deletions core/openapi.md
Original file line number Diff line number Diff line change
Expand Up @@ -1042,6 +1042,45 @@ return [
];
```

## Serving Swagger UI Under a Content Security Policy

> [!NOTE] This feature is only available with Symfony. You're welcome to contribute the Laravel
> implementation [on GitHub](https://github.com/api-platform/core).

Swagger UI renders its data and loads its scripts through several inline and external `<script>`
tags. A strict
[Content-Security-Policy](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy)
that forbids `unsafe-inline` blocks every one of them, unless each tag carries the `nonce` your
policy expects. API Platform doesn't generate or manage that nonce itself: it looks it up and, when
found, adds the matching `nonce` attribute to every `<script>` tag it renders.

The nonce is resolved in this order:

1. The `_csp_nonce` request attribute. Set it yourself (typically from a request listener that also
builds your `Content-Security-Policy` header), and API Platform reuses it as-is.
2. A Twig `csp_nonce()` function, if one is registered — the convention used by
[NelmioSecurityBundle](https://github.com/nelmio/NelmioSecurityBundle)'s CSP extension. It's
called with `'script'` as its argument.

If neither is available, no `nonce` attribute is added and the scripts are left as plain
inline/external tags, so make sure your policy allows them if you don't configure either mechanism.

For example, with NelmioSecurityBundle configured to generate a per-request nonce and to add it to
your CSP header, Swagger UI's scripts automatically pick up the same value with no extra
configuration:

```yaml
# config/packages/nelmio_security.yaml
nelmio_security:
csp:
enforce:
script-src:
- self
```

> [!NOTE] GraphiQL is served under the same mechanism; see
> [Serving GraphiQL Under a Content Security Policy](graphql.md#serving-graphiql-under-a-content-security-policy).

## Info Object

The [info object](https://swagger.io/specification/#info-object) provides metadata about the API
Expand Down
84 changes: 84 additions & 0 deletions core/operations.md
Original file line number Diff line number Diff line change
Expand Up @@ -716,6 +716,90 @@ final class ImportBookProcessor implements ProcessorInterface
> the resource metadata and in the generated OpenAPI/Hydra documentation, while a request attribute
> set at runtime is not.

## Setting the Route Matching Priority

Symfony's router matches an incoming URL against every registered route, in order, and stops at the
first one that fits. When a resource combines a static, custom URI template with the default,
parameterized one, the static route must be tried first, or it never gets a chance to match. Take a
`Book` resource that has a default `Get` item operation on `/books/{id}` and a custom
`GetCollection` operation exposing the "featured" books at `/books/featured`: because
`/books/featured` also fits the `/books/{id}` pattern (`id` becomes the string `featured`),
whichever route is registered first wins. If the item operation happens to load before the featured
one, requests to `/books/featured` are routed to `Get` with `id: 'featured'` instead of reaching the
intended operation.

The `routePriority` option is available on the standard CRUD HTTP operations: `Get`,
`GetCollection`, `Post`, `Put`, `Patch`, and `Delete`. It tells the Symfony router which route to
try first: **the higher the value, the earlier the route is checked**, regardless of the order in
which operations are declared. It accepts any integer (negative values are allowed to deprioritize a
route) and defaults to `0` when omitted.

<code-selector>

```php
<?php
// api/src/Entity/Book.php
namespace App\Entity;

use ApiPlatform\Metadata\ApiResource;
use ApiPlatform\Metadata\Get;
use ApiPlatform\Metadata\GetCollection;

#[ApiResource(operations: [
new Get(uriTemplate: '/books/{id}'),
new GetCollection(
uriTemplate: '/books/featured',
routePriority: 1,
),
])]
class Book
{
//...
}
```

```yaml
# api/config/api_platform/resources.yaml
resources:
App\Entity\Book:
operations:
ApiPlatform\Metadata\Get:
uriTemplate: "/books/{id}"
ApiPlatform\Metadata\GetCollection:
uriTemplate: "/books/featured"
routePriority: 1
```

```xml
<?xml version="1.0" encoding="UTF-8" ?>
<!-- api/config/api_platform/resources.xml -->

<resources xmlns="https://api-platform.com/schema/metadata/resources-3.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://api-platform.com/schema/metadata/resources-3.0
https://api-platform.com/schema/metadata/resources-3.0.xsd">
<resource class="App\Entity\Book">
<operations>
<operation class="ApiPlatform\Metadata\Get" uriTemplate="/books/{id}" />
<operation class="ApiPlatform\Metadata\GetCollection" uriTemplate="/books/featured" routePriority="1" />
</operations>
</resource>
</resources>
```

</code-selector>

With `routePriority: 1` set on the `/books/featured` operation, its route is now checked before
`/books/{id}`, so `GET /books/featured` reaches the intended `GetCollection` operation, and every
other `/books/{id}` request still falls through to `Get`.

> [!NOTE] Do not confuse `routePriority` with the pre-existing `priority` option: `priority` only
> orders operations within a resource's own operation list (used, for instance, to determine which
> operation generates a resource's IRI) and sorts ascending — a lower value comes first.
> `routePriority` controls Symfony route matching order and sorts descending — a higher value is
> matched first. The two options are unrelated and are intentionally kept separate to avoid this
> confusion.

## Prefixing All Routes of All Operations

Sometimes it's also useful to put a whole resource into its own "namespace" regarding the URI. Let's
Expand Down
Loading