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

ЁЯкк рдзрдбрд╛ 06 тАФ Auth: рдХрд╛рдЙрдВрдЯрд░рд╡рд░рдЪреЗ рд╣реЙрд▓ рдкрд╛рд╕

ЁЯУН рддреБрдореНрд╣реА рдЗрдереЗ рдЖрд╣рд╛рдд: 12 рдкреИрдХреА рдзрдбрд╛ 06 ┬╖ рдорд╛рдЧреЗ: lesson-05-build-an-api ┬╖ рдкреБрдвреЗ: lesson-07-errors-idempotency


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

рдзрдбреЗ 01тАУ05, рдЖрдгрд┐ рддреНрдпрд╛рд╕реЛрдмрдд рдХреЛрдгрддреНрдпрд╛ slips рдХреЛрдг рджреЗрдК рд╢рдХрддреЛ: API keys, bearer tokens / JWTs, OAuth, рдЖрдгрд┐ рдПрдХрд╕рд╛рд░рдЦреЗ рдирд╕рд▓реЗрд▓реЗ рджреЛрди рдирдХрд╛рд░ (401 рд╡рд┐рд░реБрджреНрдз 403).

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

рд╕реВрдЪрдирд╛ рдлрд▓рдХ рд╡рд╛рдЪрд╛рдпрд▓рд╛ pass рд▓рд╛рдЧрдд рдирд╛рд╣реА тАФ рдХреЛрдгреАрд╣реА GET рдХрд░реВ рд╢рдХрддреЛ. рдкрдг рдиреЛрдВрджрд╡рд╣реАрдд рд▓рд┐рд╣рд╛рдпрдЪреЗ (POST, PUT, DELETE) рддрд░ counter рд╡рд░ рд╣реЙрд▓ рдкрд╛рд╕ ЁЯкк рджрд╛рдЦрд╡рд╛рд╡рд╛ рд▓рд╛рдЧрддреЛ. рдЖрдордЪрд╛ counter рдЖрдЬ рдПрдХрд╛рдЪ рдкреНрд░рдХрд╛рд░рдЪрд╛ pass рд╕реНрд╡реАрдХрд╛рд░рддреЛ: рдПрдХ header, X-API-Key: hall-pass-123.

рдЦрд▒реНрдпрд╛ рд╢рд╛рд│рд╛ рддреАрди рдкреНрд░рдХрд╛рд░рдЪреЗ pass рджреЗрддрд╛рдд, рдЖрдгрд┐ рддреБрдореНрд╣рд╛рд▓рд╛ рддрд┐рдиреНрд╣реА рднреЗрдЯрддреАрд▓:

  1. API key ЁЯФС тАФ рдПрдХрд╛ program рд▓рд╛ (canteen app) рджрд┐рд▓реЗрд▓реА рд▓рд╛рдВрдм random string. рд╕реЛрдкреА, рджреАрд░реНрдШрдХрд╛рд│ рдЯрд┐рдХрдгрд╛рд░реА, рд░рджреНрдж рдХрд░рддрд╛ рдпреЗрдгрд╛рд░реА. рддреА рдХрдзреАрдЪ URL, log, рдХрд┐рдВрд╡рд╛ browser рдордзреНрдпреЗ рдареЗрд╡реВ рдирдХрд╛ тАФ JavaScript рдордзреНрдпреЗ ship рд╣реЛрддрд╛рдХреНрд╖рдгреА рддреА рд╕рд╛рд░реНрд╡рдЬрдирд┐рдХ рд╣реЛрддреЗ.
  2. Bearer token / JWT ЁЯОл тАФ рд╡реНрдпрдХреНрддреАрдиреЗ login рдХреЗрд▓реНрдпрд╛рдирдВрддрд░ рддрд┐рд▓рд╛ рджрд┐рд▓реЗрд▓рд╛ рд╕рд╣реА рдХреЗрд▓реЗрд▓рд╛, рдореБрджрдд рд╕рдВрдкрдгрд╛рд░рд╛ pass: Authorization: Bearer eyJтАж. Server рддреЛ рд╢реЛрдзрдгреНрдпрд╛рдРрд╡рдЬреА рд╕рд╣реА рддрдкрд╛рд╕рддреЛ; рдореБрджрдд рд╕рдВрдкрд▓реА рдХреА рддреЛ рдЖрдкреЛрдЖрдк рдЪрд╛рд▓рд╛рдпрдЪрд╛ рдмрдВрдж рд╣реЛрддреЛ.
  3. OAuth 2 / OIDC ЁЯдЭ тАФ рдЕрдзрд┐рдХрд╛рд░ рд╕реЛрдкрд╡рдгреНрдпрд╛рдЪрд╛ рдЦреЗрд│: "рд╣реЗ calendar app рдорд╛рдЭреЗ events рд╡рд╛рдЪреВ рд╢рдХрддреЗ, рдкрдг delete рдХрд░реВ рд╢рдХрдд рдирд╛рд╣реА", user рдиреЗ provider рдЪреНрдпрд╛ рдкрд╛рдирд╛рд╡рд░ рджрд┐рд▓реЗрд▓реА, рдорд░реНрдпрд╛рджрд┐рдд (scoped), рд░рджреНрдж рдХрд░рддрд╛ рдпреЗрдгрд╛рд░реА рдкрд░рд╡рд╛рдирдЧреА. CI/CD рдЖрдгрд┐ AWS schools рдордзрд▓рд╛ OIDC badge рд╣рд╛рдЪ protocol machines рдордзреНрдпреЗ рд╡рд╛рдкрд░рд▓реЗрд▓рд╛ рдЖрд╣реЗ.

Pass рдХреЛрдгрддрд╛рд╣реА рдЕрд╕реЛ, рдирд┐рдпрдо рдЖрд╣реЗ least privilege: рдкреНрд░рддреНрдпреЗрдХ app рд▓рд╛ рдПрдХ key, рддреНрдпрд╛рд▓рд╛ рдЬреЗрд╡рдвреЗ рдХрд░рд╛рдпрдЪреЗ рддреЗрд╡рдвреНрдпрд╛рдкреБрд░рддреА рдорд░реНрдпрд╛рджрд┐рдд, рдард░рд▓реЗрд▓реНрдпрд╛ рд╡реЗрд│рд╛рдкрддреНрд░рдХрд╛рдиреЗ рдмрджрд▓рд▓реЗрд▓реА (rotate), рдЖрдгрд┐ рдЙрдШрдб рдЭрд╛рд▓реА рддреНрдпрд╛рдЪ рджрд┐рд╡рд╢реА рд░рджреНрдж рдХреЗрд▓реЗрд▓реА.

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

flowchart LR
    r["ЁЯФУ GET (read)"] -->|"1 no pass needed"| ok1["200"]
    w["тЬНя╕П POST / PUT / DELETE"] -->|"2 X-API-Key?"| chk{"pass?"}
    chk -->|"none"| u["401 unauthenticated"]
    chk -->|"wrong"| f["403 forbidden"]
    chk -->|"right"| ok2["201 / 200 / 204"]
    subgraph kinds["ЁЯкк three kinds of pass"]
        k["ЁЯФС API key тАФ a program's"]
        t["ЁЯОл bearer token / JWT тАФ a person's, expires"]
        o["ЁЯдЭ OAuth / OIDC тАФ delegated, scoped"]
    end

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

ЁЯкк рдкреНрд░рддреНрдпреЗрдХ рдкреНрд░рдХрд╛рд░рдЪрд╛ рд╣реЙрд▓ рдкрд╛рд╕: рд╣рд╛ рдзрдбрд╛ API key рд╡рд╛рдкрд░рддреЛ. Basic, Bearer tokens, JWT, session cookies, PKCE рд╕рд╣ OAuth 2.0, OpenID Connect, HMAC request signing рдЖрдгрд┐ mutual TLS рдкреНрд░рддреНрдпреЗрдХрд╛рдЪреЗ рдЪрд┐рддреНрд░ рдХрд╛рдврд▓реЗрд▓реЗ рдЖрд╣реЗ тАФ рдкреНрд░рддреНрдпреЗрдХ OAuth grant type, 401 рд╡рд┐рд░реБрджреНрдз 403, рдирд┐рд░реНрдгрдп-рдорд╛рд░реНрдЧрджрд░реНрд╢рди рдЖрдгрд┐ рдЪреБрдХрд╛ рдпрд╛рдВрд╕рд╣ тАФ рдзрдбрд╛ 14 тАФ рдкреНрд░рддреНрдпреЗрдХ authentication рдкрджреНрдзрдд рдордзреНрдпреЗ. рддреНрдпрд╛рддрд▓реНрдпрд╛ рд╕рд╛рдд рдЗрдереЗ рдЪрд╛рд▓рддрд╛рдд (grant_type=password рдирд╛рдХрд╛рд░рдгрд╛рд▒реНрдпрд╛ OAuth token endpoint рд╕рд╣): python3 api/auth_demo.py рдордЧ python3 api/auth_client.py рддрд╛рд░реЗрд╡рд░рдЪрд╛ рдкреНрд░рддреНрдпреЗрдХ header, рдореБрджрдд рд╕рдВрдкрд▓реЗрд▓рд╛ token, рдмрдирд╛рд╡рдЯ JWT, рдЫреЗрдбрдЫрд╛рдб рдХреЗрд▓реЗрд▓реА рд╕рд╣реА рдЖрдгрд┐ рдкреБрдиреНрд╣рд╛ рдкрд╛рдард╡рд▓реЗрд▓реА (replayed) request рджрд╛рдЦрд╡рддреЗ.

ЁЯдФ рдХрд╛

рдХрд╛рд░рдг counter рд╣реАрдЪ рдПрдХрдореЗрд╡ рдЬрд╛рдЧрд╛ рдЖрд╣реЗ рдЬрд┐рдереЗ slip рдЖрдгрд┐ pass рджреЛрдиреНрд╣реА рджрд┐рд╕рддрд╛рдд. рддрдкрд╛рд╕рдгреА рдЗрддрд░ рдХреБрдареЗрд╣реА рдареЗрд╡рд╛ тАФ UI рдордзреНрдпреЗ, database рдордзреНрдпреЗ тАФ рдЖрдгрд┐ рдХреЛрдгреАрддрд░реА рддреА рдЯрд╛рд│рдгрд╛рд░реЗ рджрд╛рд░ рд╢реЛрдзреЗрд▓рдЪ. рдЖрдгрд┐ 401/403 рдордзрд▓рд╛ рдЧреЛрдВрдзрд│ рдореНрд╣рдгрдЬреЗ рдлрдХреНрдд рдкрд╛рдВрдбрд┐рддреНрдп рдирд╛рд╣реА: 401 рд╡рд░ clients рдкреБрдиреНрд╣рд╛ login рдХрд░рддрд╛рдд рдЖрдгрд┐ 403 рд╡рд░ рдерд╛рдВрдмрддрд╛рдд; рддреНрдпрд╛рдВрдЪреА рдЕрджрд▓рд╛рдмрджрд▓ рдХреЗрд▓реА рддрд░ users рдХрд╛рдпрдо рдлреЗрд▒реНрдпрд╛рдд рдЕрдбрдХрддрд╛рдд рдХрд┐рдВрд╡рд╛ рдЪреБрдХреАрдиреЗ рд╣рд╛рд░ рдорд╛рдирддрд╛рдд.

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

school_api.py рдордзрд▓реЗ authorized(): header рдирд╛рд╣реА тЖТ 401, рдЪреБрдХреАрдЪреА рдХрд┐рдВрдордд тЖТ 403. Key environment рдордзрд▓реНрдпрд╛ SCHOOL_API_KEY рдордзреВрди рдпреЗрддреЗ. рдкреНрд░рддреНрдпреЗрдХ write handler рддреЗ route рддрдкрд╛рд╕рдгреАрдЪреНрдпрд╛ рдирдВрддрд░ рдЖрдгрд┐ body рд╡рд╛рдЪрдгреНрдпрд╛рдЪреНрдпрд╛ рдЖрдзреА call рдХрд░рддреЛ тАФ рдзрдбрд╛ 05 рдордзрд▓рд╛ рдХреНрд░рдо.

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

B=http://127.0.0.1:8080/v1; J='Content-Type: application/json'
curl -s -X POST $B/students -H "$J" -d '{"name":"Zoya","class":"3B"}'                              # 401
curl -s -X POST $B/students -H 'X-API-Key: wrong' -H "$J" -d '{"name":"Zoya","class":"3B"}'        # 403
curl -s -X POST $B/students -H 'X-API-Key: hall-pass-123' -H "$J" -d '{"name":"Zoya","class":"3B"}' # 201
# rotate the pass without touching code:
SCHOOL_API_KEY=new-pass-456 python3 api/school_api.py 8081 &
curl -s -X DELETE http://127.0.0.1:8081/v1/students/1 -H 'X-API-Key: hall-pass-123' -o /dev/null -w '%{http_code}\n'   # 403 тАФ old pass

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

hint: the hall pass is missing рд╕рд╣ 401, рдордЧ a pass, but not the right one рд╕рд╣ 403, рдордЧ 201. Port 8081 рд╡рд░ рдЬреБрдиреНрдпрд╛ key рд▓рд╛ 403 рдорд┐рд│рддреЛ тАФ рддреБрдореНрд╣реА environment variable рдиреЗ рдЖрдгрд┐ code рдордзреНрдпреЗ рд╢реВрдиреНрдп рдмрджрд▓ рдХрд░реВрди credential rotate рдХреЗрд▓реЗ.

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

рд╡рд╛рдЪрдгреНрдпрд╛рдЪреА рдЖрдгрд┐ рд▓рд┐рд╣рд┐рдгреНрдпрд╛рдЪреА рджрд╛рд░реЗ рд╡реЗрдЧрд│реА рдЖрд╣реЗрдд, рджреЛрди рдирдХрд╛рд░рд╛рдВрдЪреЗ рдЕрд░реНрде рд╡реЗрдЧрд│реЗ рдЖрд╣реЗрдд, рдЖрдгрд┐ pass code рдЪреНрдпрд╛ рдмрд╛рд╣реЗрд░ рд░рд╛рд╣рддреЛ.

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

ЁЯПн рдкреНрд░рддреНрдпрдХреНрд╖ рд╡рд╛рдкрд░рд╛рдд рд╣реЗ рдХрд╛ рдорд╣рддреНрддреНрд╡рд╛рдЪреЗ: рдмрд╣реБрддреЗрдХ рдЦрд░реЗ API breaches рд╣реБрд╢рд╛рд░реАрдЪреЗ рдирд╕рддрд╛рдд тАФ public repo рдордзрд▓реА key, рдХрдзреАрдЪ рдореБрджрдд рди рд╕рдВрдкрдгрд╛рд░рд╛ token, рдХрд┐рдВрд╡рд╛ authorized() call рдХрд░рд╛рдпрд▓рд╛ рд╡рд┐рд╕рд░рд▓реЗрд▓рд╛ endpoint. рд╡рд░рдЪреЗ рдХрдВрдЯрд╛рд│рд╡рд╛рдгреЗ рдирд┐рдпрдо рд╣реЗрдЪ рд╕рдВрдкреВрд░реНрдг рд╕рдВрд░рдХреНрд╖рдг рдЖрд╣реЗ.

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

рднрд╛рдЧ 2 рд╕реБрд░реВ рд╣реЛрддреЛ: counter рдЦрд▒реНрдпрд╛ рдЬрдЧрд╛рдд рдЪрд╛рд▓рд╡рдгреЗ. рдЖрдзреА, рдирдореНрд░ рдирдХрд╛рд░ рдЖрдгрд┐ рдкрд╛рд╡рддреА рдХреНрд░рдорд╛рдВрдХ тАФ errors, retries рдЖрдгрд┐ idempotency keys.

git checkout lesson-07-errors-idempotency

ЁЯкк Lesson 06 тАФ Auth: hall passes at the counter

ЁЯУН You are here: Lesson 06 of 12 ┬╖ Previous: lesson-05-build-an-api ┬╖ Next: lesson-07-errors-idempotency


ЁЯУж What's in this branch

Lessons 01тАУ05, plus who may hand in which slips: API keys, bearer tokens / JWTs, OAuth, and the two refusals that are not the same thing (401 vs 403).

ЁЯзТ Explain like I'm 5

Reading the notice board needs no pass тАФ anyone may GET. But writing in the register (POST, PUT, DELETE) means showing a hall pass ЁЯкк at the counter. Our counter accepts one kind today: a header, X-API-Key: hall-pass-123.

Real schools issue three kinds of pass, and you will meet all three:

  1. API key ЁЯФС тАФ a long random string given to a program (the canteen app). Simple, long-lived, revocable. Never put it in a URL, a log, or a browser тАФ it is public the moment it ships in JavaScript.
  2. Bearer token / JWT ЁЯОл тАФ a signed, expiring pass issued to a person after they log in: Authorization: Bearer eyJтАж. The server checks the signature instead of looking it up; it stops working by itself when it expires.
  3. OAuth 2 / OIDC ЁЯдЭ тАФ the delegation dance: "this calendar app may read my events, but not delete them", granted by the user on the provider's page, scoped, revocable. The CI/CD and AWS schools' OIDC badge is this same protocol used between machines.

Whatever the pass, the rule is least privilege: a key per app, scoped to what it must do, rotated on a schedule, revoked the day it leaks.

ЁЯЧ║я╕П Diagram

flowchart LR
    r["ЁЯФУ GET (read)"] -->|"1 no pass needed"| ok1["200"]
    w["тЬНя╕П POST / PUT / DELETE"] -->|"2 X-API-Key?"| chk{"pass?"}
    chk -->|"none"| u["401 unauthenticated"]
    chk -->|"wrong"| f["403 forbidden"]
    chk -->|"right"| ok2["201 / 200 / 204"]
    subgraph kinds["ЁЯкк three kinds of pass"]
        k["ЁЯФС API key тАФ a program's"]
        t["ЁЯОл bearer token / JWT тАФ a person's, expires"]
        o["ЁЯдЭ OAuth / OIDC тАФ delegated, scoped"]
    end

тЭУ What

ЁЯкк Every kind of hall pass: this lesson uses an API key. Basic, Bearer tokens, JWT, session cookies, OAuth 2.0 with PKCE, OpenID Connect, HMAC request signing and mutual TLS are each drawn тАФ with every OAuth grant type, 401 vs 403, a decision guide and the mistakes тАФ in lesson 14 тАФ every authentication method. Seven of them run here (including an OAuth token endpoint that refuses grant_type=password): python3 api/auth_demo.py then python3 api/auth_client.py shows every header on the wire, an expired token, a forged JWT, a tampered signature and a replayed request.

ЁЯдФ Why

Because the counter is the only place that sees both the slip and the pass. Put the check anywhere else тАФ in the UI, in the database тАФ and somebody will find the door that skips it. And because 401/403 confusion is not pedantry: clients re-login on 401 and stop on 403; swap them and users loop forever or give up wrongly.

ЁЯФз How (in this repo)

authorized() in school_api.py: missing header тЖТ 401, wrong value тЖТ 403. The key comes from SCHOOL_API_KEY in the environment. Every write handler calls it after the route check and before reading the body тАФ the order from lesson 05.

ЁЯзк Try it

B=http://127.0.0.1:8080/v1; J='Content-Type: application/json'
curl -s -X POST $B/students -H "$J" -d '{"name":"Zoya","class":"3B"}'                              # 401
curl -s -X POST $B/students -H 'X-API-Key: wrong' -H "$J" -d '{"name":"Zoya","class":"3B"}'        # 403
curl -s -X POST $B/students -H 'X-API-Key: hall-pass-123' -H "$J" -d '{"name":"Zoya","class":"3B"}' # 201
# rotate the pass without touching code:
SCHOOL_API_KEY=new-pass-456 python3 api/school_api.py 8081 &
curl -s -X DELETE http://127.0.0.1:8081/v1/students/1 -H 'X-API-Key: hall-pass-123' -o /dev/null -w '%{http_code}\n'   # 403 тАФ old pass

тЬЕ Verify тАФ what you should see

401 with hint: the hall pass is missing, then 403 with a pass, but not the right one, then 201. On port 8081 the old key gets 403 тАФ you rotated a credential with an environment variable and zero code changes.

ЁЯПБ What you just proved

Reads and writes have different doors, the two refusals mean different things, and the pass lives outside the code.

тЪая╕П Common mistakes

ЁЯПн Why this matters in production: most real API breaches are not clever тАФ they are a key in a public repo, a token that never expired, or an endpoint that forgot to call authorized(). The boring rules above are the whole defence.

тПня╕П Next

Part 2 begins: running the counter for real. First, polite refusals and receipt numbers тАФ errors, retries and idempotency keys.

git checkout lesson-07-errors-idempotency
тЖР Previousbuild an apiNext тЖТerrors idempotency

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