ЁЯОл рдзрдбрд╛ 05 тАФ OAuth 2 рдЖрдгрд┐ OIDC: рд╢рд╛рд│реЗрдЪреНрдпрд╛ account рдиреЗ log in рдХрд░рд╛
ЁЯУН рддреБрдореНрд╣реА рдЗрдереЗ рдЖрд╣рд╛рдд: 16 рдкреИрдХреА рдзрдбрд╛ 05 ┬╖ рдорд╛рдЧреЗ: lesson-04-authentication ┬╖ рдкреБрдвреЗ: lesson-06-authorization
ЁЯУж рдпрд╛ рдмреНрд░рдБрдЪрдордзреНрдпреЗ рдХрд╛рдп рдЖрд╣реЗ
рдзрдбреЗ 01тАУ04, рдЖрдгрд┐ delegated login. рд╢рд╛рд│реЗрдЪреЗ app passwords рдареЗрд╡рдд рдирд╛рд╣реА. рддреЗ user рд▓рд╛
рд╢рд╛рд│реЗрдЪреНрдпрд╛ identity provider рдХрдбреЗ рдкрд╛рдард╡рддреЗ, рдЖрдгрд┐ рдкрд░рдд рдПрдХ ID token (OpenID Connect)
рдЖрдгрд┐ рдПрдХ access token (OAuth 2) рдорд┐рд│рд╡рддреЗ. рддреБрдореНрд╣реА token рдпреЛрдЧреНрдп рдкрджреНрдзрддреАрдиреЗ рддрдкрд╛рд╕рддрд╛ тАФ algorithm, signature,
issuer, audience, expiry тАФ рдЖрдгрд┐ рдкреНрд░рддреНрдпреЗрдХ рдЦреЛрдЯреЗрдкрдгрд╛ fail рд╣реЛрддрд╛рдирд╛ рдкрд╛рд╣рддрд╛. рдордЧ PKCE, рдЬреНрдпрд╛рдореБрд│реЗ рдЪреЛрд░рд▓реЗрд▓рд╛
authorization code рдирд┐рд░реБрдкрдпреЛрдЧреА рд╣реЛрддреЛ. sec/demo.py рдордзрд▓реЗ oauth() рд╣реЗ рджрд╛рдЦрд╡рддреЗ.
ЁЯзТ 5 рд╡рд░реНрд╖рд╛рдВрдЪреНрдпрд╛ рдореБрд▓рд╛рд▓рд╛ рд╕рдордЬрд╛рд╡рд▓реНрдпрд╛рд╕рд╛рд░рдЦреЗ
Library рд▓рд╛ рджреАрдкрд┐рдХрд╛ рдХреЛрдг рдЖрд╣реЗ рддреЗ рдЬрд╛рдгреВрди рдШреНрдпрд╛рдпрдЪреЗ рдЖрд╣реЗ. рддреА рддрд┐рдЪрд╛ рдЧреБрдкреНрдд рд╢рдмреНрдж рд╡рд┐рдЪрд╛рд░рдд рдирд╛рд╣реА тАФ рддреЛ рдлрдХреНрдд рд╢рд╛рд│реЗрдЪреА рдУрд│рдЦрдкрддреНрд░ рдЦрд┐рдбрдХреА рдРрдХреВ рд╢рдХрддреЗ.
рдореНрд╣рдгреВрди library рджреАрдкрд┐рдХрд╛рд▓рд╛ рдУрд│рдЦрдкрддреНрд░ рдЦрд┐рдбрдХреАрдХрдбреЗ рдкрд╛рдард╡рддреЗ. рддреА рдЬрд╛рдгреНрдпрд╛рдЖрдзреА library рдПрдХрд╛ рдХрд╛рдЧрджрд╛рд╡рд░ рдПрдХ рдЧреБрдкреНрдд рд╢рдмреНрдж рд▓рд┐рд╣рд┐рддреЗ, рддреЛ рдЦрд┐рд╢рд╛рдд рдареЗрд╡рддреЗ, рдЖрдгрд┐ рджреАрдкрд┐рдХрд╛рд▓рд╛ рдЦрд┐рдбрдХреАрд╡рд░ рджрд╛рдЦрд╡рдгреНрдпрд╛рд╕рд╛рдареА рдлрдХреНрдд рддреНрдпрд╛ рд╢рдмреНрджрд╛рдЪрд╛ рдард╕рд╛ рджреЗрддреЗ (PKCE challenge).
рдУрд│рдЦрдкрддреНрд░ рдЦрд┐рдбрдХреА рджреАрдкрд┐рдХрд╛рд▓рд╛ рддрдкрд╛рд╕рддреЗ рдЖрдгрд┐ рддрд┐рд▓рд╛ рдПрдХ рдкрд╛рд╡рддреА рджреЗрддреЗ (authorization code). рддреА рддреА library рдХрдбреЗ рдкрд░рдд рдЖрдгрддреЗ. Library рддреА рдкрд╛рд╡рддреА рдУрд│рдЦрдкрддреНрд░ рдЦрд┐рдбрдХреАрдХрдбреЗ рдШреЗрдКрди рдЬрд╛рддреЗ рдЖрдгрд┐ рдЦрд┐рд╢рд╛рддрд▓рд╛ рдЧреБрдкреНрдд рд╢рдмреНрдж рджрд╛рдЦрд╡рддреЗ (verifier). рдЦрд┐рдбрдХреА рддрдкрд╛рд╕рддреЗ: "рдпрд╛ рд╢рдмреНрджрд╛рдкрд╛рд╕реВрди рддреЛ рдард╕рд╛ рдмрдирддреЛ рдХрд╛?" рд╣реЛ тЖТ рдЦрд┐рдбрдХреА рджреАрдкрд┐рдХрд╛рдЪреЗ рдУрд│рдЦрдкрддреНрд░ (tokens) рджреЗрддреЗ.
рд╡рд╛рдЯреЗрдд рдПрдЦрд╛рджреНрдпрд╛ рдЕрдиреЛрд│рдЦреА рдорд╛рдгрд╕рд╛рдиреЗ рдкрд╛рд╡рддреА рдЪреЛрд░рд▓реА рддрд░реА рддреА рдирд┐рд░реБрдкрдпреЛрдЧреА рдЕрд╕рддреЗ тАФ library рдЪреНрдпрд╛ рдЦрд┐рд╢рд╛рддрд▓рд╛ рд╢рдмреНрдж рддреНрдпрд╛рдЪреНрдпрд╛рдХрдбреЗ рдирд╕рддреЛ.
рдЖрдгрд┐ library рдУрд│рдЦрдкрддреНрд░рд╛рд╡рд░ рд╡рд┐рд╢реНрд╡рд╛рд╕ рдареЗрд╡рдгреНрдпрд╛рдЖрдзреА рддреЗ рддрдкрд╛рд╕рддреЗ: рд╢рд╛рд│реЗрдЪрд╛ рд╢рд┐рдХреНрдХрд╛ (signature), рд╢рд╛рд│реЗрдЪреНрдпрд╛ рдУрд│рдЦрдкрддреНрд░ рдЦрд┐рдбрдХреАрдХрдбреВрди (issuer), canteen рд╕рд╛рдареА рдирд╛рд╣реА рддрд░ library рд╕рд╛рдареА (audience), рдЖрдгрд┐ expired рдирд╛рд╣реА.
ЁЯЧ║я╕П рдЖрдХреГрддреА
sequenceDiagram
participant U as ЁЯзТ Dipika's browser
participant A as ЁЯУЪ school app
participant I as ЁЯОл identity provider
A->>A: verifier = random ┬╖ challenge = BASE64URL(SHA256(verifier))
A->>U: redirect to /authorize with response_type=code, code_challenge (S256), state, nonce
U->>I: log in (password + MFA or passkey)
I->>U: redirect back with code and state
U->>A: code + state
A->>I: POST /token: code + code_verifier
I->>I: SHA256(verifier) == challenge?
I->>A: ID token + access token
A->>A: check alg ┬╖ signature ┬╖ iss ┬╖ aud ┬╖ exp ┬╖ nonce
ЁЯЧ║я╕П рдХрд╛рдврд▓реЗрд▓реА рдЖрд╡реГрддреНрддреА + рдПрдХ lab: https://school-edh.pages.dev/security/lesson-diagrams.html#l05
тЭУ рдХрд╛рдп
- OAuth 2.0 тАФ app рд▓рд╛ user рдЪрд╛ password рди рдкрд╛рд╣рддрд╛, user рдЪреНрдпрд╛ рд╡рддреАрдиреЗ API call рдХрд░рдгреНрдпрд╛рд╕рд╛рдареА access token рдорд┐рд│рд╡рдгреНрдпрд╛рдЪреА рдкрджреНрдзрдд. рд╣реЗ access рдмрджреНрджрд▓ рдЖрд╣реЗ.
- OpenID Connect (OIDC) тАФ OAuth 2.0 рд╡рд░рдЪрд╛ рдПрдХ рдерд░ рдЬреЛ ID token рдЬреЛрдбрддреЛ: рдХреЛрдгреА login рдХреЗрд▓реЗ рддреЗ рд╕рд╛рдВрдЧрдгрд╛рд░рд╛ signed JWT. рд╣реЗ login рдмрджреНрджрд▓ рдЖрд╣реЗ.
- Authorization code flow тАФ browser рд▓рд╛ рдлрдХреНрдд рдЕрд▓реНрдкрд╛рдпреБрд╖реА code рдорд┐рд│рддреЛ; app рддреЛ back-channel call рдордзреНрдпреЗ tokens рд╕рд╛рдареА рдмрджрд▓реВрди рдШреЗрддреЗ. рд╣рд╛рдЪ flow рд╡рд╛рдкрд░рд╛рдпрдЪрд╛.
- PKCE (RFC 7636) тАФ "Proof Key for Code Exchange". App рдПрдХ random code verifier
(43тАУ128 рдЕрдХреНрд╖рд░реЗ) рдмрдирд╡рддреЗ,
code_challenge_method=S256рд╕рд╣code_challenge = BASE64URL(SHA256(verifier))рдкрд╛рдард╡рддреЗ, рдЖрдгрд┐ рдирдВрддрд░ verifier рдЖрдкрд▓реНрдпрд╛рдХрдбреЗ рдЖрд╣реЗ рд╣реЗ рд╕рд┐рджреНрдз рдХрд░рддреЗ. рдлрдХреНрдд рдЪреЛрд░рд▓реЗрд▓рд╛ code рд╡рд╛рдкрд░рддрд╛ рдпреЗрдд рдирд╛рд╣реА. рд╣реЗ рдкреНрд░рддреНрдпреЗрдХ client рд╕рд╛рдареА рд╡рд╛рдкрд░рд╛ тАФ browser apps рдЖрдгрд┐ phone apps рд╕рд╛рдареА рдЖрд╡рд╢реНрдпрдХ, рдЖрдгрд┐ server apps рд╕рд╛рдареАрд╣реА рд╢рд┐рдлрд╛рд░рд╕ рдХреЗрд▓реЗрд▓реЗ. - Implicit flow рдирд╛рд╣реА тАФ tokens рдереЗрдЯ URL рдордзреНрдпреЗ рдЯрд╛рдХрдгрд╛рд░рд╛ рдЬреБрдирд╛ flow. OAuth 2.0 Security Best Current Practice (RFC 9700) рддреЛ рд╡рд╛рдкрд░реВ рдирдХрд╛ рдЕрд╕реЗ рд╕рд╛рдВрдЧрддреЗ. рддреНрдпрд╛рдРрд╡рдЬреА code + PKCE рд╡рд╛рдкрд░рд╛.
stateрдЖрдгрд┐nonceтАФstateрдЙрддреНрддрд░рд╛рд▓рд╛ app рдиреЗ рд╕реБрд░реВ рдХреЗрд▓реЗрд▓реНрдпрд╛ request рд╢реА рдЬреЛрдбрддреЛ (рддреЛ login callback рд╡рд░рдЪрд╛ CSRF рдерд╛рдВрдмрд╡рддреЛ).nonceID token рдордзреНрдпреЗ рдЬрд╛рддреЛ; app рддреЛ рдЬреБрд│рддреЛ рдХрд╛ рддреЗ рддрдкрд╛рд╕рддреЗ, рдореНрд╣рдгрдЬреЗ рдЬреБрдирд╛ ID token рдкреБрдиреНрд╣рд╛ рд╡рд╛рдкрд░рддрд╛ (replay) рдпреЗрдд рдирд╛рд╣реА.- ID token рддрдкрд╛рд╕рдгреЗ тАФ рдХреЛрдгрддреНрдпрд╛рд╣реА claim рд╡рд░ рд╡рд┐рд╢реНрд╡рд╛рд╕ рдареЗрд╡рдгреНрдпрд╛рдЖрдзреА:
- algorithm тАФ рдлрдХреНрдд рддреБрдореНрд╣рд╛рд▓рд╛ рдЕрдкреЗрдХреНрд╖рд┐рдд рдЕрд╕рд▓реЗрд▓рд╛ (IdP рдХрдбреВрди RS256 / ES256);
noneрдирд╛рдХрд╛рд░рд╛, - signature тАФ IdP рдиреЗ рдкреНрд░рдХрд╛рд╢рд┐рдд рдХреЗрд▓реЗрд▓реНрдпрд╛ keys (JWKS) рдиреЗ,
issтАФ рддреБрдордЪрд╛ рд╡рд┐рд╢реНрд╡рд╛рд╕ рдЕрд╕рд▓реЗрд▓рд╛ рдЕрдЪреВрдХ issuer,audтАФ рддреБрдордЪрд╛рдЪ client id (рджреБрд╕рд▒реНрдпрд╛ app рд╕рд╛рдареАрдЪрд╛ token рддреБрдордЪреНрдпрд╛рд╕рд╛рдареА рдирд╛рд╣реА),expтАФ expired рдирд╛рд╣реА (рдлрдХреНрдд рдереЛрдбрд╛ clock skew рдЪрд╛рд▓реВ рджреНрдпрд╛),nonceтАФ рддреБрдореНрд╣реА рдкрд╛рдард╡рд▓реЗрд▓реА value.
- algorithm тАФ рдлрдХреНрдд рддреБрдореНрд╣рд╛рд▓рд╛ рдЕрдкреЗрдХреНрд╖рд┐рдд рдЕрд╕рд▓реЗрд▓рд╛ (IdP рдХрдбреВрди RS256 / ES256);
- JWT signed рдЕрд╕рддреЛ, encrypted рдирд╛рд╣реА тАФ рдХреЛрдгреАрд╣реА рддреНрдпрд╛рдЪреЗ claims рд╡рд╛рдЪреВ рд╢рдХрддреЛ (base64url). Signature рдлрдХреНрдд рдХреЛрдгреА рддреЗ рдмрджрд▓рд▓реЗ рдирд╛рд╣реАрдд рд╣реЗ рд╕рд┐рджреНрдз рдХрд░рддреЗ. JWT рдордзреНрдпреЗ рдХрдзреАрд╣реА secrets рдареЗрд╡реВ рдирдХрд╛.
ЁЯдФ рдХрд╛
рдХрд╛рд░рдг рддреБрдореНрд╣реА рд╕реЛрдбрд▓реЗрд▓реА рдкреНрд░рддреНрдпреЗрдХ token рддрдкрд╛рд╕рдгреА рдореНрд╣рдгрдЬреЗ рдПрдХ рджрд╛рд░. Signature рд╕реЛрдбрд▓реЗ рддрд░ рдХреЛрдгреАрд╣реА
"role": "admin" рд▓рд┐рд╣реВ рд╢рдХрддреЛ. aud рд╕реЛрдбрд▓реЗ рддрд░ canteen app рд▓рд╛ рджрд┐рд▓реЗрд▓рд╛ token library рдордзреНрдпреЗ рдЪрд╛рд▓рддреЛ.
exp рд╕реЛрдбрд▓реЗ рддрд░ рдЧреЗрд▓реНрдпрд╛ рд╡рд░реНрд╖реА рдЪреЛрд░рд▓реЗрд▓рд╛ token рдЕрдЬреВрдирд╣реА рдЪрд╛рд▓рддреЛ. alg: none рд╕реНрд╡реАрдХрд╛рд░рд▓реЗ рддрд░
signature рддрдкрд╛рд╕рдгреА рд╣рд▓реНрд▓реЗрдЦреЛрд░рдЪ рдмрдВрдж рдХрд░рддреЛ. рдЖрдгрд┐ PKCE рд╢рд┐рд╡рд╛рдп, рддреНрдпрд╛рдЪ phone рд╡рд░рдЪреНрдпрд╛
рджреБрд╖реНрдЯ app рдиреЗ рдкрдХрдбрд▓реЗрд▓рд╛, рдХрд┐рдВрд╡рд╛ log рдордзреНрдпреЗ leak рдЭрд╛рд▓реЗрд▓рд╛ code, user рдЪреНрдпрд╛ tokens рдордзреНрдпреЗ рдмрджрд▓рддреЛ.
ЁЯФз рдХрд╕реЗ (рдпрд╛ repo рдордзреНрдпреЗ)
sec/identity.py рдордзреНрдпреЗ: make_jwt(claims, secret, alg)
header.payload.signature token рдмрдирд╡рддреЗ (HMAC-SHA256 тАФ shared secret рдореБрд│реЗ lab рдЫреЛрдЯрд╛ рд░рд╛рд╣рддреЛ;
рдЦрд░реЗ IdPs private key рдиреЗ sign рдХрд░рддрд╛рдд рдЖрдгрд┐ public keys рдкреНрд░рдХрд╛рд╢рд┐рдд рдХрд░рддрд╛рдд). verify_jwt(token, secret, issuer, audience, now) рдпрд╛ рдХреНрд░рдорд╛рдиреЗ рддрдкрд╛рд╕рддреЗ: algorithm HS256 рдЪ рдЕрд╕рд▓рд╛ рдкрд╛рд╣рд┐рдЬреЗ, signature, iss,
aud, рдордЧ exp. pkce_pair(verifier) S256 challenge рдкрд░рдд рджреЗрддреЗ; pkce_ok(challenge, verifier) рддреЛ рдкреБрдиреНрд╣рд╛ рдЧрдгрди рдХрд░рддреЗ. Lab рдордзреНрдпреЗ nonce рдЪреЗ model рдирд╛рд╣реА тАФ рдкреНрд░рддреНрдпрдХреНрд╖ рд╡рд╛рдкрд░рд╛рдд рдкрд╛рд╣рд╛.
ЁЯзк рдХрд░реВрди рдкрд╛рд╣рд╛
python3 sec/demo.py oauth
python3 - <<'EOF'
import sys, base64, json; sys.path.insert(0, "sec"); from identity import make_jwt, verify_jwt, pkce_pair, pkce_ok
NOW, KEY, ISS = 1_790_000_000, "school-idp-secret", "https://login.school.example"
tok = make_jwt(dict(iss=ISS, aud="school-app", sub="dipika", role="parent", exp=NOW + 300), KEY)
h, p, s = tok.split(".")
claims = json.loads(base64.urlsafe_b64decode(p + "=" * (-len(p) % 4)))
print("anyone can READ the claims:", claims)
claims["role"] = "admin"
p2 = base64.urlsafe_b64encode(json.dumps(claims).encode()).rstrip(b"=").decode()
print("role changed to admin тЖТ", verify_jwt(f"{h}.{p2}.{s}", KEY, ISS, "school-app", NOW)[1])
other = make_jwt(dict(iss="https://login.other.example", aud="school-app", exp=NOW + 300), KEY)
print("another issuer тЖТ", verify_jwt(other, KEY, ISS, "school-app", NOW)[1])
print("five minutes later тЖТ", verify_jwt(tok, KEY, ISS, "school-app", NOW + 300)[1])
v, c = pkce_pair("dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk")
print("challenge (S256) тЖТ", c)
print("same as RFC 7636, Appendix B тЖТ", c == "E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM")
print("right verifier тЖТ", pkce_ok(c, v), "┬╖ sending the challenge back тЖТ", pkce_ok(c, c))
EOF
тЬЕ рддрдкрд╛рд╕рд╛ тАФ рддреБрдореНрд╣рд╛рд▓рд╛ рдХрд╛рдп рджрд┐рд╕рд╛рдпрд▓рд╛ рд╣рд╡реЗ
oauth рд╣реЗ print рдХрд░рддреЗ:
тФАтФА the ID token the school login gives the app (OIDC) тАФ checked before trusting any claim:
good тЖТ ok
forged тЖТ bad signature
alg none тЖТ algorithm 'none' refused
other app тЖТ wrong audience
expired тЖТ expired
тФАтФА PKCE: the app sends challenge E9Melhoa2OwvFrEMтАж ┬╖ later proves it with the verifier тЖТ True ┬╖ a thief with only the code тЖТ False
authorization code + PKCE for browsers and phones ┬╖ never the implicit flow ┬╖ tokens short-lived
рддреБрдордЪрд╛ snippet рд╣реЗ print рдХрд░рддреЛ:
anyone can READ the claims: {'iss': 'https://login.school.example', 'aud': 'school-app', 'sub': 'dipika', 'role': 'parent', 'exp': 1790000300}
role changed to admin тЖТ bad signature
another issuer тЖТ wrong issuer
five minutes later тЖТ expired
challenge (S256) тЖТ E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM
same as RFC 7636, Appendix B тЖТ True
right verifier тЖТ True ┬╖ sending the challenge back тЖТ False
ЁЯПБ рддреБрдореНрд╣реА рдЖрддреНрддрд╛рдЪ рдХрд╛рдп рд╕рд┐рджреНрдз рдХреЗрд▓реЗ
JWT рдордзрд▓реЗ claims рдХреЛрдгреАрд╣реА рд╡рд╛рдЪреВ рд╢рдХрддреЛ тАФ рдлрдХреНрдд signature рддреНрдпрд╛рдВрдЪреЗ рд░рдХреНрд╖рдг рдХрд░рддреЗ, рдЖрдгрд┐ рдПрдХ
рдмрджрд▓рд▓реЗрд▓рд╛ claim рддреЗ рдореЛрдбрддреЛ. рдкреНрд░рддреНрдпреЗрдХ рддрдкрд╛рд╕рдгреА рд╡реЗрдЧрд│рд╛ рдЦреЛрдЯреЗрдкрдгрд╛ рдирд╛рдХрд╛рд░рддреЗ: alg none, рджреБрд╕рд▒реНрдпрд╛ app рдЪрд╛
token, рджреБрд╕рд▒реНрдпрд╛ issuer рдЪрд╛ token, рдЬреБрдирд╛ token. Lab рдЪрд╛ PKCE challenge RFC 7636 рдордзрд▓реНрдпрд╛ рдЙрджрд╛рд╣рд░рдгрд╛рд╢реА
рдЕрдЧрджреА рдЬреБрд│рддреЛ. рдЖрдгрд┐ challenge рдЙрдШрдбрдкрдгреЗ рдкреНрд░рд╡рд╛рд╕ рдХрд░рддреЛ; рдлрдХреНрдд verifier рдЪ
code рд╡рд╛рдкрд░реВ рд╢рдХрддреЛ.
тЪая╕П рдиреЗрд╣рдореАрдЪреНрдпрд╛ рдЪреБрдХрд╛
- JWT decode рдХрд░реВрди рддреЛ verify рди рдХрд░рддрд╛ claims рд╡рд╛рдкрд░рдгреЗ (
verify_signature=False) - token рдЪреНрдпрд╛ header рд▓рд╛ algorithm рдирд┐рд╡рдбреВ рджреЗрдгреЗ тАФ рдпрд╛рджреА рдирд┐рд╢реНрдЪрд┐рдд рдХрд░рд╛ (
algorithms=["RS256"]) audрд╕реЛрдбрдгреЗ, рддреНрдпрд╛рдореБрд│реЗ рддреНрдпрд╛рдЪ IdP рдЪрд╛ рдХреЛрдгрддрд╛рд╣реА token рд╕реНрд╡реАрдХрд╛рд░рд▓рд╛ рдЬрд╛рддреЛ- рдирд╡реНрдпрд╛ app рдордзреНрдпреЗ implicit flow, рдХрд┐рдВрд╡рд╛ "password grant"
state/nonceрдирд╛рд╣реА, рдХрд┐рдВрд╡рд╛ IdP рд╕реИрд▓рдкрдгреЗ рдЬреБрд│рд╡рддреЛ рдЕрд╕рд╛redirect_uri- рджрд┐рд╡рд╕рднрд░ рдЯрд┐рдХрдгрд╛рд░реЗ access tokens, XSS рд╡рд╛рдЪреВ рд╢рдХреЗрд▓ рдЕрд╢рд╛
localStorageрдордзреНрдпреЗ рдареЗрд╡рд▓реЗрд▓реЗ - login рдЪрд╛ рдкреБрд░рд╛рд╡рд╛ рдореНрд╣рдгреВрди access token рд╡рд╛рдкрд░рдгреЗ тАФ рддреЗ ID token рдЪреЗ рдХрд╛рдо рдЖрд╣реЗ
ЁЯПн рдкреНрд░рддреНрдпрдХреНрд╖ рд╡рд╛рдкрд░рд╛рдд
рдЦрд▒реНрдпрд╛ account рд╡рд░ тАФ authorization code flow рдЖрдгрд┐ PKCE рд╕рд╣ OIDC client (Python,
Authlib + Flask). рддреЛ IdP рдЪреЗ discovery document рд╡рд╛рдЪрддреЛ, state рдЖрдгрд┐ nonce рдкрд╛рдард╡рддреЛ, рдЖрдгрд┐
ID token рддрдкрд╛рд╕рддреЛ:
from authlib.integrations.flask_client import OAuth
oauth = OAuth(app)
oauth.register(
name="school",
server_metadata_url="https://login.school.example/.well-known/openid-configuration",
client_id=os.environ["OIDC_CLIENT_ID"], client_secret=os.environ["OIDC_CLIENT_SECRET"],
client_kwargs={"scope": "openid email profile", "code_challenge_method": "S256"},
)
@app.route("/login")
def login():
return oauth.school.authorize_redirect(url_for("callback", _external=True))
@app.route("/callback")
def callback():
token = oauth.school.authorize_access_token() # checks state, swaps code + verifier, validates the ID token and nonce
session.clear(); session["user"] = token["userinfo"]["sub"]
return redirect("/")
Token рд╕реНрд╡рддрдГ verify рдХрд░рдгреЗ (access tokens рдШреЗрдгрд╛рд░реА API), PyJWT рдЖрдгрд┐ IdP рдЪреНрдпрд╛ JWKS рд╕рд╣:
import jwt
jwks = jwt.PyJWKClient("https://login.school.example/.well-known/jwks.json")
key = jwks.get_signing_key_from_jwt(token).key
claims = jwt.decode(token, key, algorithms=["RS256"], # pinned тАФ never from the header
audience="school-api", issuer="https://login.school.example",
leeway=30, options={"require": ["exp", "iss", "aud", "sub"]})
Browser app (single-page app) рдордзреНрдпреЗ oidc-client-ts рд╕рд╛рд░рдЦреА certified library рд╡рд╛рдкрд░рд╛,
response_type: "code" рд╕рд╣ тАФ PKCE default рдиреЗ рдЪрд╛рд▓реВ рдЕрд╕рддреЗ тАФ рдЖрдгрд┐ tokens рдЕрд▓реНрдкрд╛рдпреБрд╖реА рдареЗрд╡рд╛.
ЁЯПн рдкреНрд░рддреНрдпрдХреНрд╖ рд╡рд╛рдкрд░рд╛рдд рд╣реЗ рдХрд╛ рдорд╣рддреНрддреНрд╡рд╛рдЪреЗ: managed identity provider (Amazon Cognito, Microsoft Entra ID, Okta, Auth0, Keycloak) рдЖрдгрд┐ certified client library рд╡рд╛рдкрд░рд╛. рдЕрдЪреВрдХ redirect URIs register рдХрд░рд╛. Access tokens рд▓рд╣рд╛рди (minutes) рдареЗрд╡рд╛, refresh-token rotation рд╡рд╛рдкрд░рд╛, рдЖрдгрд┐ рдпрд╛ рдзрдбреНрдпрд╛рддрд▓реА рдкреНрд░рддреНрдпреЗрдХ рддрдкрд╛рд╕рдгреА library рд▓рд╛ рдХрд░реВ рджреНрдпрд╛ тАФ рдордЧ рджреБрд╕рд▒реНрдпрд╛ audience рд╕рд╛рдареАрдЪрд╛ token рдирд╛рдХрд╛рд░рд▓рд╛ рдЬрд╛рддреЛ рдХрд╛ рддреЗ test рдХрд░рд╛.
тПня╕П рдкреБрдвреЗ
Token рд╕рд╛рдВрдЧрддреЛ рджреАрдкрд┐рдХрд╛ рдХреЛрдг рдЖрд╣реЗ. рддреА рдХреЛрдгрддреЗ records рдЙрдШрдбреВ рд╢рдХрддреЗ рддреЗ рддреЛ рд╕рд╛рдВрдЧрдд рдирд╛рд╣реА. рдкреБрдвреЗ: рдмрд╣реБрддреЗрдХ APIs рд╡рд┐рд╕рд░рддрд╛рдд рддреА authorization рддрдкрд╛рд╕рдгреА.
git checkout lesson-06-authorization