ЁЯПл The SchoolтА║ЁЯЫОя╕П API GatewayтА║ЁЯФР рдзрдбрд╛ 06 тАФ рдЖрдд рдХреЛрдг рдпреЗрдК рд╢рдХрддреЛ: IAM, JWT рдЖрдгрд┐ Lambda authorizers
ЁЯЦ╝я╕П See the drawing + lab ЁЯПа Course home ЁЯМ┐ Branch on GitHub тЬПя╕П View source
ЁЯЦ╝я╕П рдЖрдХреГрддреА рдЖрдгрд┐ labThe drawing + lab рдкреВрд░реНрдг рдкрд╛рдирд╛рд╡рд░ рдЙрдШрдбрд╛ тЖЧOpen full page тЖЧ

ЁЯФР рдзрдбрд╛ 06 тАФ рдЖрдд рдХреЛрдг рдпреЗрдК рд╢рдХрддреЛ: IAM, JWT рдЖрдгрд┐ Lambda authorizers

ЁЯУН рддреБрдореНрд╣реА рдЗрдереЗ рдЖрд╣рд╛рдд: 12 рдкреИрдХреА рдзрдбрд╛ 06 ┬╖ рдорд╛рдЧреЗ: lesson-05-stages-deployments ┬╖ рдкреБрдвреЗ: lesson-07-throttling


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

рдзрдбреЗ 01тАУ05, рдЖрдгрд┐ badge рдЪреА рддрдкрд╛рд╕рдгреА: JWT authorizer (signature, issuer, audience, expiry, рдордЧ scope), Lambda authorizer (рддреБрдордЪрд╛ рд╕реНрд╡рддрдГрдЪрд╛ code рдирд┐рд░реНрдгрдп рдШреЗрддреЛ), AWS рдордзрд▓реНрдпрд╛ callers рд╕рд╛рдареА IAM (SigV4) тАФ рдЖрдгрд┐ API key рдореНрд╣рдгрдЬреЗ authentication рдХрд╛ рдирд╛рд╣реА. рдЗрдереЗ рднрд╛рдЧ 1: front office рд╕рдВрдкрддреЛ. apigw/demo.py рдордзрд▓реЗ auth() рдкрд╛рдЪ tokens рдЖрдгрд┐ рджреЛрди badges рд╡рд╛рдкрд░реВрди рдкрд╛рд╣рддреЗ.

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

Desk рд╡рд░ clerk badge ЁЯкк рдорд╛рдЧрддреЛ. рдЪрд╛рдВрдЧрд▓реНрдпрд╛ badge рдордзреНрдпреЗ рдЪрд╛рд░ рдЧреЛрд╖реНрдЯреА рдЕрд╕рддрд╛рдд рдЬреНрдпрд╛ clerk рддрдкрд╛рд╕рддреЛ:

  1. рд╢рд┐рдХреНрдХрд╛ рдЦрд░рд╛ рдЖрд╣реЗ (рдкреЗрдирдиреЗ рдХрд╛рдврд▓реЗрд▓рд╛ рдирд╛рд╣реА) тАФ signature;
  2. рддреЛ рдЖрдкрд▓реНрдпрд╛рдЪ badge office рдХрдбреВрди рдЖрд▓рд╛ рдЖрд╣реЗ тАФ issuer;
  3. рддреЛ рдпрд╛ рд╢рд╛рд│реЗрд╕рд╛рдареА рдмрдирд╡рд▓рд╛ рдЖрд╣реЗ, рд╢реЗрдЬрд╛рд░рдЪреНрдпрд╛ library рд╕рд╛рдареА рдирд╛рд╣реА тАФ audience;
  4. рддрд╛рд░реАрдЦ рдЙрд▓рдЯреВрди рдЧреЗрд▓реЗрд▓реА рдирд╛рд╣реА тАФ expiry.

рдХреЛрдгрддреАрд╣реА рддрдкрд╛рд╕рдгреА рдЕрдкрдпрд╢реА тЖТ "рддреБрдореНрд╣реА рдХреЛрдг?" (401). рдЪрд╛рд░рд╣реА рдкрд╛рд╕, рдкрдг badge рд╡рд░ "marks рд╡рд╛рдЪреВ рд╢рдХрддреЛ" рдЕрд╕реЗ рдЖрд╣реЗ рдЖрдгрд┐ visitor рд▓рд╛ marks рд▓рд┐рд╣рд╛рдпрдЪреЗ рдЖрд╣реЗрдд тЖТ "рдореА рддреБрдореНрд╣рд╛рд▓рд╛ рдУрд│рдЦрддреЛ, рдкрдг рдирд╛рд╣реА" (403).

рдХрд╛рд╣реА рджрд╛рд░рд╛рдВрд╡рд░ рд╕реНрд╡рддрдГрдЪрд╛ рдирд┐рдпрдо рдЕрд╕рд▓реЗрд▓рд╛ рдЦрд╛рд╕ рдкрд╣рд╛рд░реЗрдХрд░реА ЁЯзСтАНтЬИя╕П рдЕрд╕рддреЛ тАФ "рдлрдХреНрдд T рдиреЗ рд╕реБрд░реВ рд╣реЛрдгрд╛рд░реЗ staff badges". рддреЛ рдореНрд╣рдгрдЬреЗ Lambda authorizer.

рдЖрдгрд┐ partners рджрд╛рдЦрд╡рддрд╛рдд рддреЛ visitor pass number? рддреЛ clerk рд▓рд╛ рдлрдХреНрдд рдХреЛрдгрддрд╛ partner рднреЗрдЯреА рдореЛрдЬрддреЛ рдЖрд╣реЗ рддреЗ рд╕рд╛рдВрдЧрддреЛ. рддреЛ number copy рдХрд░рдгрд╛рд░рд╛ рдХреЛрдгреАрд╣реА рддреЛ рд╡рд╛рдкрд░реВ рд╢рдХрддреЛ. рддреЛ badge рдирд╛рд╣реА.

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

flowchart LR
    req["ЁЯЩЛ GET /students/me<br/>Authorization: Bearer тАж"] --> s{"ЁЯФП signature?"}
    s -->|"forged"| u["401 bad signature"]
    s --> i{"ЁЯПл issuer?"}
    i -->|"other issuer"| u2["401 wrong issuer"]
    i --> a{"ЁЯОп audience = school-api?"}
    a -->|"library-api"| u3["401 wrong audience"]
    a --> e{"тП░ expired?"}
    e -->|"yes"| u4["401 expired"]
    e --> sc{"ЁЯУЬ scope?"}
    sc -->|"needs marks:write"| f["403 Forbidden"]
    sc --> ok["тЬЕ 200 тАФ principal teacher-42"]

ЁЯЧ║я╕П рдХрд╛рдврд▓реЗрд▓реА рдЖрд╡реГрддреНрддреА + рдПрдХ lab: https://school-edh.pages.dev/apigateway/lesson-diagrams.html#l06

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

ЁЯдФ рдХрд╛

рдХрд╛рд░рдг рд╕реНрд╡рддрдГ tokens рддрдкрд╛рд╕рдгрд╛рд░рд╛ рдкреНрд░рддреНрдпреЗрдХ back end рдореНрд╣рдгрдЬреЗ expiry рдХрд┐рдВрд╡рд╛ audience рд╡рд┐рд╕рд░рдгреНрдпрд╛рдЪреА рдЖрдгрдЦреА рдПрдХ рдЬрд╛рдЧрд╛. рджрд╛рд░рд╛рд╡рд░рдЪрд╛ authorizer рд╣реА рддрдкрд╛рд╕рдгреА рдПрдХрджрд╛рдЪ, рдПрдХрд╛рдЪ рдкрджреНрдзрддреАрдиреЗ, рдХреЛрдгрддрд╛рд╣реА kitchen code рдЪрд╛рд▓рдгреНрдпрд╛рдЖрдзреА рдХрд░рддреЛ тАФ рдЖрдгрд┐ рддрдкрд╛рд╕рд▓реЗрд▓реА identity (principal) рдЖрдд рдкрд╛рдард╡рддреЛ, рдореНрд╣рдгрдЬреЗ kitchen рддрд┐рдЪреНрдпрд╛рд╡рд░ рд╡рд┐рд╢реНрд╡рд╛рд╕ рдареЗрд╡реВ рд╢рдХрддреЗ. Audience рдЪреА рддрдкрд╛рд╕рдгреА рд╕рд░реНрд╡рд╛рдд рдорд╣рддреНрддреНрд╡рд╛рдЪреА: рддреА рдирд╕реЗрд▓ рддрд░ library app рд╕рд╛рдареА рдмрдирд╡рд▓реЗрд▓рд╛ token рд╢рд╛рд│реЗрдЪрд╛ API рдЙрдШрдбреЗрд▓.

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

apigw/gateway.py рдордзрд▓реЗ check_jwt() рдЪрд╛рд░ рддрдкрд╛рд╕рдгреНрдпрд╛ рдХреНрд░рдорд╛рдиреЗ рдХрд░рддреЗ рдЖрдгрд┐ (claims, None) рдХрд┐рдВрд╡рд╛ (None, reason) рдкрд░рдд рджреЗрддреЗ. Gateway._handle() рдХрд╛рд░рдгрд╛рд╕рд╣ 401 рдкрд░рдд рджреЗрддреЗ, рдордЧ route.scope рддрдкрд╛рд╕рддреЗ (POST /marks рд▓рд╛ marks:write рд▓рд╛рдЧрддреЛ) рдЖрдгрд┐ рддреЛ рдирд╕реЗрд▓ рддрд░ 403 рдкрд░рдд рджреЗрддреЗ; token рдЪрд╛ sub request рдЪрд╛ principal рдмрдирддреЛ. route.auth == "lambda" рд╕рд╛рдареА рддреЗ apigw/demo.py рдордзрд▓реЗ teacher_badge() call рдХрд░рддреЗ, рдЬреЗ рдлрдХреНрдд badge-T-42 рд╕реНрд╡реАрдХрд╛рд░рддреЗ. Demo рдордзрд▓реЗ token() teacher-42 рд╕рд╛рдареА рдПрдХрд╛ рддрд╛рд╕рд╛рдЪрд╛ рдпреЛрдЧреНрдп token рдмрдирд╡рддреЗ.

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

python3 apigw/demo.py auth
python3 - <<'EOF'
import sys; sys.path.insert(0, "apigw"); from demo import office, token, JWT
from gateway import make_jwt, check_jwt
gw, c = office()
now = c()
print(check_jwt("hello", **JWT, now=now))
print(check_jwt(make_jwt(dict(iss="https://evil.example", aud="school-api", exp=now + 60), JWT["secret"]), **JWT, now=now))
print(check_jwt(make_jwt(dict(iss=JWT["issuer"], aud=["library-api", "school-api"], sub="katrina", exp=now + 60), JWT["secret"]), **JWT, now=now))
print(check_jwt(make_jwt(dict(iss=JWT["issuer"], aud="school-api", exp=now + 60), "guessed-secret"), **JWT, now=now))
w = token(c, scope="marks:read marks:write")
print(gw.handle("POST", "/marks", {"Authorization": "Bearer " + w}, {"student": "9", "subject": "science", "mark": 88})[0::2])
print(gw.handle("GET", "/staffroom", {"Authorization": "badge-T-42"})[0::2], gw.logs[-1]["principal"])
EOF

рдЪрд╛рд▓реВ office рд╡рд░, рдШрдбреНрдпрд╛рд│рд╛рд╡рд░реВрди рдмрдирд╡рд▓реЗрд▓реНрдпрд╛ рдПрдХрд╛ рддрд╛рд╕рд╛рдЪреНрдпрд╛ рдЦрд▒реНрдпрд╛ token рд╕рд╣:

python3 apigw/demo.py serve &
sleep 1                                # give the office a second to open
TOKEN=$(python3 -c "import sys,time; sys.path.insert(0,'apigw'); from demo import token; print(token(time.time))")
curl localhost:8080/students/me
curl -H "Authorization: Bearer $TOKEN" localhost:8080/students/me
curl -X POST -H "Authorization: Bearer $TOKEN" -H 'content-type: application/json' \
     -d '{"student":"7","subject":"maths","mark":91}' localhost:8080/marks
curl -H "Authorization: badge-T-42" localhost:8080/staffroom
kill %1

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

auth рдкреНрд░рддреНрдпреЗрдХ token рд╕рд╛рдареА рдПрдХ, рдЕрд╕реЗ print рдХрд░рддреЗ: no token тЖТ 401 {'message': 'Unauthorized', 'why': 'no token'}, forged тЖТ 401 тАж 'bad signature', expired тЖТ 401 тАж 'expired', other app's token тЖТ 401 тАж 'wrong audience', good тЖТ 200 {'you': 'teacher-42'}; рдордЧ POST /marks тЖТ 403 {'message': 'Forbidden', 'why': 'needs scope marks:write'}; рдордЧ Lambda authorizer: badge-S-1 рд╕рд╛рдареА GET /staffroom тЖТ 403 {'message': 'User is not authorized to access this resource'} рдЖрдгрд┐ badge-T-42 рд╕рд╛рдареА 200 {'you': 'teacher-42'}.

рддреБрдордЪрд╛ snippet (None, 'not a JWT'), (None, 'wrong issuer'), рдХрддрд░рд┐рдирд╛рдЪреНрдпрд╛ token рдЪреЗ claims (school-api рдЕрд╕рд▓реЗрд▓реА aud list рд╕реНрд╡реАрдХрд╛рд░рд▓реА рдЬрд╛рддреЗ), рдЕрдВрджрд╛рдЬрд╛рдиреЗ рд▓рд╛рд╡рд▓реЗрд▓реНрдпрд╛ secret рд╕рд╛рдареА (None, 'bad signature'), рджреЛрдиреНрд╣реА scopes рдЕрд╕рд▓реЗрд▓реНрдпрд╛ token рд╕рд╛рдареА (201, {'saved': {'student': '9', 'subject': 'science', 'mark': 88}, 'table': 'marks-prod'}), рдЖрдгрд┐ (200, {'you': 'teacher-42'}) teacher-42 print рдХрд░рддреЛ.

рдЪрд╛рд▓реВ office {"message": "Unauthorized", "why": "no token"}, рдордЧ {"you": "teacher-42"}, рдордЧ {"message": "Forbidden", "why": "needs scope marks:write"} (default token рдордзреНрдпреЗ рдлрдХреНрдд marks:read рдЖрд╣реЗ), рдордЧ badge рд╕рд╛рдареА {"you": "teacher-42"} рдЕрд╕реЗ рдЙрддреНрддрд░ рджреЗрддреЗ.

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

рдкреНрд░рддреНрдпреЗрдХ 401 рдЖрдгрд┐ рдкреНрд░рддреНрдпреЗрдХ 403 рдЪреЗ рдХрд╛рд░рдг рддреБрдореНрд╣реА рд╕рд╛рдВрдЧреВ рд╢рдХрддрд╛: badge рд╡рд░ рдЪрд╛рд░ рддрдкрд╛рд╕рдгреНрдпрд╛, рдордЧ scope рдЪреА рддрдкрд╛рд╕рдгреА тАФ рдЖрдгрд┐ Lambda authorizer рдореНрд╣рдгрдЬреЗ рддреНрдпрд╛рдЪ рджрд╛рд░рд╛рд╡рд░рдЪрд╛ рддреБрдордЪрд╛ рд╕реНрд╡рддрдГрдЪрд╛ рдирд┐рдпрдо.

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

ЁЯПн рдкреНрд░рддреНрдпрдХреНрд╖ рд╡рд╛рдкрд░рд╛рдд

On a real account тАФ HTTP API рд╡рд░рдЪрд╛ JWT authorizer (Cognito рдХрд┐рдВрд╡рд╛ рдХреЛрдгрддрд╛рд╣реА OIDC issuer) рдЖрдгрд┐ scope рд▓рд╛рдЧрдгрд╛рд░рд╛ route:

aws apigatewayv2 create-authorizer --api-id a1b2c3 --name school-jwt \
    --authorizer-type JWT --identity-source '$request.header.Authorization' \
    --jwt-configuration Audience=school-api,Issuer=https://cognito-idp.ap-south-1.amazonaws.com/ap-south-1_AbCdEf123
aws apigatewayv2 update-route --api-id a1b2c3 --route-id r0ute1 \
    --authorization-type JWT --authorizer-id au7h01 --authorization-scopes marks:write

AWS SAM тАФ рдкреВрд░реНрдг HTTP API рд╕рд╛рдареА рддреЗрдЪ:

SchoolApi:
  Type: AWS::Serverless::HttpApi
  Properties:
    Auth:
      DefaultAuthorizer: SchoolJwt
      Authorizers:
        SchoolJwt:
          IdentitySource: $request.header.Authorization
          JwtConfiguration:
            issuer: https://login.school.example
            audience: [school-api]

300-second result cache рдЕрд╕рд▓реЗрд▓рд╛ REST API Lambda authorizer:

aws apigateway create-authorizer --rest-api-id abc123 --name staff-badge --type TOKEN \
    --identity-source method.request.header.Authorization \
    --authorizer-uri arn:aws:apigateway:ap-south-1:lambda:path/2015-03-31/functions/arn:aws:lambda:ap-south-1:111122223333:function:staff-badge/invocations \
    --authorizer-result-ttl-in-seconds 300

ЁЯПн Production рдордзреНрдпреЗ рд╣реЗ рдХрд╛ рдорд╣рддреНрддреНрд╡рд╛рдЪреЗ: рдкреНрд░рддреНрдпреЗрдХ route рд▓рд╛ authorizer рд╣рд╡рд╛, рдХрд┐рдВрд╡рд╛ рддреЛ public рдХрд╛ рдЖрд╣реЗ рдпрд╛рдЪреЗ рд▓рд┐рдЦрд┐рдд рдХрд╛рд░рдг. Reviews рддреАрди рдЧреЛрд╖реНрдЯреА рддрдкрд╛рд╕рддрд╛рдд: audience, рдкреНрд░рддреНрдпреЗрдХ route рдЪреЗ scopes, рдЖрдгрд┐ рдХреЛрдгрддрд╛рд╣реА route рдлрдХреНрдд API key рд╡рд░ рдЕрд╡рд▓рдВрдмреВрди рдирд╛рд╣реА.

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

рднрд╛рдЧ 1 рдкреВрд░реНрдг рдЭрд╛рд▓рд╛: рддреБрдореНрд╣реА рдХреЛрдг рдЖрд╣рд╛рдд рддреЗ office рд▓рд╛ рдорд╛рд╣реАрдд рдЖрд╣реЗ. рднрд╛рдЧ 2 рддреЗ рдЦрд░реЛрдЦрд░ рдЪрд╛рд▓рд╡рддреЛ, рд╕реБрд░реБрд╡рд╛рдд рджрд╛рд░рд╛рд╡рд░рдЪреНрдпрд╛ рдЧрд░реНрджреАрдкрд╛рд╕реВрди тАФ throttling рдЖрдгрд┐ usage plans.

git checkout lesson-07-throttling

ЁЯФР Lesson 06 тАФ Who may come in: IAM, JWT and Lambda authorizers

ЁЯУН You are here: Lesson 06 of 12 ┬╖ Previous: lesson-05-stages-deployments ┬╖ Next: lesson-07-throttling


ЁЯУж What's in this branch

Lessons 01тАУ05, plus the badge check: a JWT authorizer (signature, issuer, audience, expiry, then scope), a Lambda authorizer (your own code decides), IAM (SigV4) for callers inside AWS тАФ and why an API key is not authentication. This ends Part 1: the front office. auth() in apigw/demo.py tries five tokens and two badges.

ЁЯзТ Explain like I'm 5

At the desk, the clerk asks for a badge ЁЯкк. A good badge has four things the clerk checks:

  1. the stamp is real (not drawn with a pen) тАФ the signature;
  2. it comes from our badge office тАФ the issuer;
  3. it was made for this school, not for the library next door тАФ the audience;
  4. the date has not passed тАФ the expiry.

Any check fails тЖТ "Who are you?" (401). All four pass, but the badge says "may read marks" and the visitor wants to write marks тЖТ "I know you, but no" (403).

Some doors have a special guard ЁЯзСтАНтЬИя╕П with their own rule тАФ "only staff badges starting with T". That is a Lambda authorizer.

And the visitor pass number that partners show? It only tells the clerk which partner is counting visits. Anyone who copies the number can use it. It is not a badge.

ЁЯЧ║я╕П Diagram

flowchart LR
    req["ЁЯЩЛ GET /students/me<br/>Authorization: Bearer тАж"] --> s{"ЁЯФП signature?"}
    s -->|"forged"| u["401 bad signature"]
    s --> i{"ЁЯПл issuer?"}
    i -->|"other issuer"| u2["401 wrong issuer"]
    i --> a{"ЁЯОп audience = school-api?"}
    a -->|"library-api"| u3["401 wrong audience"]
    a --> e{"тП░ expired?"}
    e -->|"yes"| u4["401 expired"]
    e --> sc{"ЁЯУЬ scope?"}
    sc -->|"needs marks:write"| f["403 Forbidden"]
    sc --> ok["тЬЕ 200 тАФ principal teacher-42"]

ЁЯЧ║я╕П Drawn version + a lab: https://school-edh.pages.dev/apigateway/lesson-diagrams.html#l06

тЭУ What

ЁЯдФ Why

Because every back end that checks tokens itself is one more place to forget the expiry or the audience. An authorizer at the door makes the check once, the same way, before any kitchen code runs тАФ and passes the verified identity (principal) inward, so the kitchen can trust it. The audience check matters most: without it, a token made for the library app would open the school API.

ЁЯФз How (in this repo)

check_jwt() in apigw/gateway.py makes the four checks in order and returns (claims, None) or (None, reason). Gateway._handle() returns 401 with the reason, then checks route.scope (POST /marks needs marks:write) and returns 403 if it is missing; the token's sub becomes the request's principal. For route.auth == "lambda" it calls teacher_badge() from apigw/demo.py, which accepts only badge-T-42. token() in the demo makes a good one-hour token for teacher-42.

ЁЯзк Try it

python3 apigw/demo.py auth
python3 - <<'EOF'
import sys; sys.path.insert(0, "apigw"); from demo import office, token, JWT
from gateway import make_jwt, check_jwt
gw, c = office()
now = c()
print(check_jwt("hello", **JWT, now=now))
print(check_jwt(make_jwt(dict(iss="https://evil.example", aud="school-api", exp=now + 60), JWT["secret"]), **JWT, now=now))
print(check_jwt(make_jwt(dict(iss=JWT["issuer"], aud=["library-api", "school-api"], sub="katrina", exp=now + 60), JWT["secret"]), **JWT, now=now))
print(check_jwt(make_jwt(dict(iss=JWT["issuer"], aud="school-api", exp=now + 60), "guessed-secret"), **JWT, now=now))
w = token(c, scope="marks:read marks:write")
print(gw.handle("POST", "/marks", {"Authorization": "Bearer " + w}, {"student": "9", "subject": "science", "mark": 88})[0::2])
print(gw.handle("GET", "/staffroom", {"Authorization": "badge-T-42"})[0::2], gw.logs[-1]["principal"])
EOF

On the live office, with a real one-hour token made from the clock:

python3 apigw/demo.py serve &
sleep 1                                # give the office a second to open
TOKEN=$(python3 -c "import sys,time; sys.path.insert(0,'apigw'); from demo import token; print(token(time.time))")
curl localhost:8080/students/me
curl -H "Authorization: Bearer $TOKEN" localhost:8080/students/me
curl -X POST -H "Authorization: Bearer $TOKEN" -H 'content-type: application/json' \
     -d '{"student":"7","subject":"maths","mark":91}' localhost:8080/marks
curl -H "Authorization: badge-T-42" localhost:8080/staffroom
kill %1

тЬЕ Verify тАФ what you should see

auth prints, one per token: no token тЖТ 401 {'message': 'Unauthorized', 'why': 'no token'}, forged тЖТ 401 тАж 'bad signature', expired тЖТ 401 тАж 'expired', other app's token тЖТ 401 тАж 'wrong audience', good тЖТ 200 {'you': 'teacher-42'}; then POST /marks тЖТ 403 {'message': 'Forbidden', 'why': 'needs scope marks:write'}; then the Lambda authorizer: GET /staffroom тЖТ 403 {'message': 'User is not authorized to access this resource'} for badge-S-1 and 200 {'you': 'teacher-42'} for badge-T-42.

Your snippet prints (None, 'not a JWT'), (None, 'wrong issuer'), the claims for Katrina's token (an aud list that contains school-api is accepted), (None, 'bad signature') for the guessed secret, (201, {'saved': {'student': '9', 'subject': 'science', 'mark': 88}, 'table': 'marks-prod'}) for the token with both scopes, and (200, {'you': 'teacher-42'}) teacher-42.

The live office answers {"message": "Unauthorized", "why": "no token"}, then {"you": "teacher-42"}, then {"message": "Forbidden", "why": "needs scope marks:write"} (the default token only has marks:read), then {"you": "teacher-42"} for the badge.

ЁЯПБ What you just proved

You can name the reason for every 401 and every 403: four checks on the badge, then a scope check тАФ and a Lambda authorizer is simply your own rule at the same door.

тЪая╕П Common mistakes

ЁЯПн In production

On a real account тАФ a JWT authorizer on an HTTP API (Cognito or any OIDC issuer) and a route that needs a scope:

aws apigatewayv2 create-authorizer --api-id a1b2c3 --name school-jwt \
    --authorizer-type JWT --identity-source '$request.header.Authorization' \
    --jwt-configuration Audience=school-api,Issuer=https://cognito-idp.ap-south-1.amazonaws.com/ap-south-1_AbCdEf123
aws apigatewayv2 update-route --api-id a1b2c3 --route-id r0ute1 \
    --authorization-type JWT --authorizer-id au7h01 --authorization-scopes marks:write

AWS SAM тАФ the same for a whole HTTP API:

SchoolApi:
  Type: AWS::Serverless::HttpApi
  Properties:
    Auth:
      DefaultAuthorizer: SchoolJwt
      Authorizers:
        SchoolJwt:
          IdentitySource: $request.header.Authorization
          JwtConfiguration:
            issuer: https://login.school.example
            audience: [school-api]

A REST API Lambda authorizer with a 300-second result cache:

aws apigateway create-authorizer --rest-api-id abc123 --name staff-badge --type TOKEN \
    --identity-source method.request.header.Authorization \
    --authorizer-uri arn:aws:apigateway:ap-south-1:lambda:path/2015-03-31/functions/arn:aws:lambda:ap-south-1:111122223333:function:staff-badge/invocations \
    --authorizer-result-ttl-in-seconds 300

ЁЯПн Why this matters in production: every route should have an authorizer or a written reason why it is public. Reviews check three things: the audience, the scopes per route, and that no route relies on an API key alone.

тПня╕П Next

Part 1 is done: the office knows who you are. Part 2 runs it for real, starting with the crowd at the door тАФ throttling and usage plans.

git checkout lesson-07-throttling
тЖР Previousstages deploymentsNext тЖТthrottling

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.