Authentication и Authorization: JWT, OAuth, RBAC, OPA
Зачем знать: Auth — это первая линия защиты приложения. Middle 2 Go-разработчик должен понимать различия между authentication (кто ты?) и authorization (что можно?), уметь правильно интегрировать JWT/OAuth, выбирать между session-based и token-based, реализовывать RBAC/ABAC. В 2026 году стандарт — OAuth 2.0 + PKCE + OIDC для user-facing, mTLS / API keys для service-to-service. Без понимания этого ты не сможешь интегрировать с любой современной identity infrastructure (Auth0, Keycloak, AWS Cognito).
Содержание
Заголовок раздела «Содержание»- Концепция: Authentication, Authorization, identity
- Production-практики: JWT, OAuth, RBAC, OPA
- Gotchas: JWT pitfalls, refresh token race, password storage
- Real cases: Auth0, Okta, Keycloak, Zanzibar
- Вопросы для собеседования
- Practice
- Источники
1. Концепция: Authentication, Authorization, identity
Заголовок раздела «1. Концепция: Authentication, Authorization, identity»1.1 AuthN vs AuthZ
Заголовок раздела «1.1 AuthN vs AuthZ»Authentication (AuthN) — “кто ты?”. Verify identity. Authorization (AuthZ) — “что тебе можно?”. Verify permissions.
Пример:
- AuthN: проверяем password user’a → знаем, что это Alice.
- AuthZ: знаем, что Alice — admin → разрешаем delete users.
1.2 Session-based vs Token-based
Заголовок раздела «1.2 Session-based vs Token-based»Session-based:
- Server создаёт session, хранит state.
- Client получает session ID в cookie.
- Каждый request — server lookup session.
- Easy revocation (delete session).
- Tightly coupled с server (sticky session или shared store).
Token-based (JWT):
- Stateless — токен содержит все claims.
- Cryptographically signed.
- Server verify signature, не хранит state.
- Hard revocation (нужны blacklist или short TTL).
- Easy для distributed / multi-service.
Когда что:
| Use case | Choice |
|---|---|
| Monolith with sticky session | Session |
| Microservices, multi-region | JWT |
| Mobile app | JWT (refresh token) |
| SPA + backend | JWT or session с CORS |
| High security (banking) | Session (revocation control) |
1.3 JWT (JSON Web Token)
Заголовок раздела «1.3 JWT (JSON Web Token)»JWT — token формата header.payload.signature, encoded в base64.
Header:
{"alg": "RS256", "typ": "JWT", "kid": "abc123"}Payload (claims):
{ "iss": "https://auth.example.com", "sub": "user_123", "aud": "api.example.com", "exp": 1735689600, "iat": 1735685000, "nbf": 1735685000, "jti": "unique-token-id", "scope": "read:orders write:orders", "custom_claim": "value"}Signature: HMAC-SHA256(base64url(header) + '.' + base64url(payload), secret) или RSA-SHA256 с private key.
Standard claims (RFC 7519):
iss(issuer) — кто выдал.sub(subject) — кому (обычно user_id).aud(audience) — для кого (target service).exp(expiration) — когда истекает.iat(issued at) — когда выдан.nbf(not before) — не использовать до.jti(JWT ID) — unique ID (для revocation).
1.4 JWT algorithms
Заголовок раздела «1.4 JWT algorithms»HS256 (HMAC-SHA256) — symmetric.
- Same secret для sign и verify.
- Used когда issuer == verifier (один сервис).
- НЕ для multi-service (все знают secret = слабо).
RS256 (RSA-SHA256) — asymmetric.
- Private key sign, public key verify.
- Issuer hold private key, services hold public.
- Standard для multi-service / multi-team.
ES256 (ECDSA-SHA256) — asymmetric, smaller signature.
- Modern, recommended.
EdDSA (Ed25519) — faster, smaller.
alg=none — НИКОГДА. Атаки в прошлом.
1.5 JWT в Go: golang-jwt/jwt/v5
Заголовок раздела «1.5 JWT в Go: golang-jwt/jwt/v5»import "github.com/golang-jwt/jwt/v5"
// Signclaims := jwt.MapClaims{ "sub": "user_123", "exp": time.Now().Add(15 * time.Minute).Unix(), "iat": time.Now().Unix(),}token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)tokenString, err := token.SignedString([]byte("secret"))
// Verifyparsed, err := jwt.Parse(tokenString, func(t *jwt.Token) (interface{}, error) { if _, ok := t.Method.(*jwt.SigningMethodHMAC); !ok { return nil, fmt.Errorf("unexpected method: %v", t.Header["alg"]) } return []byte("secret"), nil})if claims, ok := parsed.Claims.(jwt.MapClaims); ok && parsed.Valid { sub := claims["sub"].(string)}Важно: ВСЕГДА проверять alg в callback — иначе можно подменить через alg=none.
1.6 Refresh tokens
Заголовок раздела «1.6 Refresh tokens»Access token — short-lived (5-15 мин), для API calls. Refresh token — long-lived (7-30 дней), для получения нового access token.
Flow:
- Login → получаем access + refresh.
- Используем access для API (15 мин).
- Access expired → POST /refresh с refresh token → новый access.
- Refresh может rotate’аться (new refresh каждый раз).
Refresh token rotation:
- Каждый refresh → новый access AND новый refresh.
- Old refresh invalidated.
- Замечают replay → revoke всё (вероятно stolen).
1.7 OAuth 2.0 flows
Заголовок раздела «1.7 OAuth 2.0 flows»OAuth 2.0 — framework для delegated authorization. “User даёт app доступ к своим resources без передачи password”.
Roles:
- Resource Owner — user.
- Client — application (web, mobile).
- Authorization Server — выдаёт tokens.
- Resource Server — defends API.
Authorization Code Flow (web apps):
1. User → Client: hits "Sign in"2. Client → AuthZ Server: redirect with client_id, scope, redirect_uri3. AuthZ Server → User: login page4. User → AuthZ Server: credentials5. AuthZ Server → Client: redirect with code6. Client → AuthZ Server: POST code + client_secret7. AuthZ Server → Client: access_token + refresh_token8. Client → Resource Server: API call с access_tokenPKCE (Proof Key for Code Exchange) — required для public clients (mobile, SPA):
- Client generates
code_verifier(random). - Sends
code_challenge= SHA256(verifier). - При exchange: sends
code_verifier. - Auth server verifies challenge.
- Защищает от code interception.
В 2025 RFC 9700 рекомендует PKCE для ВСЕХ clients.
Client Credentials Flow (service-to-service):
- No user involved.
- Client → AuthZ: client_id + client_secret + grant_type=client_credentials.
- Returns access_token.
Implicit Flow (DEPRECATED) — token прямо в URL, security issues.
Resource Owner Password Credentials (DEPRECATED) — user даёт password client’у, не safe.
1.8 OpenID Connect (OIDC)
Заголовок раздела «1.8 OpenID Connect (OIDC)»OIDC = OAuth 2.0 + identity layer.
OAuth даёт access token для API.
OIDC ещё даёт id_token (JWT с user info).
Discovery endpoint: /.well-known/openid-configuration.
{ "issuer": "https://auth.example.com", "authorization_endpoint": "https://auth.example.com/authorize", "token_endpoint": "https://auth.example.com/token", "userinfo_endpoint": "https://auth.example.com/userinfo", "jwks_uri": "https://auth.example.com/jwks.json"}JWKS endpoint — public keys для verify JWTs.
1.9 Identity Providers
Заголовок раздела «1.9 Identity Providers»Cloud:
- Auth0 (Okta acquired) — easy, дорого.
- Okta — enterprise.
- AWS Cognito — AWS-native.
- Google Identity / Sign-In with Google.
- Azure AD B2C.
Self-hosted:
- Keycloak (Red Hat) — full-featured, в production отлично.
- ORY Hydra/Kratos — Go-native, microservices.
- Authentik — modern OSS.
- Dex — OIDC connector.
1.10 SSO (Single Sign-On)
Заголовок раздела «1.10 SSO (Single Sign-On)»SSO — один login → доступ ко всем приложениям.
Protocols:
- SAML 2.0 — enterprise standard (XML, legacy).
- OIDC — modern (JSON, REST).
В 2026 — OIDC доминирует, SAML только legacy enterprise.
1.11 2FA / MFA
Заголовок раздела «1.11 2FA / MFA»TOTP (Time-based One-Time Password) — Google Authenticator, Authy.
- Shared secret + current time → 6-digit code.
- Library:
github.com/pquerna/otp.
import "github.com/pquerna/otp/totp"
// Generate secretkey, _ := totp.Generate(totp.GenerateOpts{ Issuer: "Example", AccountName: "alice@example.com",})secret := key.Secret() // store
// Verifyvalid := totp.Validate(userInput, secret)WebAuthn / Passkeys — modern, hardware-based.
- FIDO2 standard.
- Phishing-resistant.
- Microsoft, Google, Apple support.
- Library:
github.com/go-webauthn/webauthn.
SMS OTP — устарел, vulnerable to SIM-swap.
1.12 Authorization модели
Заголовок раздела «1.12 Authorization модели»RBAC (Role-Based Access Control):
- User → Roles → Permissions.
- Simple, fits 80% случаев.
- Гибкость низкая.
User: AliceRole: editorPermissions: [read:article, write:article, delete:own_article]ABAC (Attribute-Based Access Control):
- Policy на основе attributes (user, resource, context).
- Гибкость высокая, complexity высокая.
Policy: User can edit if: - user.department == resource.department - AND time within work hours - AND user.clearance >= resource.classificationPBAC (Policy-Based) — generalization ABAC, policy as code.
ReBAC (Relationship-Based) — Google Zanzibar paper.
- Permissions через graph relationships.
- “User can edit doc if user.is_member_of(doc.owner_team)“.
1.13 Casbin
Заголовок раздела «1.13 Casbin»Casbin — popular Go library для AuthZ. Supports RBAC, ABAC, REST.
import "github.com/casbin/casbin/v2"
e, _ := casbin.NewEnforcer("model.conf", "policy.csv")
// Checkok, _ := e.Enforce("alice", "/articles/123", "read")model.conf:
[request_definition]r = sub, obj, act
[policy_definition]p = sub, obj, act
[role_definition]g = _, _
[policy_effect]e = some(where (p.eft == allow))
[matchers]m = g(r.sub, p.sub) && keyMatch(r.obj, p.obj) && r.act == p.actpolicy.csv:
p, admin, /*, *p, editor, /articles/*, readp, editor, /articles/*, writeg, alice, editorg, bob, admin1.14 OPA (Open Policy Agent)
Заголовок раздела «1.14 OPA (Open Policy Agent)»OPA — CNCF проект, centralized authorization. Policy language — Rego.
package authz
default allow := false
allow if { input.method == "GET" input.path == ["articles", _] input.user.role == "reader"}
allow if { input.user.role == "admin"}Deployment:
- Sidecar — каждый pod имеет OPA, query localhost.
- Library — Go SDK для in-process.
- Centralized — single service.
Используется:
- Kubernetes admission controllers (Gatekeeper).
- Istio AuthZ.
- Enterprise applications.
1.15 Zanzibar / SpiceDB
Заголовок раздела «1.15 Zanzibar / SpiceDB»Google Zanzibar — paper (2019) о graph-based authorization для Google services (Docs, Drive).
Concepts:
- Objects (documents, folders, teams).
- Relations (member, owner, viewer).
- Permissions (read, write, delete).
group:eng#member@user:alice # alice is member of eng groupdocument:doc1#viewer@group:eng#member # eng members can view doc1→ Alice can view doc1SpiceDB (Authzed) — open-source реализация Zanzibar.
1.16 Password hashing
Заголовок раздела «1.16 Password hashing»Recommendations 2026 (OWASP):
- Argon2id — winner Password Hashing Competition.
- bcrypt — classic, safe.
- scrypt — alternative.
НЕ ИСПОЛЬЗОВАТЬ: MD5, SHA1, SHA256, SHA512 (слишком быстрые → brute force).
Argon2id:
import "golang.org/x/crypto/argon2"
salt := make([]byte, 16)rand.Read(salt)
// Memory: 64 MiB, Time: 3 iterations, Parallelism: 4hash := argon2.IDKey([]byte(password), salt, 3, 64*1024, 4, 32)bcrypt:
import "golang.org/x/crypto/bcrypt"
hash, _ := bcrypt.GenerateFromPassword([]byte(password), 12)err := bcrypt.CompareHashAndPassword(hash, []byte(input))Cost factor — настройте так, чтобы hash занимал ~250ms на вашем железе.
2. Production-практики
Заголовок раздела «2. Production-практики»2.1 Полный auth flow с JWT
Заголовок раздела «2.1 Полный auth flow с JWT»// Loginfunc (s *Server) Login(w http.ResponseWriter, r *http.Request) { var req LoginReq json.NewDecoder(r.Body).Decode(&req)
user, err := s.db.GetUserByEmail(req.Email) if err != nil { http.Error(w, "invalid credentials", 401) return }
if bcrypt.CompareHashAndPassword(user.PasswordHash, []byte(req.Password)) != nil { http.Error(w, "invalid credentials", 401) return }
access := s.generateAccessToken(user) refresh := s.generateRefreshToken(user)
s.db.StoreRefreshToken(user.ID, refresh)
json.NewEncoder(w).Encode(LoginResp{ AccessToken: access, RefreshToken: refresh, ExpiresIn: 900, })}
func (s *Server) generateAccessToken(u User) string { claims := jwt.MapClaims{ "sub": u.ID, "email": u.Email, "role": u.Role, "exp": time.Now().Add(15 * time.Minute).Unix(), "iat": time.Now().Unix(), "iss": "myapp", } token := jwt.NewWithClaims(jwt.SigningMethodRS256, claims) signed, _ := token.SignedString(s.privateKey) return signed}
// Middlewarefunc (s *Server) AuthMiddleware(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { tokenStr := strings.TrimPrefix(r.Header.Get("Authorization"), "Bearer ") if tokenStr == "" { http.Error(w, "missing token", 401) return }
token, err := jwt.Parse(tokenStr, func(t *jwt.Token) (interface{}, error) { if _, ok := t.Method.(*jwt.SigningMethodRSA); !ok { return nil, fmt.Errorf("unexpected method") } return s.publicKey, nil })
if err != nil || !token.Valid { http.Error(w, "invalid token", 401) return }
claims := token.Claims.(jwt.MapClaims) ctx := context.WithValue(r.Context(), userIDKey, claims["sub"].(string)) ctx = context.WithValue(ctx, userRoleKey, claims["role"].(string))
next.ServeHTTP(w, r.WithContext(ctx)) })}
// Refreshfunc (s *Server) Refresh(w http.ResponseWriter, r *http.Request) { var req RefreshReq json.NewDecoder(r.Body).Decode(&req)
userID, err := s.db.ValidateRefreshToken(req.RefreshToken) if err != nil { // Token reuse detection — security alert! s.db.RevokeAllUserTokens(userID) http.Error(w, "invalid token", 401) return }
// Rotate s.db.RevokeRefreshToken(req.RefreshToken) newRefresh := s.generateRefreshToken(user) s.db.StoreRefreshToken(user.ID, newRefresh)
newAccess := s.generateAccessToken(user)
json.NewEncoder(w).Encode(RefreshResp{ AccessToken: newAccess, RefreshToken: newRefresh, })}2.2 OAuth2 client в Go
Заголовок раздела «2.2 OAuth2 client в Go»import "golang.org/x/oauth2"
config := &oauth2.Config{ ClientID: "client_id", ClientSecret: "client_secret", RedirectURL: "https://myapp.com/callback", Scopes: []string{"openid", "profile", "email"}, Endpoint: oauth2.Endpoint{ AuthURL: "https://auth.example.com/oauth/authorize", TokenURL: "https://auth.example.com/oauth/token", },}
// Generate auth URLstate := generateRandomState()url := config.AuthCodeURL(state, oauth2.AccessTypeOffline)// Redirect user to url
// Callbackfunc callback(w http.ResponseWriter, r *http.Request) { state := r.URL.Query().Get("state") if !validateState(state) { /* error */ }
code := r.URL.Query().Get("code") token, err := config.Exchange(r.Context(), code) if err != nil { /* error */ }
// Use token client := config.Client(r.Context(), token) resp, _ := client.Get("https://api.example.com/me")}2.3 OIDC client
Заголовок раздела «2.3 OIDC client»import ( "github.com/coreos/go-oidc/v3/oidc" "golang.org/x/oauth2")
provider, err := oidc.NewProvider(ctx, "https://auth.example.com")// Discovery endpoint автоматически прочитан
config := &oauth2.Config{ ClientID: "...", ClientSecret: "...", Endpoint: provider.Endpoint(), RedirectURL: "https://myapp.com/callback", Scopes: []string{oidc.ScopeOpenID, "profile", "email"},}
verifier := provider.Verifier(&oidc.Config{ClientID: "..."})
// Callbackoauth2Token, _ := config.Exchange(ctx, code)rawIDToken, _ := oauth2Token.Extra("id_token").(string)idToken, _ := verifier.Verify(ctx, rawIDToken)
var claims struct { Email string `json:"email"` Name string `json:"name"`}idToken.Claims(&claims)2.4 RBAC implementation
Заголовок раздела «2.4 RBAC implementation»type Role string
const ( RoleUser Role = "user" RoleAdmin Role = "admin" RoleSuper Role = "super")
type Permission string
const ( PermReadArticle Permission = "read:article" PermWriteArticle Permission = "write:article")
var rolePermissions = map[Role][]Permission{ RoleUser: {PermReadArticle}, RoleAdmin: {PermReadArticle, PermWriteArticle}, RoleSuper: {PermReadArticle, PermWriteArticle, "delete:any"},}
func HasPermission(role Role, perm Permission) bool { for _, p := range rolePermissions[role] { if p == perm { return true } } return false}
// Middlewarefunc RequirePermission(perm Permission) func(http.Handler) http.Handler { return func(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { role := r.Context().Value(userRoleKey).(Role) if !HasPermission(role, perm) { http.Error(w, "forbidden", 403) return } next.ServeHTTP(w, r) }) }}2.5 Casbin enforcer
Заголовок раздела «2.5 Casbin enforcer»e, _ := casbin.NewEnforcer("model.conf", "policy.csv")
// Watch policy file (auto-reload)w, _ := filewatcher.NewWatcher("policy.csv")e.SetWatcher(w)
func authMiddleware(e *casbin.Enforcer) func(http.Handler) http.Handler { return func(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { user := r.Context().Value(userIDKey).(string) ok, _ := e.Enforce(user, r.URL.Path, r.Method) if !ok { http.Error(w, "forbidden", 403) return } next.ServeHTTP(w, r) }) }}2.6 OPA sidecar pattern
Заголовок раздела «2.6 OPA sidecar pattern»import "github.com/open-policy-agent/opa/sdk"
opaSDK, _ := sdk.New(ctx, sdk.Options{ Config: bytes.NewReader([]byte(`services: - name: bundles-service url: https://opa.example.combundles: authz: service: bundles-service`)),})
// Decisiondecision, _ := opaSDK.Decision(ctx, sdk.DecisionOptions{ Path: "/authz/allow", Input: map[string]interface{}{ "user": user, "method": r.Method, "path": r.URL.Path, },})
if !decision.Result.(bool) { http.Error(w, "forbidden", 403)}2.7 Storing refresh tokens
Заголовок раздела «2.7 Storing refresh tokens»CREATE TABLE refresh_tokens ( token_hash CHAR(64) PRIMARY KEY, -- SHA256 of token user_id BIGINT NOT NULL, expires_at TIMESTAMP NOT NULL, created_at TIMESTAMP NOT NULL DEFAULT NOW(), revoked_at TIMESTAMP NULL, INDEX (user_id), INDEX (expires_at));Store HASH, не plain token! Если DB leaked — tokens не использовать.
2.8 mTLS для service-to-service
Заголовок раздела «2.8 mTLS для service-to-service»// ServertlsConfig := &tls.Config{ ClientAuth: tls.RequireAndVerifyClientCert, ClientCAs: loadCAs("ca.crt"),}srv := &http.Server{TLSConfig: tlsConfig}
// ClientclientCert, _ := tls.LoadX509KeyPair("client.crt", "client.key")client := &http.Client{ Transport: &http.Transport{ TLSClientConfig: &tls.Config{ Certificates: []tls.Certificate{clientCert}, RootCAs: loadCAs("ca.crt"), }, },}В k8s через Istio / Linkerd — автоматически.
2.9 API keys
Заголовок раздела «2.9 API keys»Simple для service-to-service:
func apiKeyMiddleware(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { key := r.Header.Get("X-API-Key") if key == "" { http.Error(w, "missing api key", 401) return }
// Hash before lookup (timing-safe DB query) hash := sha256.Sum256([]byte(key)) info, err := db.GetAPIKey(hex.EncodeToString(hash[:])) if err != nil { http.Error(w, "invalid key", 401) return }
if info.Revoked || info.ExpiresAt.Before(time.Now()) { http.Error(w, "invalid key", 401) return }
ctx := context.WithValue(r.Context(), "tenant", info.TenantID) next.ServeHTTP(w, r.WithContext(ctx)) })}2.10 Session-based с Redis
Заголовок раздела «2.10 Session-based с Redis»import "github.com/gorilla/sessions"
store := sessions.NewCookieStore([]byte("32-byte-secret"))// Или Redis-backed: github.com/boj/redistore
func login(w http.ResponseWriter, r *http.Request) { session, _ := store.Get(r, "auth") session.Values["user_id"] = user.ID session.Options = &sessions.Options{ Path: "/", MaxAge: 3600, HttpOnly: true, Secure: true, SameSite: http.SameSiteStrictMode, } session.Save(r, w)}
func protected(w http.ResponseWriter, r *http.Request) { session, _ := store.Get(r, "auth") userID, ok := session.Values["user_id"] if !ok { http.Error(w, "unauthorized", 401) return } // ...}3. Gotchas
Заголовок раздела «3. Gotchas»3.1 ⚠️ alg=none attack
Заголовок раздела «3.1 ⚠️ alg=none attack»JWT с "alg": "none" означает signature не verified. Атакующий может подделать любой токен. Защита: ALWAYS verify alg в parse callback:
jwt.Parse(token, func(t *jwt.Token) (interface{}, error) { if _, ok := t.Method.(*jwt.SigningMethodRSA); !ok { return nil, fmt.Errorf("unexpected method: %v", t.Header["alg"]) } return publicKey, nil})3.2 ⚠️ Key confusion (HS256 vs RS256)
Заголовок раздела «3.2 ⚠️ Key confusion (HS256 vs RS256)»Если verifier ожидает RS256 (asymmetric), но не проверяет alg, attacker может:
- Подать JWT с alg=HS256 и signature, sign’нутой публичным ключом сервера (доступен).
- Проверить как HMAC с тем же public key → pass.
Защита: проверь alg ИЛИ используй library, которая разделяет HMAC и RSA verify.
3.3 ⚠️ JWT в URL
Заголовок раздела «3.3 ⚠️ JWT в URL»https://app.com/page?token=eyJ...Plain text в:
- Browser history.
- Referer headers.
- Server access logs.
Always в Authorization header или secure cookie.
3.4 ⚠️ Long-lived access tokens без revocation
Заголовок раздела «3.4 ⚠️ Long-lived access tokens без revocation»JWT stateless — нельзя revoke до expiration. Если access token живёт 24h, и telegram’нули user — он всё ещё имеет доступ 24h.
Решения:
- Short TTL (5-15 мин) + refresh token (revokable).
- Token blacklist (jti в Redis).
- Hybrid (JWT с быстрой DB lookup для critical actions).
3.5 ⚠️ Refresh token race
Заголовок раздела «3.5 ⚠️ Refresh token race»Mobile app делает 2 параллельных request → один refresh’нул, второй с старым refresh. Если просто reject — UX страдает.
Решение:
- Grace period — старый refresh valid ещё 30 секунд после rotation.
- Lock per refresh token.
3.6 ⚠️ Cookie без SameSite
Заголовок раздела «3.6 ⚠️ Cookie без SameSite»Cookie без SameSite=Strict или Lax → CSRF possible. Modern browsers default’ом Lax, но prove explicitly.
3.7 ⚠️ Password timing attack
Заголовок раздела «3.7 ⚠️ Password timing attack»// УЯЗВИМО (хотя bcrypt константный)if user.Password == hash(input) { ... }
// Bcrypt — constant-time, но check existence:user, err := db.GetUserByEmail(email)if err != nil { // User not found // Fast return → attacker timing-distinguishes valid emails return errors.New("invalid")}if bcrypt.CompareHashAndPassword(user.Hash, []byte(input)) != nil { // Slow (bcrypt verify) return errors.New("invalid")}Решение: всегда run bcrypt:
user, _ := db.GetUserByEmail(email)hashToCheck := []byte("$2a$12$dummyhash...")if user != nil { hashToCheck = user.Hash }bcrypt.CompareHashAndPassword(hashToCheck, []byte(input))// + check user != nil for actual auth3.8 ⚠️ OAuth state parameter
Заголовок раздела «3.8 ⚠️ OAuth state parameter»Без state parameter — CSRF на OAuth callback. Attacker может trigger callback с своим code → токен attached к user account.
state := generateRandomState()// Save state in sessionurl := config.AuthCodeURL(state)
// Callbackif r.URL.Query().Get("state") != session.State { // CSRF}3.9 ⚠️ JWT clock skew
Заголовок раздела «3.9 ⚠️ JWT clock skew»exp claim vs server time — может быть skew. Use 5-second leeway:
parser := jwt.NewParser(jwt.WithLeeway(5 * time.Second))parser.Parse(...)3.10 ⚠️ Audience missing check
Заголовок раздела «3.10 ⚠️ Audience missing check»JWT для другого service может быть accepted, если не проверять aud. Verify audience:
parser := jwt.NewParser( jwt.WithAudience("api.example.com"), jwt.WithIssuer("https://auth.example.com"),)3.11 ⚠️ Role escalation в JWT
Заголовок раздела «3.11 ⚠️ Role escalation в JWT»Если role в JWT и attacker может modify claims без resign → escalate. Always verify signature first.
Если roles в DB lookup’аются по userID — safer, но latency.
3.12 ⚠️ Session fixation
Заголовок раздела «3.12 ⚠️ Session fixation»Attacker устанавливает session ID жертве (через URL). Жертва logs in → attacker uses same session.
Решение: regenerate session ID после login.
3.13 ⚠️ Permission cache staleness
Заголовок раздела «3.13 ⚠️ Permission cache staleness»Если cache user permissions — изменение role не отражается сразу. Trade-off:
- Short TTL (минута) — latency vs freshness.
- Pub/sub invalidation — complex.
3.14 ⚠️ Casbin loading policy
Заголовок раздела «3.14 ⚠️ Casbin loading policy»Casbin загружает policy в memory. Изменения в файле/DB не reflected без e.LoadPolicy(). Use watcher (file watcher или Redis pub/sub).
4. Real cases
Заголовок раздела «4. Real cases»4.1 Auth0
Заголовок раздела «4.1 Auth0»- Founded 2013, acquired by Okta 2021 ($6.5B).
- Cloud-hosted identity.
- Supports OAuth2, OIDC, SAML.
- Standard для startups.
4.2 Okta
Заголовок раздела «4.2 Okta»- Enterprise SSO leader.
- Workforce + Customer Identity.
- Integrations с тысячами SaaS.
4.3 Keycloak
Заголовок раздела «4.3 Keycloak»- Open-source, Red Hat.
- Self-hosted.
- Full OIDC, OAuth2, SAML.
- Используется enterprise с complinace требованиями.
4.4 Google Zanzibar
Заголовок раздела «4.4 Google Zanzibar»- Internal authorization system Google.
- 20M+ QPS, p99 < 10ms.
- Graph-based ReBAC.
- Paper 2019.
4.5 SpiceDB (Authzed)
Заголовок раздела «4.5 SpiceDB (Authzed)»- Open-source Zanzibar implementation.
- Используется Netflix, Snapchat.
- Go-based.
4.6 GitHub fine-grained permissions
Заголовок раздела «4.6 GitHub fine-grained permissions»GitHub Apps использует fine-grained permissions (как ReBAC):
- Permission per resource type.
- Installation-level vs user-level.
- Expressive but complex.
4.7 Тинькофф / T-Bank
Заголовок раздела «4.7 Тинькофф / T-Bank»- Custom OIDC provider.
- mTLS для service-to-service.
- 2FA mandatory.
4.8 Авито
Заголовок раздела «4.8 Авито»- Keycloak-based.
- OPA для AuthZ decisions.
- OPA sidecar pattern в k8s.
5. Вопросы для собеседования
Заголовок раздела «5. Вопросы для собеседования»Q1: Чем authentication отличается от authorization? A: Authentication — “кто ты?” (verify identity, password/token check). Authorization — “что тебе можно?” (check permissions). AuthN всегда перед AuthZ.
Q2: Session vs JWT — что выбрать? A: Session — server state, легко revoke, для monolith. JWT — stateless, scales horizontal, для microservices. Hybrid: JWT короткий + refresh token + DB check для critical actions.
Q3: Какие алгоритмы JWT? A: HS256 (symmetric, один сервис). RS256/ES256 (asymmetric, multi-service). EdDSA (modern). НИКОГДА alg=none. ВСЕГДА проверять alg в callback.
Q4: Что такое refresh token и зачем? A: Long-lived token (7-30 days) для получения нового access token. Access — short (15 мин), быстрая expiration. Refresh — slower, in DB, revokable. Trade-off security/UX.
Q5: Refresh token rotation? A: При каждом refresh — НОВЫЙ refresh, старый invalidate. Если старый используется — token theft detection, revoke всё. Modern best practice.
Q6: Какие OAuth 2.0 flows? A: Authorization Code (web с PKCE), Client Credentials (service-to-service), Device Code (TV, CLI). Implicit и Password Credentials — DEPRECATED.
Q7: Что такое PKCE? A: Proof Key for Code Exchange. Защита от code interception в public clients (mobile, SPA). Client generates code_verifier, sends code_challenge (SHA256). При exchange отправляет verifier. RFC 9700 (2024) рекомендует для ALL clients.
Q8: OAuth 2.0 vs OIDC? A: OAuth — авторизация (access token для API). OIDC — extension OAuth для аутентификации (id_token JWT с user info). OIDC = OAuth + identity layer.
Q9: RBAC vs ABAC? A: RBAC — user → roles → permissions, simple. ABAC — policy на attributes (user, resource, context), flexible. RBAC покрывает 80% случаев, ABAC для complex requirements.
Q10: Что такое OPA? A: Open Policy Agent. CNCF проект, centralized authorization. Policy language Rego. Sidecar / library deployment. Decouple policy от code.
Q11: Что такое Zanzibar? A: Google internal AuthZ system (paper 2019). Graph-based (relations + objects). Используется Google Docs, Drive. 20M+ QPS, p99 <10ms. SpiceDB — open-source реализация.
Q12: Какой password hashing? A: Argon2id (winner Password Hashing Competition 2015) или bcrypt. Cost factor → 250ms hash. НЕ MD5/SHA256 (слишком быстрые).
Q13: Зачем salt в password hashing? A: Защита от rainbow tables — precomputed hash lookups. Каждый user — уникальный salt. bcrypt/argon2 включают salt в hash automatic.
Q14: Как защититься от brute force? A: Rate limiting per IP/account, account lockout после N попыток, captcha, exponential backoff, monitoring suspicious patterns, 2FA mandatory.
Q15: Что такое 2FA / MFA? A: Two/Multi-factor authentication. Knowledge (password) + possession (phone/yubikey) + inherence (biometric). TOTP, SMS (deprecated), WebAuthn (passkeys, modern).
Q16: WebAuthn / passkeys — что это?
A: FIDO2 standard для phishing-resistant authentication. Hardware/biometric. Public key crypto. В 2026 — push’ится Apple, Google, Microsoft. Library go-webauthn/webauthn.
Q17: mTLS — когда? A: Service-to-service authentication. Сертификат для каждого сервиса (issued by internal CA). В k8s через Istio/Linkerd автоматически. Сильнее API keys (нет shared secret), но manage overhead.
Q18: Что такое SSO? A: Single Sign-On — один login → доступ всем приложениям. Protocols: OIDC (modern), SAML (legacy enterprise). Идентичность через IdP (Okta, Keycloak).
Q19: Что такое JWT JTI? A: JWT ID — unique identifier для token. Используется для revocation (blacklist by jti) и replay protection. Optional но recommended.
Q20: Какие проблемы с long-lived access tokens? A: Невозможно revoke до expiration (stateless). Если token stolen — 24h access. Решение: short TTL + refresh tokens (revokable in DB) или JWT blacklist.
Q21: Что такое key confusion attack? A: Verifier ожидает RS256, но не verify alg. Attacker подаёт JWT с alg=HS256, sign’нув public RSA key как HMAC secret → verify passes. Защита: ALWAYS check method.
Q22: Что такое JWKS?
A: JSON Web Key Set — endpoint с public keys provider’a. /.well-known/jwks.json. Allows key rotation без redeployment verifier’ов.
Q23: Как handle role changes у залогиненного user? A: 1) Short access token TTL — изменения видны быстро. 2) Хранить роль в DB, lookup при каждом request (latency). 3) Pub/sub invalidation. Trade-off latency/freshness.
Q24: Что такое Casbin model?
A: Конфигурация — request/policy definitions, role definitions, matchers, effect. Гибко: RBAC, ABAC, REST. e.Enforce(sub, obj, act) для check.
Q25: Какие проблемы хранить JWT в localStorage? A: XSS — attacker JavaScript может прочитать. Recommendation: HttpOnly cookies. Минусы cookies — CSRF (mitigate SameSite=Strict). В SPA — обычно localStorage с осторожностью или Authorization header.
6. Practice
Заголовок раздела «6. Practice»-
Полный auth flow: реализуй register/login/refresh с JWT + refresh tokens (rotation).
-
OAuth integration: интегрируй Google sign-in через golang.org/x/oauth2 + go-oidc.
-
RBAC implementation: реализуй RBAC middleware (роли в DB или Casbin). 5 ролей, 20 permissions.
-
OPA policy: напиши Rego policy для multi-tenant: user только своего tenant’а ресурсы.
-
Argon2 vs bcrypt: сравни benchmark на твоём железе. Выбери cost factor для 250ms.
-
TOTP 2FA: добавь TOTP 2FA с github.com/pquerna/otp. QR code generation, verify.
-
WebAuthn: реализуй passkey registration и login через go-webauthn.
-
mTLS service-to-service: настрой два Go-сервиса с mTLS. Self-signed CA.
7. Источники
Заголовок раздела «7. Источники»- OAuth 2.0 RFC 6749: https://datatracker.ietf.org/doc/html/rfc6749.
- OIDC spec: https://openid.net/specs/openid-connect-core-1_0.html.
- JWT RFC 7519: https://datatracker.ietf.org/doc/html/rfc7519.
- golang-jwt/jwt: https://github.com/golang-jwt/jwt.
- Casbin: https://casbin.org/.
- OPA: https://www.openpolicyagent.org/.
- Zanzibar paper: https://research.google/pubs/zanzibar-googles-consistent-global-authorization-system/.
- SpiceDB: https://authzed.com/spicedb.
- OWASP Authentication Cheat Sheet: https://cheatsheetseries.owasp.org/cheatsheets/Authentication_Cheat_Sheet.html.
- Keycloak docs: https://www.keycloak.org/documentation.