ЁЯПл The SchoolтА║ЁЯУМ UIтА║ЁЯУи рдзрдбрд╛ 11 тАФ Backends рд╢реА рдмреЛрд▓рдгреЗ: рдХрд╛рдЙрдВрдЯрд░рдЪреНрдпрд╛ рдЪрд┐рдареНрдареНрдпрд╛ рднрд░рдгреЗ
ЁЯЦ╝я╕П See the drawing + lab ЁЯПа Course home ЁЯМ┐ Branch on GitHub тЬПя╕П View source
ЁЯЦ╝я╕П рдЖрдХреГрддреА рдЖрдгрд┐ labThe drawing + lab рдкреВрд░реНрдг рдкрд╛рдирд╛рд╡рд░ рдЙрдШрдбрд╛ тЖЧOpen full page тЖЧ

ЁЯУи рдзрдбрд╛ 11 тАФ Backends рд╢реА рдмреЛрд▓рдгреЗ: рдХрд╛рдЙрдВрдЯрд░рдЪреНрдпрд╛ рдЪрд┐рдареНрдареНрдпрд╛ рднрд░рдгреЗ

ЁЯУН рддреБрдореНрд╣реА рдЗрдереЗ рдЖрд╣рд╛рдд: 12 рдкреИрдХреА рдзрдбрд╛ 11 ┬╖ рдорд╛рдЧреАрд▓: lesson-10-testing ┬╖ рдкреБрдвреАрд▓: lesson-12-build-deploy-observe


ЁЯУж рдпрд╛ рдмреНрд░рдБрдЪрдордзреНрдпреЗ рдХрд╛рдп рдЖрд╣реЗ

рдзрдбреЗ 01тАУ10, рдЖрдгрд┐ рддреНрдпрд╛рд╕реЛрдмрдд рдлрд▓рдХ рдЖрдгрд┐ рдХрд╛рдЙрдВрдЯрд░ рдпрд╛рдВрдЪреНрдпрд╛рддрд▓реА рд╕реАрдорд╛: CORS, browser рдордзрд▓реА credentials, рдкреНрд░рддреНрдпреЗрдХ request рдЪреЗ рддреАрди рдЪреЗрд╣рд░реЗ, рдЖрдгрд┐ рджреЛрди рд╕реБрд░рдХреНрд╖рд╛ рдирд┐рдпрдо тАФ рдлрд▓рдХрд╛рд╡рд░ рдХрдзреАрдЪ рд╡рд┐рд╢реНрд╡рд╛рд╕ рдареЗрд╡реВ рдирдХрд╛, рдордЬрдХреВрд░ рдХрдзреАрдЪ HTML рдореНрд╣рдгреВрди рдШрд╛рд▓реВ рдирдХрд╛.

ЁЯзТ 5 рд╡рд░реНрд╖рд╛рдВрдЪреНрдпрд╛ рдореБрд▓рд╛рд▓рд╛ рд╕рдордЬрд╛рд╡рд▓реНрдпрд╛рд╕рд╛рд░рдЦреЗ

рдлрд▓рдХ рдХрд╛рдЙрдВрдЯрд░рд╡рд░ рдЪрд┐рдареНрдареНрдпрд╛ рднрд░рддреЛ (API рд╢рд╛рд│рд╛). рдлрд▓рдХ рдПрдХрд╛ рдЕрдиреЛрд│рдЦреА рдорд╛рдгрд╕рд╛рдЪреНрдпрд╛ browser рдордзреНрдпреЗ рдЪрд╛рд▓рддреЛ, рдореНрд╣рдгреВрди рдЪрд╛рд░ рдЧреЛрд╖реНрдЯреА рд╡реЗрдЧрд│реНрдпрд╛ рдЕрд╕рддрд╛рдд:

  1. CORS ЁЯЪз тАФ рдХрд╛рдЙрдВрдЯрд░рдиреЗ рдкрд░рд╡рд╛рдирдЧреА рджрд┐рд▓реНрдпрд╛рд╢рд┐рд╡рд╛рдп (Access-Control-Allow-Origin) browser рдПрдХрд╛ origin рд╡рд░рдЪреНрдпрд╛ page рд▓рд╛ рджреБрд╕рд▒реНрдпрд╛ origin рдЪреЗ response рд╡рд╛рдЪреВ рджреЗрдд рдирд╛рд╣реА. рд╕рд╛рдзреНрдпрд╛ GET рдкрд▓реАрдХрдбрдЪреНрдпрд╛ рдХреЛрдгрддреНрдпрд╛рд╣реА рдЧреЛрд╖реНрдЯреАрд╕рд╛рдареА browser рдЖрдзреА рдкрд░рд╡рд╛рдирдЧреА рдорд╛рдЧрддреЛ (рдПрдХ OPTIONS preflight). рдЖрдкрд▓рд╛ рдЫреЛрдЯрд╛ рдХрд╛рдЙрдВрдЯрд░ рджреЛрдиреНрд╣реАрдВрдирд╛ рдЙрддреНрддрд░ рджреЗрддреЛ.
  2. Browser рдордзреНрдпреЗ рдХрд╛рд╣реАрдЪ рдЧреБрдкреНрдд рдирд╕рддреЗ ЁЯкк тАФ рддреБрдореНрд╣реА рдкрд╛рдард╡рд▓реЗрд▓рд╛ рдкреНрд░рддреНрдпреЗрдХ byte рд╡рд╛рдЪрддрд╛ рдпреЗрддреЛ. JavaScript рдордзреНрдпреЗ API keys рдирдХреЛрдд. Users рдирд╛ рдХрдореА рдХрд╛рд│ рдЯрд┐рдХрдгрд╛рд░реЗ tokens рдорд┐рд│рддрд╛рдд (рдХрд┐рдВрд╡рд╛ JS рд╡рд╛рдЪреВ рд╢рдХрдд рдирд╛рд╣реА рдЕрд╢реА httpOnly cookie); рджреАрд░реНрдШрдХрд╛рд│ рдЯрд┐рдХрдгрд╛рд▒реНрдпрд╛ machine keys servers рд╡рд░рдЪ рд░рд╛рд╣рддрд╛рдд.
  3. рдиреЗрд╣рдореА рддреАрди рдЪреЗрд╣рд░реЗ тП│ тАФ loading, рдпрд╢, error. рдлрдХреНрдд рдпрд╢ рджрд╛рдЦрд╡рдгрд╛рд░рд╛ рдлрд▓рдХ, рдХрд╛рдЙрдВрдЯрд░ рдордВрдж рдХрд┐рдВрд╡рд╛ рдмрдВрдж рдЕрд╕рд▓рд╛ рдХреА рдХреЛрд░реЗ рдкрд╛рди рджрд╛рдЦрд╡рддреЛ.
  4. рдлрд▓рдХрд╛рд╡рд░ рдХрдзреАрдЪ рд╡рд┐рд╢реНрд╡рд╛рд╕ рдареЗрд╡реВ рдирдХрд╛ ЁЯЫбя╕П тАФ рддреБрдордЪреЗ form validation рд╣реЗ users рд╕рд╛рдареА рдХреЗрд▓реЗрд▓реА рдореЗрд╣реЗрд░рдмрд╛рдиреА рдЖрд╣реЗ; рдХреЛрдгреАрд╣реА рдереЗрдЯ request рдкрд╛рдард╡реВ рд╢рдХрддреЛ. рдХрд╛рдЙрдВрдЯрд░ рдкреБрдиреНрд╣рд╛ validate рдХрд░рддреЛ рдЖрдгрд┐ рдЕрдбрдЪрдгреАрдВрд╕рд╣ 400 рдкрд░рдд рдХрд░рддреЛ (API рд╢рд╛рд│реЗрдЪрд╛ рдзрдбрд╛ 04). рдЖрдгрд┐ рдЙрд▓рдЯ рдмрд╛рдЬреВрдиреЗ: рдХрд╛рдЙрдВрдЯрд░рдЪрд╛ рдордЬрдХреВрд░ page рдордзреНрдпреЗ innerHTML рдиреЗ рдХрдзреАрдЪ рдШрд╛рд▓реВ рдирдХрд╛ тАФ <img src=x onerror=alert(1)> рдирд╛рд╡рд╛рдЪрд╛ рд╡рд┐рджреНрдпрд╛рд░реНрдереА code рдЪрд╛рд▓рд╡реЗрд▓. рдиреЗрд╣рдореА textContent.

ЁЯЧ║я╕П рдЖрдХреГрддреА

sequenceDiagram
    participant B as ЁЯУМ board (browser)
    participant C as ЁЯПв counter (API)
    B->>C: 1 OPTIONS /api/students (preflight тАФ non-simple request)
    C-->>B: 2 Access-Control-Allow-Origin/Methods/Headers
    B->>C: 3 POST /api/students {name, class}  тЖР board validated (kindness)
    C-->>B: 4 400 {"error":{"code":"validation_failed"}}  тЖР counter validated (truth)
    Note over B: draw the error face ┬╖ textContent, never innerHTML

тЭУ рдХрд╛рдп

ЁЯдФ рдХрд╛

рдХрд╛рд░рдг front-end рдордзрд▓реНрдпрд╛ рд╕рдЧрд│реНрдпрд╛рдд рд╕рд╛рдорд╛рдиреНрдп рджреЛрди рд╕реБрд░рдХреНрд╖рд╛ рдШрдЯрдирд╛ рдпрд╛рдЪ рд╕реАрдореЗрд╡рд░ рдШрдбрддрд╛рдд (bundle рдордзреНрдпреЗ рдкрд╛рдард╡рд▓реЗрд▓реА рдЙрдШрдб рдЭрд╛рд▓реЗрд▓реА key; user рдЪреНрдпрд╛ рдордЬрдХреБрд░рд╛рддреВрди XSS), рдЖрдгрд┐ рд╕рдЧрд│реНрдпрд╛рдд рд╕рд╛рдорд╛рдиреНрдп рд╡рд┐рд╢реНрд╡рд╛рд╕рд╛рд░реНрд╣рддреЗрдЪреА рддрдХреНрд░рд╛рд░рд╣реА рдЗрдереЗрдЪ рдЕрд╕рддреЗ ("рддреЗ рдлрдХреНрдд рдлрд┐рд░рдд рд░рд╛рд╣рддреЗ" тАФ timeout рдирд╛рд╣реА, error рдЪреЗрд╣рд░рд╛ рдирд╛рд╣реА). рдЪрд╛рд░рдкреИрдХреА рддреАрди рдирд┐рдпрдорд╛рдВрдЪреА рдХрд┐рдВрдордд рдкреНрд░рддреНрдпреЗрдХреА рдПрдХ рдУрд│ рдЖрд╣реЗ.

ЁЯФз рдХрд╕реЗ (рдпрд╛ repo рдордзреНрдпреЗ)

ui/app.js: load() рдЖрдгрд┐ enrol() рддреАрди рдЪреЗрд╣рд░реЗ рджрд╛рдЦрд╡рддрд╛рдд; StudentCard user рдиреЗ рджрд┐рд▓реЗрд▓реНрдпрд╛ рдкреНрд░рддреНрдпреЗрдХ value рд╕рд╛рдареА textContent рд╡рд╛рдкрд░рддреЛ. ui/mock_api.py CORS headers рд▓рд╛рд╡рддреЛ, OPTIONS рд▓рд╛ рдЙрддреНрддрд░ рджреЗрддреЛ, form server-side рд╡рд░ рдкреБрдиреНрд╣рд╛ validate рдХрд░рддреЛ, рдЖрдгрд┐ ?slow=1 рд╡ ?fail=1 рджреЗрддреЛ рдореНрд╣рдгрдЬреЗ рддреБрдореНрд╣реА рд╣рд╡рд╛ рддреЗрд╡реНрд╣рд╛ рдкреНрд░рддреНрдпреЗрдХ рдЪреЗрд╣рд░рд╛ рдкрд╛рд╣реВ рд╢рдХрддрд╛.

ЁЯзк рдХрд░реВрди рдкрд╛рд╣рд╛

python3 ui/mock_api.py
# 1) the three faces: edit API in app.js to '/api/students?slow=1' (loading), then '?fail=1' (error), then back
# 2) never trust the board тАФ bypass the form entirely:
curl -s -X POST http://127.0.0.1:8000/api/students -H 'Content-Type: application/json' -d '{"name":"X","class":"9Z"}'
#    тЖТ 400 validation_failed. The counter refused what the board never would have sent.
# 3) XSS, safely: enrol a student named  <b>bold</b>  тАФ the card shows the literal text (textContent).
#    Then temporarily use innerHTML in StudentCard and enrol  <img src=x onerror="alert('xss')">  тАФ see it fire. Undo it.
# 4) CORS: open the board from a different origin (python3 -m http.server 9001 in ui/) and watch the console тАФ
#    then notice mock_api.py's Access-Control-Allow-Origin header is what makes the fetch work at all.

тЬЕ рддрдкрд╛рд╕рд╛ тАФ рддреБрдореНрд╣рд╛рд▓рд╛ рдХрд╛рдп рджрд┐рд╕рд╛рдпрд▓рд╛ рд╣рд╡реЗ

Loading рдЖрдгрд┐ error рдЪреЗрд╣рд░реЗ рд╣рд╡реЗ рддреЗрд╡реНрд╣рд╛ рджрд┐рд╕рддрд╛рдд рдЖрдгрд┐ рдХрдзреАрдЪ рдХреЛрд░реЗ рдкрд╛рди рд╕реЛрдбрдд рдирд╛рд╣реАрдд. рдереЗрдЯ curl рд▓рд╛ 400 validation_failed рдорд┐рд│рддреЛ тАФ рдХрд╛рдЙрдВрдЯрд░ рдлрд▓рдХрд╛рд╡рд░ рд╡рд┐рд╢реНрд╡рд╛рд╕ рдареЗрд╡рдд рдирд╛рд╣реА рдпрд╛рдЪрд╛ рдкреБрд░рд╛рд╡рд╛. <b>bold</b> рд╡рд┐рджреНрдпрд╛рд░реНрдереА textContent рд╕рд╣ рдордЬрдХреВрд░ рдореНрд╣рдгреВрди рджрд┐рд╕рддреЛ рдЖрдгрд┐ innerHTML рд╕рд╣ markup рдореНрд╣рдгреВрди (рдХрд┐рдВрд╡рд╛ рддреНрдпрд╛рд╣реВрди рд╡рд╛рдИрдЯ).

ЁЯПБ рддреБрдореНрд╣реА рдЖрддреНрддрд╛рдЪ рдХрд╛рдп рд╕рд┐рджреНрдз рдХреЗрд▓реЗ

рдлрд▓рдХ request рдЪрд╛ рдкреНрд░рддреНрдпреЗрдХ рдирд┐рдХрд╛рд▓ рд╣рд╛рддрд╛рд│рддреЛ, рдЖрдгрд┐ рддреБрдореНрд╣реА рджреЛрдиреНрд╣реА рд╕реБрд░рдХреНрд╖рд╛ рдирд┐рдпрдо тАФ server-side validation рдЖрдгрд┐ escaping тАФ рджреЛрдиреНрд╣реА рдмрд╛рдЬреВрдВрдиреА рджрд╛рдЦрд╡рд▓реЗ.

тЪая╕П рдиреЗрд╣рдореАрдЪреНрдпрд╛ рдЪреБрдХрд╛

ЁЯПн рдкреНрд░рддреНрдпрдХреНрд╖ рд╡рд╛рдкрд░рд╛рдд рд╣реЗ рдХрд╛ рдорд╣рддреНрддреНрд╡рд╛рдЪреЗ: рдкреНрд░рддреНрдпреЗрдХ security checklist рд╡рд░рдЪреНрдпрд╛ рджреЛрди front-end рдЧреЛрд╖реНрдЯреА рдореНрд╣рдгрдЬреЗ XSS рдЖрдгрд┐ рдЙрдШрдб рдЭрд╛рд▓реЗрд▓реНрдпрд╛ keys, рдЖрдгрд┐ "page рдХрд╛рдпрдо рдлрд┐рд░рдд рд░рд╛рд╣рддреЗ" рд╣реЗ рд╕рдЧрд│реНрдпрд╛рдд рд╕рд╛рдорд╛рдиреНрдп support ticket рдЖрд╣реЗ. рдЪрд╛рд░ рдирд┐рдпрдо, рдЪрд╛рд░ рдУрд│реА, рдмрд╣реБрддреЗрдХ рдзреЛрдХрд╛.

тПня╕П рдкреБрдвреЗ

рд╢реЗрд╡рдЯрдЪрд╛ рдЯрдкреНрдкрд╛: build, deploy рдЖрдгрд┐ observe тАФ рд▓реЛрдХрд╛рдВрдирд╛ рджрд┐рд╕реЗрд▓ рдЕрд╢рд╛ рдард┐рдХрд╛рдгреА рдлрд▓рдХ рд▓рд╛рд╡рдгреЗ, рдЖрдгрд┐ рдХрд╛рдп рдШрдбрддреЗ рддреНрдпрд╛рд╡рд░ рд▓рдХреНрд╖ рдареЗрд╡рдгреЗ.

git checkout lesson-12-build-deploy-observe

ЁЯУи Lesson 11 тАФ Talking to backends: filling in the counter's slips

ЁЯУН You are here: Lesson 11 of 12 ┬╖ Previous: lesson-10-testing ┬╖ Next: lesson-12-build-deploy-observe


ЁЯУж What's in this branch

Lessons 01тАУ10, plus the boundary between board and counter: CORS, credentials in a browser, the three faces of every request, and the two security rules тАФ never trust the board, never inject text as HTML.

ЁЯзТ Explain like I'm 5

The board fills in slips at the counter (the API school). Four things are different because the board runs inside a stranger's browser:

  1. CORS ЁЯЪз тАФ the browser will not let a page at one origin read a response from another unless the counter says it may (Access-Control-Allow-Origin). For anything beyond a simple GET, the browser asks permission first (an OPTIONS preflight). Our little counter answers both.
  2. Nothing in the browser is secret ЁЯкк тАФ every byte you ship can be read. No API keys in JavaScript. Users get short-lived tokens (or an httpOnly cookie the JS cannot read); long-lived machine keys stay on servers.
  3. Three faces, always тП│ тАФ loading, success, error. A board that only draws success shows a blank page whenever the counter is slow or down.
  4. Never trust the board ЁЯЫбя╕П тАФ your form validation is a kindness to users; anyone can send a raw request. The counter validates again and returns 400 with problems (API school lesson 04). And the reverse: never put counter text into the page with innerHTML тАФ a student named <img src=x onerror=alert(1)> would run code. textContent, always.

ЁЯЧ║я╕П Diagram

sequenceDiagram
    participant B as ЁЯУМ board (browser)
    participant C as ЁЯПв counter (API)
    B->>C: 1 OPTIONS /api/students (preflight тАФ non-simple request)
    C-->>B: 2 Access-Control-Allow-Origin/Methods/Headers
    B->>C: 3 POST /api/students {name, class}  тЖР board validated (kindness)
    C-->>B: 4 400 {"error":{"code":"validation_failed"}}  тЖР counter validated (truth)
    Note over B: draw the error face ┬╖ textContent, never innerHTML

тЭУ What

ЁЯдФ Why

Because this boundary is where the two most common front-end security incidents live (a leaked key shipped in a bundle; an XSS through user text), and where the most common reliability complaint lives ("it just spins" тАФ no timeout, no error face). Three of the four rules cost one line each.

ЁЯФз How (in this repo)

ui/app.js: load() and enrol() show the three faces; StudentCard uses textContent for every user-supplied value. ui/mock_api.py sets the CORS headers, answers OPTIONS, re-validates the form server-side, and offers ?slow=1 and ?fail=1 so you can see each face on demand.

ЁЯзк Try it

python3 ui/mock_api.py
# 1) the three faces: edit API in app.js to '/api/students?slow=1' (loading), then '?fail=1' (error), then back
# 2) never trust the board тАФ bypass the form entirely:
curl -s -X POST http://127.0.0.1:8000/api/students -H 'Content-Type: application/json' -d '{"name":"X","class":"9Z"}'
#    тЖТ 400 validation_failed. The counter refused what the board never would have sent.
# 3) XSS, safely: enrol a student named  <b>bold</b>  тАФ the card shows the literal text (textContent).
#    Then temporarily use innerHTML in StudentCard and enrol  <img src=x onerror="alert('xss')">  тАФ see it fire. Undo it.
# 4) CORS: open the board from a different origin (python3 -m http.server 9001 in ui/) and watch the console тАФ
#    then notice mock_api.py's Access-Control-Allow-Origin header is what makes the fetch work at all.

тЬЕ Verify тАФ what you should see

Loading and error faces appear on demand and never leave a blank page. The raw curl gets 400 validation_failed тАФ proof the counter does not trust the board. The <b>bold</b> student renders as text with textContent and as markup (or worse) with innerHTML.

ЁЯПБ What you just proved

The board handles every outcome of a request, and you demonstrated both security rules тАФ server-side validation and escaping тАФ from both sides.

тЪая╕П Common mistakes

ЁЯПн Why this matters in production: XSS and leaked keys are the two front-end items on every security checklist, and "the page spins forever" is the most common support ticket. Four rules, four lines, most of the risk.

тПня╕П Next

The last mile: build, deploy and observe тАФ pinning the board up where people can see it, and watching what happens.

git checkout lesson-12-build-deploy-observe
тЖР PrevioustestingNext тЖТbuild deploy observe

This page is the lesson's README from the lesson-11-talking-to-backends branch, shown here so the whole School stays on one site. Code files open on GitHub at the same branch.