ЁЯМР рдзрдбрд╛ 09 тАФ CORS: browser рдЖрдзреА рд╡рд┐рдЪрд╛рд░рддреЛ
ЁЯУН рддреБрдореНрд╣реА рдЗрдереЗ рдЖрд╣рд╛рдд: 12 рдкреИрдХреА рдзрдбрд╛ 09 ┬╖ рдорд╛рдЧреЗ: lesson-08-caching ┬╖ рдкреБрдвреЗ: lesson-10-custom-domains
ЁЯУж рдпрд╛ рдмреНрд░рдБрдЪрдордзреНрдпреЗ рдХрд╛рдп рдЖрд╣реЗ
рдзрдбреЗ 01тАУ08, рдЖрдгрд┐ CORS: browser рдЖрдзреА рдкрд╛рдард╡рддреЛ рддреА preflight OPTIONS request,
office рдЙрддреНрддрд░рд╛рдд рджреЗрддреЗ рддреЗ Access-Control-Allow-* headers, рдЖрдгрд┐ CORS рддреБрдордЪреНрдпрд╛ API рдЪреЗ рдирд╛рд╣реА рддрд░
browsers рдЪреЗ рд╕рдВрд░рдХреНрд╖рдг рдХрд╛ рдХрд░рддреЗ. apigw/demo.py рдордзрд▓реЗ cors() рд╢рд╛рд│реЗрдЪреНрдпрд╛ рд╕реНрд╡рддрдГрдЪреНрдпрд╛
web site рдХрдбреВрди рдЖрдгрд┐ рдПрдХрд╛ рдЕрдиреЛрд│рдЦреА site рдХрдбреВрди рдЖрд▓реЗрд▓реНрдпрд╛ preflight рд▓рд╛ рдЙрддреНрддрд░ рджреЗрддреЗ.
ЁЯзТ 5 рд╡рд░реНрд╖рд╛рдВрдЪреНрдпрд╛ рдореБрд▓рд╛рд▓рд╛ рд╕рдордЬрд╛рд╡рд▓реНрдпрд╛рд╕рд╛рд░рдЦреЗ
рдкрд╛рд▓рдХрд╛рдВрдЪрд╛ browser ЁЯМР рд╣рд╛ рдЕрддрд┐рд╢рдп рдХрд╛рд│рдЬреАрдкреВрд░реНрд╡рдХ рд╡рд╛рдЧрдгрд╛рд░рд╛ рдорджрддрдиреАрд╕ рдЖрд╣реЗ. рдкрд╛рд▓рдХ рд╢рд╛рд│реЗрдЪреЗ
web page, https://school.example, рд╡рд╛рдЪрдд рдЖрд╣реЗрдд. рддреЗ page browser рд▓рд╛ рд╡рд┐рдЪрд╛рд░рддреЗ: "рдХреГрдкрдпрд╛ рдРрд╢реНрд╡рд░реНрдпрд╛рдЪреЗ
рдЧреБрдг https://api.school.example рд╡рд░реВрди рдЖрдгреВрди рджреЗ". рддреЛ рджреБрд╕рд░рд╛ рдкрддреНрддрд╛ рдЖрд╣реЗ.
рдореНрд╣рдгреВрди browser рдЖрдзреА office рдЪреНрдпрд╛ рджрд╛рд░рд╛рд╡рд░ рдЯрдХрдЯрдХ рдХрд░рддреЛ рдЖрдгрд┐ рд╡рд┐рдЪрд╛рд░рддреЛ: "school.example рд╡рд░рдЪреНрдпрд╛
page рд▓рд╛ GET рдкрд╛рдард╡рд╛рдпрдЪрд╛ рдЖрд╣реЗ. рдЪрд╛рд▓реЗрд▓ рдХрд╛?" рд╣реА рдЯрдХрдЯрдХ рдореНрд╣рдгрдЬреЗ
preflight (OPTIONS).
Clerk рдЙрддреНрддрд░ рджреЗрддреЛ: "рд╣реЛ тАФ school.example рд╕рд╛рдареА, рдпрд╛ methods, рд╣реЗ headers, рдЖрдгрд┐ рд╣реЗ рдЙрддреНрддрд░
рддреВ 10 рдорд┐рдирд┐рдЯреЗ рд▓рдХреНрд╖рд╛рдд рдареЗрд╡реВ рд╢рдХрддреЛрд╕." рдордЧ browser рдЦрд░реА request рдкрд╛рдард╡рддреЛ.
evil.example рд╡рд░рдЪреЗ рдПрдХ page рддреЗрдЪ рд╡рд┐рдЪрд╛рд░рддреЗ. Clerk рдирд╛рд╣реА рдореНрд╣рдгрддреЛ. рдордЧ browser
рддреНрдпрд╛ page рд▓рд╛ рдЙрддреНрддрд░ рджреНрдпрд╛рдпрд▓рд╛ рдирдХрд╛рд░ рджреЗрддреЛ.
рд▓рдХреНрд╖ рджреНрдпрд╛: рдЖрдЬреНрдЮрд╛ рдкрд╛рд│рддреЛ рддреЛ browser. curl рд╡рд╛рдкрд░рдгрд╛рд░реА рд╡реНрдпрдХреНрддреА рдХрдзреАрдЪ рдЖрдзреА рд╡рд┐рдЪрд╛рд░рдд рдирд╛рд╣реА тАФ clerk рдЪреЗ
"рдирд╛рд╣реА" рддрд┐рд▓рд╛ рдерд╛рдВрдмрд╡рдд рдирд╛рд╣реА.
ЁЯЧ║я╕П рдЖрдХреГрддреА
sequenceDiagram
participant P as ЁЯУД page on school.example
participant B as ЁЯМР browser
participant O as ЁЯЫОя╕П front office
P->>B: fetch api/students/7
B->>O: OPTIONS /students/7 ┬╖ Origin: https://school.example
O-->>B: 204 ┬╖ Allow-Origin: https://school.example ┬╖ Max-Age: 600
B->>O: GET /students/7 ┬╖ Origin: https://school.example
O-->>B: 200 ┬╖ Access-Control-Allow-Origin: https://school.example
B-->>P: the answer
Note over B,O: Origin https://evil.example тЖТ 403, and the browser blocks the page
ЁЯЧ║я╕П рдХрд╛рдврд▓реЗрд▓реА рдЖрдХреГрддреА + рдПрдХ lab: https://school-edh.pages.dev/apigateway/lesson-diagrams.html#l09
тЭУ рдХрд╛рдп
- Origin тАФ page рдЪреЗ scheme + host + port:
https://school.example.http://school.exampleрд╣рд╛ рд╡реЗрдЧрд│рд╛ origin рдЖрд╣реЗ. - Same-origin policy тАФ browser рдЪрд╛ рдирд┐рдпрдо: рджреБрд╕рд▒реНрдпрд╛ origin рдиреЗ рдкрд░рд╡рд╛рдирдЧреА рджрд┐рд▓реНрдпрд╛рд╢рд┐рд╡рд╛рдп page рддреНрдпрд╛ origin рдХрдбрдЪреА рдЙрддреНрддрд░реЗ рд╡рд╛рдЪреВ рд╢рдХрдд рдирд╛рд╣реА. CORS (Cross-Origin Resource Sharing) рд╣реА рдкрд░рд╡рд╛рдирдЧреА рджреЗрдгреНрдпрд╛рдЪреА рдкрджреНрдзрдд рдЖрд╣реЗ.
- Preflight тАФ "non-simple" requests рд╕рд╛рдареА (рдЙрджрд╛. JSON bodies,
Authorizationheader,PUT/DELETE), browser рдЖрдзреАOrigin,Access-Control-Request-MethodрдЖрдгрд┐Access-Control-Request-Headersрд╕рд╣OPTIONSрдкрд╛рдард╡рддреЛ. - рдЙрддреНрддрд░рд╛рдЪреЗ headers тАФ
Access-Control-Allow-Origin(рдПрдХ origin, рдХрд┐рдВрд╡рд╛*),Access-Control-Allow-Methods,Access-Control-Allow-Headers,Access-Control-Max-Age(browser рдЙрддреНрддрд░ рдХрд┐рддреА рдХрд╛рд│ рд▓рдХреНрд╖рд╛рдд рдареЗрд╡реВ рд╢рдХрддреЛ), рдЖрдгрд┐Access-Control-Allow-Credentials(cookies; рдордЧ origin*рдЕрд╕реВ рд╢рдХрдд рдирд╛рд╣реА). - рдЦрд▒реНрдпрд╛ response рд▓рд╛рд╣реА header рд╣рд╡рд╛ тАФ
Access-Control-Allow-OriginрдкреНрд░рддреНрдпрдХреНрд╖GET/POSTрдЙрддреНрддрд░рд╛рд╡рд░рд╣реА рдЕрд╕рд╛рдпрд▓рд╛ рд╣рд╡рд╛, error рдЙрддреНрддрд░рд╛рдВрд╕рд╣, рдирд╛рд╣реАрддрд░ page рддреЗ рд╡рд╛рдЪреВ рд╢рдХрдд рдирд╛рд╣реА. - API Gateway рдордзреНрдпреЗ тАФ HTTP APIs: рдПрдХрдЪ
corsConfiguration, рдЖрдгрд┐ office preflights рдирд╛ рд╕реНрд╡рддрдГрдЪ рдЙрддреНрддрд░ рджреЗрддреЗ. REST APIs: рдПрдХOPTIONSmethod (рд╕рд╣рд╕рд╛ mock integration) headers рдкрд░рдд рджреЗрддреЗ; proxy integration рдЕрд╕реЗрд▓ рддрд░ рддреБрдордЪреНрдпрд╛ back end рдиреЗ рд╕реНрд╡рддрдГрдЪреНрдпрд╛ responses рдордзреНрдпреЗAccess-Control-Allow-OriginрдШрд╛рд▓рд╛рдпрд▓рд╛ рд╣рд╡рд╛; headers gateway responses (DEFAULT_4XX,DEFAULT_5XX) рдордзреНрдпреЗрд╣реА рдШрд╛рд▓рд╛, рдореНрд╣рдгрдЬреЗ browser pages errors рд╡рд╛рдЪреВ рд╢рдХрддреАрд▓.
ЁЯдФ рдХрд╛
рдХрд╛рд░рдг рдЬрд╡рд│рдЬрд╡рд│ рдкреНрд░рддреНрдпреЗрдХ web app рджреБрд╕рд▒реНрдпрд╛ origin рд╡рд░рдЪрд╛ API call рдХрд░рддреЗ, рдЖрдгрд┐ header рдирд╕рдгреЗ рд╣рд╛ рд╕рдЧрд│реНрдпрд╛рдд рд╕рд╛рдорд╛рдиреНрдп "curl рдордзреНрдпреЗ рдЪрд╛рд▓рддреЗ рдкрдг browser рдордзреНрдпреЗ рдирд╛рд╣реА" bug рдЖрд╣реЗ. рдЖрдгрд┐ рдХрд╛рд░рдг рдЕрдиреЗрдХрд╛рдВрдирд╛ рд╡рд╛рдЯрддреЗ рдХреА CORS рд╣реА API рд╕рд╛рдареА security рднрд┐рдВрдд рдЖрд╣реЗ. рддрд╕реЗ рдирд╛рд╣реА: рддреА user рдЪреНрдпрд╛ browser рдЪреЗ рдЕрд╢рд╛ pages рдкрд╛рд╕реВрди рд╕рдВрд░рдХреНрд╖рдг рдХрд░рддреЗ рдЬреА user рдЪреНрдпрд╛ cookies рд╡рд╛рдкрд░реВрди data рд╡рд╛рдЪрд╛рдпрдЪрд╛ рдкреНрд░рдпрддреНрди рдХрд░рддрд╛рдд. рддреБрдордЪреНрдпрд╛ API рд▓рд╛ auth рд▓рд╛рдЧрддреЛрдЪ (рдзрдбрд╛ 06).
ЁЯФз рдХрд╕реЗ (рдпрд╛ repo рдордзреНрдпреЗ)
apigw/gateway.py рдордзрд▓рд╛ Gateway._handle() рдкреНрд░рддреНрдпреЗрдХ
OPTIONS рд▓рд╛ route matching рдЪреНрдпрд╛ рдЖрдзреАрдЪ рд╕реНрд╡рддрдГ рдЙрддреНрддрд░ рджреЗрддреЛ (HTTP API рдЪреНрдпрд╛ CORS setting рдкреНрд░рдорд╛рдгреЗ):
cors_origins рдордзрд▓реНрдпрд╛ origin рд▓рд╛ рдЪрд╛рд░ Access-Control-* headers рд╕рд╣ 204 рдорд┐рд│рддреЛ; рдЗрддрд░ рдХреЛрдгрддреНрдпрд╛рд╣реА origin рд▓рд╛
403 CORS origin not allowed рдорд┐рд│рддреЛ. Request рдЪрд╛ Origin рдкрд░рд╡рд╛рдирдЧреА рдЕрд╕рд▓реЗрд▓рд╛ рдЕрд╕реЗрд▓ рддреЗрд╡реНрд╣рд╛ Gateway.handle()
рдЦрд▒реНрдпрд╛ рдЙрддреНрддрд░рд╛рддрд╣реА Access-Control-Allow-Origin рдШрд╛рд▓рддреЛ.
office() рдиреЗрдордХрд╛ рдПрдХрдЪ origin рдкрд░рд╡рд╛рдирдЧреА рджреЗрддреЛ: https://school.example.
ЁЯзк рдХрд░реВрди рдкрд╛рд╣рд╛
python3 apigw/demo.py cors
python3 - <<'EOF'
import sys; sys.path.insert(0, "apigw"); from demo import office
gw, _ = office()
for origin in ("https://school.example", "http://school.example", "https://school.example.evil.com"):
st, hd, _ = gw.handle("OPTIONS", "/marks", {"Origin": origin, "Access-Control-Request-Method": "POST"})
print(st, origin, hd.get("Access-Control-Allow-Origin"))
st, hd, out = gw.handle("GET", "/students/9", {"Origin": "https://evil.example"})
print("GET from evil.example:", st, out, "┬╖ Allow-Origin header:", hd.get("Access-Control-Allow-Origin"))
EOF
рдЪрд╛рд▓реВ office рд╡рд░ рддреБрдореНрд╣реАрдЪ browser рдмрдирд╛:
python3 apigw/demo.py serve &
sleep 1 # give the office a second to open
curl -i -X OPTIONS -H "Origin: https://school.example" -H "Access-Control-Request-Method: GET" localhost:8080/students/7
curl -i -X OPTIONS -H "Origin: https://evil.example" localhost:8080/students/7
curl -i -H "Origin: https://evil.example" localhost:8080/students/9
kill %1
тЬЕ рддрдкрд╛рд╕рд╛ тАФ рддреБрдореНрд╣рд╛рд▓рд╛ рдХрд╛рдп рджрд┐рд╕рд╛рдпрд▓рд╛ рд╣рд╡реЗ
cors рдЫрд╛рдкрддреЗ
OPTIONS /students/7 тЖТ 204 Access-Control-Allow-Origin: https://school.example Access-Control-Allow-Methods: GET,POST,PUT,DELETE Access-Control-Allow-Headers: authorization,content-type,x-api-key Access-Control-Max-Age: 600,
рдордЧ https://evil.example рд╕рд╛рдареА OPTIONS /students/7 тЖТ 403 {'message': 'CORS origin not allowed'},
рдордЧ
X-Cache: Miss Access-Control-Allow-Origin: https://school.example рд╕рд╣ рдЦрд░рд╛ GET.
рддреБрдордЪрд╛ snippet рдЫрд╛рдкрддреЛ 204 https://school.example https://school.example,
403 http://school.example None (рд╡реЗрдЧрд│рд╛ scheme рдореНрд╣рдгрдЬреЗ рд╡реЗрдЧрд│рд╛ origin) рдЖрдгрд┐
403 https://school.example.evil.com None (рд╕рд╛рд░рдЦреЗ рджрд┐рд╕рдгрд╛рд░реЗ рдирд╛рд╡ рдореНрд╣рдгрдЬреЗ рддреЛрдЪ origin рдирд╛рд╣реА).
рд╢реЗрд╡рдЯрдЪреА рдУрд│ рд╕рдЧрд│реНрдпрд╛рдд рдорд╣рддреНрддреНрд╡рд╛рдЪреА рдЖрд╣реЗ:
GET from evil.example: 200 {'id': '9', 'name': 'Katrina', 'class': '9A'} ┬╖ Allow-Origin header: None
тАФ office рдиреЗ рдЙрддреНрддрд░ рджрд┐рд▓реЗрдЪ; рдлрдХреНрдд browser рддреЗ рдЙрддреНрддрд░ page рдкрд╛рд╕реВрди рд▓рдкрд╡реЗрд▓.
рдЪрд╛рд▓реВ office рд╡рд░: рдЪрд╛рд░ Access-Control-* headers рд╕рд╣ HTTP/1.0 204 No Content;
{"message": "CORS origin not allowed"} рд╕рд╣ HTTP/1.0 403 Forbidden; рдЖрдгрд┐ evil.example рдХрдбреВрди рдЖрд▓реЗрд▓реНрдпрд╛
рд╕рд╛рдзреНрдпрд╛ GET рд╕рд╛рдареА data рд╕рд╣ HTTP/1.0 200 OK, рдкрдг
Access-Control-Allow-Origin header рдирд╛рд╣реА.
ЁЯПБ рддреБрдореНрд╣реА рдЖрддреНрддрд╛рдЪ рдХрд╛рдп рд╕рд┐рджреНрдз рдХреЗрд▓реЗ
рддреБрдореНрд╣реА browser рдкреНрд░рдорд╛рдгреЗ preflight рд╡рд╛рдЪреВ рд╢рдХрддрд╛, рдЖрдгрд┐ рддреБрдореНрд╣реА рд╕реНрд╡рддрдГрдЪреНрдпрд╛ рдбреЛрд│реНрдпрд╛рдВрдиреА рдкрд╛рд╣рд┐рд▓реЗ рдХреА CORS
curl рд▓рд╛ рдерд╛рдВрдмрд╡рдд рдирд╛рд╣реА тАФ рддреЛ browsers рдкрд╛рд│рддрд╛рдд рддреЛ рдирд┐рдпрдо рдЖрд╣реЗ, API рд╡рд░рдЪреЗ рдХреБрд▓реВрдк рдирд╛рд╣реА.
тЪая╕П рдиреЗрд╣рдореАрдЪреНрдпрд╛ рдЪреБрдХрд╛
- credentials (cookies) рд╕реЛрдмрдд
Access-Control-Allow-Origin: *тАФ browsers рддреЗ рдирд╛рдХрд╛рд░рддрд╛рдд - CORS headers
OPTIONSрдЙрддреНрддрд░рд╛рд╡рд░ рдЖрд╣реЗрдд рдкрдг рдЦрд▒реНрдпрд╛ response рд╡рд░ рдирд╛рд╣реАрдд (рдХрд┐рдВрд╡рд╛4XX/5XXerrors рд╡рд░ рдирд╛рд╣реАрдд) - "school.example рдиреЗ рд╕рдВрдкрддреЛ" рдЕрд╕реЗ origins рдЬреБрд│рд╡рдгреЗ тАФ
evil-school.exampleрдкрд╛рд╕ рд╣реЛрддреЛ; рдиреЗрдордХреЗ origins рдпрд╛рджреАрдд рд▓рд┐рд╣рд╛ - рдЦрд▒реНрдпрд╛ auth рд╕рдорд╕реНрдпреЗрд╡рд░ CORS рд░реБрдВрдж рдХрд░реВрди "рдЙрдкрд╛рдп" рдХрд░рдгреЗ тАФ CORS рдореНрд╣рдгрдЬреЗ auth рдирд╛рд╣реА
Allow-HeadersрдордзреНрдпреЗauthorizationрд╡рд┐рд╕рд░рдгреЗ, рддреНрдпрд╛рдореБрд│реЗ token рдЕрд╕рд▓реЗрд▓реА рдкреНрд░рддреНрдпреЗрдХ request preflight рдордзреНрдпреЗрдЪ рдЕрдкрдпрд╢реА рдард░рддреЗ
ЁЯПн рдкреНрд░рддреНрдпрдХреНрд╖ рд╡рд╛рдкрд░рд╛рдд
рдЦрд▒реНрдпрд╛ account рд╡рд░ тАФ HTTP API рд╡рд░ CORS рдореНрд╣рдгрдЬреЗ рдПрдХрдЪ setting:
aws apigatewayv2 update-api --api-id a1b2c3 --cors-configuration \
AllowOrigins=https://school.example,AllowMethods=GET,POST,AllowHeaders=authorization,content-type,MaxAge=600
REST API рд╡рд░, browser pages рдирд╛ office рдЪреЗ рд╕реНрд╡рддрдГрдЪреЗ errors рд╕реБрджреНрдзрд╛ рд╡рд╛рдЪреВ рджреНрдпрд╛:
cat > cors-4xx.json <<'EOF'
{ "gatewayresponse.header.Access-Control-Allow-Origin": "'https://school.example'" }
EOF
aws apigateway put-gateway-response --rest-api-id abc123 --response-type DEFAULT_4XX \
--response-parameters file://cors-4xx.json
(рдЖрддрд▓реЗ single quotes рдЖрд╡рд╢реНрдпрдХ рдЖрд╣реЗрдд: рддреЗ value рдард░рд▓реЗрд▓реА (fixed) рдЖрд╣реЗ, variable рдирд╛рд╣реА рд╣реЗ рджрд╛рдЦрд╡рддрд╛рдд.)
рдЖрдгрд┐ Lambda proxy back end рд╕реНрд╡рддрдГрдЪреНрдпрд╛ рдЙрддреНрддрд░рд╛рдВрдордзреНрдпреЗ header рдШрд╛рд▓рддреЛ:
return {"statusCode": 200,
"headers": {"Access-Control-Allow-Origin": "https://school.example"},
"body": json.dumps(student)}
ЁЯПн Production рдордзреНрдпреЗ рд╣реЗ рдХрд╛ рдорд╣рддреНрддреНрд╡рд╛рдЪреЗ рдЖрд╣реЗ: рдкреНрд░рддреНрдпреЗрдХ stage рд╕рд╛рдареА рдиреЗрдордХреЗ origins рдпрд╛рджреАрдд рд▓рд┐рд╣рд╛ (
devweb site рдЖрдгрд┐prodweb site рд╡реЗрдЧрд│реНрдпрд╛ рдЖрд╣реЗрдд), errors рд╡рд░рд╣реА headers рдШрд╛рд▓рд╛, рдЖрдгрд┐ CORS рд▓рд╛ рдХрдзреАрд╣реА authorizer рдЪрд╛ рдкрд░реНрдпрд╛рдп рдорд╛рдиреВ рдирдХрд╛.
тПня╕П рдкреБрдвреЗ
Browsers рдЦреВрд╢ рдЖрд╣реЗрдд. рдкрдг рдкрддреНрддрд╛ рдЕрдЬреВрдирд╣реА abc123.execute-api.ap-south-1.amazonaws.com рдЖрд╣реЗ.
Office рд▓рд╛ рдирд╛рд╡рдлрд▓рдХ рджреНрдпрд╛ тАФ custom domains рдЖрдгрд┐ TLS.
git checkout lesson-10-custom-domains