feat(19.3-02): per-group CORS policies through cors:<profile> with router-registered preflights
- surf: LoadCORSProfiles reads http.cors_profiles.<name> (http.cors keys without paths, validated the same way); factory cors:<profile> sets the profile's headers; an unknown profile fails boot naming plugin and route - BuildRouter registers OPTIONS <path> (owner surf, recovery only) for every path whose routes name cors:<profile>; two profiles on one path, an existing OPTIONS route or overlap with http.cors.paths fail boot - docs: CORS profiles section, middleware table and README rows
This commit is contained in:
@@ -98,6 +98,7 @@ The framework registers these names:
|
||||
|------|------|
|
||||
| `throttle:<bucket>` or `throttle:<max>,<minutes>` | Rate limiting; see [Rate limiting](rate-limiting.md). |
|
||||
| `body.limit:<bytes>` | Replaces the default request body limit for the route. |
|
||||
| `cors:<profile>` | CORS headers from `http.cors_profiles.<profile>`; see [CORS profiles](#cors-profiles). |
|
||||
| `locale.from-principal` | Switches the request locale to the signed-in user's preferred locale. |
|
||||
| `backend` | The admin guard, when the admin is enabled. |
|
||||
|
||||
@@ -246,3 +247,22 @@ cors:
|
||||
The browser sends the request with `credentials: "include"`; the answer carries `Access-Control-Allow-Origin: https://www.example.com`, `Access-Control-Allow-Credentials: true` and `Vary: Origin`.
|
||||
|
||||
surf answers every `OPTIONS` request on a CORS path itself, with 204, before any route runs, so a plugin never registers an `OPTIONS` route. A preflight gets the same headers Laravel's `HandleCors` sends: with `allowed_methods: ["*"]` the `Access-Control-Allow-Methods` value is the requested method, upper-cased, and with `allowed_headers: ["*"]` the `Access-Control-Allow-Headers` value is the requested header list.
|
||||
|
||||
## CORS profiles
|
||||
|
||||
`http.cors` applies one policy to a set of paths. A route group that needs its own policy, for example a credentialed one for a single-page app while the rest of the API stays public, names a profile with the `cors:<profile>` middleware. Profiles live under `http.cors_profiles.<name>` and take the `http.cors` keys except `paths`:
|
||||
|
||||
```yaml
|
||||
cors_profiles:
|
||||
spa:
|
||||
allowed_origins: ["https://www.example.com"]
|
||||
allowed_methods: ["GET", "POST"]
|
||||
allowed_headers: ["Content-Type"]
|
||||
supports_credentials: true
|
||||
```
|
||||
|
||||
This fragment belongs in `config/http.yaml`. A route group then lists `cors:spa` among its middleware names, next to its guard, exactly as it lists `throttle:60,1`.
|
||||
|
||||
A profile is checked like `http.cors`: credentials with a `*` origin, a pattern that does not compile and a PHP-delimited pattern fail the start-up, with errors naming `http.cors_profiles.<name>.<key>`. Profile names use lower-case letters, digits, underscore and dash, and a profile that sets `paths` fails too.
|
||||
|
||||
ServeMux answers an `OPTIONS` request on a path registered only for `GET` or `POST` with 405 before any route middleware runs, so the middleware alone would never see a browser's preflight. surf therefore registers one `OPTIONS <path>` route for every path whose routes name `cors:<profile>`, owned by `surf` in `route:list`. It answers the preflight from the profile, with the same headers as the global preflight, and runs no other middleware: a preflight carries no credentials, so no auth, throttle or body limit applies. The start-up fails when a route names an unknown profile, when two routes on one path name different profiles, when a plugin already declared an `OPTIONS` route on that path, and when the path also matches `http.cors.paths`, because the global wrapper answers every preflight on its paths before routing.
|
||||
|
||||
Reference in New Issue
Block a user