Перейти к содержимому

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).

  1. Концепция: Authentication, Authorization, identity
  2. Production-практики: JWT, OAuth, RBAC, OPA
  3. Gotchas: JWT pitfalls, refresh token race, password storage
  4. Real cases: Auth0, Okta, Keycloak, Zanzibar
  5. Вопросы для собеседования
  6. Practice
  7. Источники

Authentication (AuthN) — “кто ты?”. Verify identity. Authorization (AuthZ) — “что тебе можно?”. Verify permissions.

Пример:

  • AuthN: проверяем password user’a → знаем, что это Alice.
  • AuthZ: знаем, что Alice — admin → разрешаем delete users.

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 caseChoice
Monolith with sticky sessionSession
Microservices, multi-regionJWT
Mobile appJWT (refresh token)
SPA + backendJWT or session с CORS
High security (banking)Session (revocation control)

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).

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 — НИКОГДА. Атаки в прошлом.

import "github.com/golang-jwt/jwt/v5"
// Sign
claims := 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"))
// Verify
parsed, 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.

Access token — short-lived (5-15 мин), для API calls. Refresh token — long-lived (7-30 дней), для получения нового access token.

Flow:

  1. Login → получаем access + refresh.
  2. Используем access для API (15 мин).
  3. Access expired → POST /refresh с refresh token → новый access.
  4. Refresh может rotate’аться (new refresh каждый раз).

Refresh token rotation:

  • Каждый refresh → новый access AND новый refresh.
  • Old refresh invalidated.
  • Замечают replay → revoke всё (вероятно stolen).

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_uri
3. AuthZ Server → User: login page
4. User → AuthZ Server: credentials
5. AuthZ Server → Client: redirect with code
6. Client → AuthZ Server: POST code + client_secret
7. AuthZ Server → Client: access_token + refresh_token
8. Client → Resource Server: API call с access_token

PKCE (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.

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.

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.

SSO — один login → доступ ко всем приложениям.

Protocols:

  • SAML 2.0 — enterprise standard (XML, legacy).
  • OIDC — modern (JSON, REST).

В 2026 — OIDC доминирует, SAML только legacy enterprise.

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 secret
key, _ := totp.Generate(totp.GenerateOpts{
Issuer: "Example",
AccountName: "alice@example.com",
})
secret := key.Secret() // store
// Verify
valid := 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.

RBAC (Role-Based Access Control):

  • User → Roles → Permissions.
  • Simple, fits 80% случаев.
  • Гибкость низкая.
User: Alice
Role: editor
Permissions: [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.classification

PBAC (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)“.

Casbin — popular Go library для AuthZ. Supports RBAC, ABAC, REST.

import "github.com/casbin/casbin/v2"
e, _ := casbin.NewEnforcer("model.conf", "policy.csv")
// Check
ok, _ := 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.act

policy.csv:

p, admin, /*, *
p, editor, /articles/*, read
p, editor, /articles/*, write
g, alice, editor
g, bob, admin

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.

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 group
document:doc1#viewer@group:eng#member # eng members can view doc1
→ Alice can view doc1

SpiceDB (Authzed) — open-source реализация Zanzibar.

Recommendations 2026 (OWASP):

  1. Argon2id — winner Password Hashing Competition.
  2. bcrypt — classic, safe.
  3. 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: 4
hash := 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 на вашем железе.


// Login
func (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
}
// Middleware
func (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))
})
}
// Refresh
func (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,
})
}
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 URL
state := generateRandomState()
url := config.AuthCodeURL(state, oauth2.AccessTypeOffline)
// Redirect user to url
// Callback
func 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")
}
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: "..."})
// Callback
oauth2Token, _ := 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)
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
}
// Middleware
func 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)
})
}
}
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)
})
}
}
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.com
bundles:
authz:
service: bundles-service
`)),
})
// Decision
decision, _ := 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)
}
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 не использовать.

// Server
tlsConfig := &tls.Config{
ClientAuth: tls.RequireAndVerifyClientCert,
ClientCAs: loadCAs("ca.crt"),
}
srv := &http.Server{TLSConfig: tlsConfig}
// Client
clientCert, _ := 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 — автоматически.

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))
})
}
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
}
// ...
}

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
})

Если verifier ожидает RS256 (asymmetric), но не проверяет alg, attacker может:

  • Подать JWT с alg=HS256 и signature, sign’нутой публичным ключом сервера (доступен).
  • Проверить как HMAC с тем же public key → pass.

Защита: проверь alg ИЛИ используй library, которая разделяет HMAC и RSA verify.

https://app.com/page?token=eyJ...

Plain text в:

  • Browser history.
  • Referer headers.
  • Server access logs.

Always в Authorization header или secure cookie.

JWT stateless — нельзя revoke до expiration. Если access token живёт 24h, и telegram’нули user — он всё ещё имеет доступ 24h.

Решения:

  1. Short TTL (5-15 мин) + refresh token (revokable).
  2. Token blacklist (jti в Redis).
  3. Hybrid (JWT с быстрой DB lookup для critical actions).

Mobile app делает 2 параллельных request → один refresh’нул, второй с старым refresh. Если просто reject — UX страдает.

Решение:

  • Grace period — старый refresh valid ещё 30 секунд после rotation.
  • Lock per refresh token.

Cookie без SameSite=Strict или Lax → CSRF possible. Modern browsers default’ом Lax, но prove explicitly.

// УЯЗВИМО (хотя 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 auth

Без state parameter — CSRF на OAuth callback. Attacker может trigger callback с своим code → токен attached к user account.

state := generateRandomState()
// Save state in session
url := config.AuthCodeURL(state)
// Callback
if r.URL.Query().Get("state") != session.State {
// CSRF
}

exp claim vs server time — может быть skew. Use 5-second leeway:

parser := jwt.NewParser(jwt.WithLeeway(5 * time.Second))
parser.Parse(...)

JWT для другого service может быть accepted, если не проверять aud. Verify audience:

parser := jwt.NewParser(
jwt.WithAudience("api.example.com"),
jwt.WithIssuer("https://auth.example.com"),
)

Если role в JWT и attacker может modify claims без resign → escalate. Always verify signature first.

Если roles в DB lookup’аются по userID — safer, но latency.

Attacker устанавливает session ID жертве (через URL). Жертва logs in → attacker uses same session.

Решение: regenerate session ID после login.

Если cache user permissions — изменение role не отражается сразу. Trade-off:

  • Short TTL (минута) — latency vs freshness.
  • Pub/sub invalidation — complex.

Casbin загружает policy в memory. Изменения в файле/DB не reflected без e.LoadPolicy(). Use watcher (file watcher или Redis pub/sub).


  • Founded 2013, acquired by Okta 2021 ($6.5B).
  • Cloud-hosted identity.
  • Supports OAuth2, OIDC, SAML.
  • Standard для startups.
  • Enterprise SSO leader.
  • Workforce + Customer Identity.
  • Integrations с тысячами SaaS.
  • Open-source, Red Hat.
  • Self-hosted.
  • Full OIDC, OAuth2, SAML.
  • Используется enterprise с complinace требованиями.
  • Internal authorization system Google.
  • 20M+ QPS, p99 < 10ms.
  • Graph-based ReBAC.
  • Paper 2019.
  • Open-source Zanzibar implementation.
  • Используется Netflix, Snapchat.
  • Go-based.

GitHub Apps использует fine-grained permissions (как ReBAC):

  • Permission per resource type.
  • Installation-level vs user-level.
  • Expressive but complex.
  • Custom OIDC provider.
  • mTLS для service-to-service.
  • 2FA mandatory.
  • Keycloak-based.
  • OPA для AuthZ decisions.
  • OPA sidecar pattern в k8s.

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.


  1. Полный auth flow: реализуй register/login/refresh с JWT + refresh tokens (rotation).

  2. OAuth integration: интегрируй Google sign-in через golang.org/x/oauth2 + go-oidc.

  3. RBAC implementation: реализуй RBAC middleware (роли в DB или Casbin). 5 ролей, 20 permissions.

  4. OPA policy: напиши Rego policy для multi-tenant: user только своего tenant’а ресурсы.

  5. Argon2 vs bcrypt: сравни benchmark на твоём железе. Выбери cost factor для 250ms.

  6. TOTP 2FA: добавь TOTP 2FA с github.com/pquerna/otp. QR code generation, verify.

  7. WebAuthn: реализуй passkey registration и login через go-webauthn.

  8. mTLS service-to-service: настрой два Go-сервиса с mTLS. Self-signed CA.


  1. OAuth 2.0 RFC 6749: https://datatracker.ietf.org/doc/html/rfc6749.
  2. OIDC spec: https://openid.net/specs/openid-connect-core-1_0.html.
  3. JWT RFC 7519: https://datatracker.ietf.org/doc/html/rfc7519.
  4. golang-jwt/jwt: https://github.com/golang-jwt/jwt.
  5. Casbin: https://casbin.org/.
  6. OPA: https://www.openpolicyagent.org/.
  7. Zanzibar paper: https://research.google/pubs/zanzibar-googles-consistent-global-authorization-system/.
  8. SpiceDB: https://authzed.com/spicedb.
  9. OWASP Authentication Cheat Sheet: https://cheatsheetseries.owasp.org/cheatsheets/Authentication_Cheat_Sheet.html.
  10. Keycloak docs: https://www.keycloak.org/documentation.