Модуль: Backend · Уровень: Middle+/Senior

TL;DR#

  • Аутентификация (AuthN) — «кто ты?» (проверка личности). Авторизация (AuthZ) — «что тебе можно?» (проверка прав). Это два независимых шага: сначала AuthN, потом AuthZ.
  • Сессии (stateful): server-side store + opaque cookie. Легко отозвать, но требует общего стора и липкости/репликации. JWT (stateless): self-contained подпись, масштабируется горизонтально, но невозможно мгновенно отозвать без денилиста. Не путайте «stateless токен» с «нет состояния вообще».
  • Cookie: всегда HttpOnly, Secure, SameSite. Защита от XSS (HttpOnly) и CSRF (SameSite/anti-CSRF token) — это разные угрозы, нужны оба слоя.
  • Пароли: только argon2id (предпочтительно) / bcrypt / scrypt с уникальной солью. Никогда plaintext/MD5/SHA-256 без KDF. Сравнение — constant-time.
  • RBAC (роли) для простых иерархий; ABAC (атрибуты + политики) для контекстных решений; ReBAC (Zanzibar/SpiceDB) для графов отношений («владелец документа», sharing).
  • API keys — для machine-to-machine: хранить только хэш, скоупить, ротировать. mTLS — взаимная аутентификация по сертификатам, типично в service mesh.
  • Секреты: не в git, не хардкодить. Env vars — минимум; Vault/Secrets Manager + ротация + encryption at rest — для прода. Принцип наименьших привилегий + defense in depth везде.

Теория#

1. Аутентификация (AuthN) vs Авторизация (AuthZ)#

Это самая базовая и самая часто путаемая пара понятий.

АспектАутентификация (AuthN)Авторизация (AuthZ)
Вопрос«Кто ты?»«Что тебе разрешено?»
ЦельПодтвердить личностьПринять решение о доступе
КогдаПервый шагПосле успешной AuthN
АртефактыПароль, OTP, сертификат, биометрияРоли, разрешения, политики, ACL
Ошибка401 Unauthorized (несмотря на название — это про AuthN!)403 Forbidden
Меняется лиРедко (личность стабильна)Часто (права меняются динамически)

Тонкость HTTP-статусов: 401 Unauthorized исторически назван неверно — это «не аутентифицирован» (нет/невалидны креды). 403 Forbidden — «аутентифицирован, но прав не хватает». На senior это любят спрашивать.

// Псевдо-pipeline: сначала AuthN, потом AuthZ
func AuthMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        // AuthN: кто ты?
        user, err := authenticate(r) // парсит токен/сессию -> личность
        if err != nil {
            w.WriteHeader(http.StatusUnauthorized) // 401: не доказал, кто ты
            return
        }
        ctx := context.WithValue(r.Context(), userKey{}, user)
        next.ServeHTTP(w, r.WithContext(ctx))
    })
}

func RequirePermission(perm string, next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        user := r.Context().Value(userKey{}).(*User)
        // AuthZ: что тебе можно?
        if !user.Can(perm) {
            w.WriteHeader(http.StatusForbidden) // 403: ты известен, но нельзя
            return
        }
        next.ServeHTTP(w, r)
    })
}

Идентификация vs аутентификация: идентификация — это заявление «я Алиса» (username). Аутентификация — доказательство этого заявления (пароль/токен). Email != аутентификация.

2. Сессии (stateful) vs токены (stateless JWT)#

Сессии (stateful)#

Сервер генерирует случайный непрозрачный (opaque) session ID, кладёт его в Set-Cookie, а связанные данные хранит на сервере (Redis/Postgres/память).

// Создание сессии
sid := randomToken(32) // криптослучайный, 256 бит
store.Set(ctx, "sess:"+sid, SessionData{UserID: u.ID, ...}, 24*time.Hour)
http.SetCookie(w, &http.Cookie{
    Name:     "sid",
    Value:    sid,
    HttpOnly: true,
    Secure:   true,
    SameSite: http.SameSiteLaxMode,
    Path:     "/",
    MaxAge:   86400,
})

Токены (stateless, JWT)#

JWT = base64url(header).base64url(payload).base64url(signature). Сервер подписывает (HMAC HS256 или асимметрично RS256/ES256/EdDSA) и при каждом запросе проверяет подпись, не обращаясь к хранилищу. Данные (claims) лежат в payload — он подписан, но не зашифрован (base64, читается любым).

import "github.com/golang-jwt/jwt/v5"

claims := jwt.MapClaims{
    "sub": u.ID,
    "exp": time.Now().Add(15 * time.Minute).Unix(), // короткий TTL!
    "iat": time.Now().Unix(),
    "scope": "read:orders write:orders",
}
tok := jwt.NewWithClaims(jwt.SigningMethodES256, claims)
signed, _ := tok.SignedString(privateKey)

// Проверка — ОБЯЗАТЕЛЬНО фиксируем допустимые алгоритмы
parsed, err := jwt.Parse(signed, func(t *jwt.Token) (any, error) {
    if _, ok := t.Method.(*jwt.SigningMethodECDSA); !ok {
        return nil, fmt.Errorf("unexpected alg: %v", t.Header["alg"]) // защита от alg-confusion
    }
    return publicKey, nil
}, jwt.WithValidMethods([]string{"ES256"}))

Таблица сравнения#

КритерийСессии (stateful)JWT (stateless)
Хранение состоянияСервер (Redis/БД)Клиент (токен сам несёт claims)
МасштабированиеНужен общий стор или липкие сессииЛюбая нода проверяет по ключу — горизонтально просто
Revocation (отзыв)Мгновенно: удалил из стораНельзя до exp без денилиста/короткого TTL
Размер на запросМаленький cookie (ID)Большой (весь payload в каждом запросе)
Утечка данныхНа сервере, клиент видит только IDPayload читается любым (не класть PII/секреты)
Изменение правСразу при следующем запросеТолько после ре-выпуска токена
ТранспортОбычно cookieCookie или Authorization: Bearer
Типичный рискCSRF (если cookie)XSS (если хранить в localStorage)

Revocation JWT — основная боль#

JWT нельзя «отозвать» по дизайну. Решения:

  • Короткий TTL access-токена (5–15 мин) + долгоживущий refresh-токен, который хранится stateful (в БД) и отзывается. Это де-факто стандарт.
  • Денилист (blocklist) отозванных jti в Redis — но это возвращает stateful-зависимость, теряется главное преимущество.
  • Token versioning: в claims кладём token_version, сравниваем с версией в БД (инкремент = отзыв всех токенов юзера). Но это снова чтение БД на каждый запрос.

Вывод: «полностью stateless» auth — миф для систем, где нужен мгновенный logout/ban. Refresh-токен всё равно stateful.

Где хранить токен на клиенте (CSRF vs XSS)#

Место храненияXSS-рискCSRF-рискКомментарий
localStorageВысокий (JS читает токен)НетЛюбой XSS = кража токена
HttpOnly cookieНизкий (JS не читает)ЕстьНужен SameSite + anti-CSRF token
In-memory (JS-переменная)СреднийНетТеряется при reload, нужен refresh-flow

Лучший практичный вариант для SPA: access-токен в памяти + refresh-токен в HttpOnly Secure SameSite cookie.

Set-Cookie: sid=abc; HttpOnly; Secure; SameSite=Lax; Path=/; Domain=example.com; Max-Age=86400
АтрибутНазначение
HttpOnlyCookie недоступна из JS (document.cookie) — митигирует кражу при XSS
SecureОтправляется только по HTTPS — защита от перехвата по сети
SameSite=StrictНе шлётся при любых кросс-сайтовых запросах (даже по клику из письма) — максимум защиты от CSRF, но ломает «переход извне»
SameSite=LaxШлётся при top-level навигации GET, но не при POST/iframe/fetch с другого сайта — разумный дефолт (и дефолт браузеров)
SameSite=NoneШлётся всегда; требует Secure. Для кросс-доменных сценариев (виджеты, сторонние API)
DomainКакие хосты получают cookie. Без Domain — только точный хост; с Domain=example.com — и поддомены
PathОграничивает cookie путём URL
__Host- / __Secure- префиксыБраузер форсит требования: __Host- требует Secure, Path=/, без Domain — сильная защита от подмены

SameSite снижает CSRF, но не заменяет anti-CSRF токены полностью (старые браузеры, SameSite=None, GET-мутации). Defense in depth: SameSite=Lax + double-submit/synchronizer token для мутаций.

4. Хранение паролей#

Никогда не храните plaintext. Никогда не используйте «голые» хэши (MD5, SHA-1, SHA-256) — они быстрые, что и нужно атакующему для брутфорса/радужных таблиц. Нужен медленный KDF с солью.

АлгоритмТипПараметрыРекомендация
argon2idMemory-hardmemory, iterations, parallelismПредпочтительно (победитель PHC, стоек к GPU/ASIC)
scryptMemory-hardN, r, pХорошо, если нет argon2
bcryptCPU-hardcost (work factor)Ок, но лимит пароля 72 байта (молча обрезает!)
PBKDF2CPU-harditerationsДопустим (FIPS), но слабее против GPU
import "golang.org/x/crypto/argon2"

// Регистрация
salt := make([]byte, 16)
rand.Read(salt) // crypto/rand!
hash := argon2.IDKey([]byte(password), salt,
    3,        // time (iterations)
    64*1024,  // memory KiB (64 MB)
    4,        // parallelism
    32)       // keyLen
// Хранить: alg, params, salt, hash (формат PHC-строки $argon2id$v=19$m=...,t=...,p=...$salt$hash)

// Проверка — constant-time сравнение!
import "crypto/subtle"
candidate := argon2.IDKey([]byte(input), salt, 3, 64*1024, 4, 32)
ok := subtle.ConstantTimeCompare(candidate, hash) == 1

Ключевые моменты:

  • Соль — уникальная на каждый пароль, делает радужные таблицы бесполезными. Хранится рядом с хэшем (не секрет).
  • Pepper (опционально) — глобальный секрет, добавляемый к паролю, хранится отдельно (в Vault/HSM, не в БД). Защищает при утечке только БД.
  • Constant-time сравнение (subtle.ConstantTimeCompare) — обычное == для байтов сравнивает с ранним выходом и течёт время → timing attack. Для самого хэша argon2 сравнивает фиксированную длину, но для токенов/HMAC это критично.
  • Параметры со временем растут — храните их вместе с хэшем, чтобы апгрейдить (rehash при логине, если параметры устарели).

bcrypt 72-байтный лимит: всё после 72 байта игнорируется. Если предхэшировать паролем длиннее (например base64(SHA-256(pwd))) — следите за null-байтами, bcrypt обрезает по \0. Argon2 этого лишён.

5. Модели авторизации: RBAC vs ABAC vs ReBAC#

МодельИдеяРешение основано наКогда применятьМинусы
RBACПользователь → роли → разрешенияРоль субъектаПростые иерархии (admin/editor/viewer), enterprise«Role explosion», плохо выражает контекст («только свои заказы»)
ABACПолитики над атрибутами субъекта/ресурса/средыАтрибуты + правилаКонтекстные/динамические решения (время, гео, отдел, статус документа)Сложно отлаживать/аудировать, «почему доступ?»
ReBACГраф отношений между субъектами и ресурсамиСвязь («owner of», «member of team»)Sharing, иерархии ресурсов, Google-Docs-likeНужна спец. инфраструктура (Zanzibar/SpiceDB), сложность

RBAC#

type Role struct {
    Name        string
    Permissions []string // "orders:read", "orders:write"
}
func (u *User) Can(perm string) bool {
    for _, r := range u.Roles {
        if slices.Contains(r.Permissions, perm) {
            return true
        }
    }
    return false
}

ABAC — политика как функция атрибутов#

// "Менеджер может читать заказы своего региона в рабочее время"
func canReadOrder(subj Subject, res Order, env Env) bool {
    return subj.Role == "manager" &&
        subj.Region == res.Region &&
        env.Hour >= 9 && env.Hour < 18
}

На практике политики выносят в движок: OPA/Rego, Cedar (AWS), Casbin.

ReBAC — Zanzibar / SpiceDB / OpenFGA#

Авторизация как проверка достижимости в графе отношений. Запись (tuple): document:readme#viewer@user:alice. Проверка: «может ли alice читать readme?» обходит граф (viewereditorowner, наследование через папки/группы).

// SpiceDB-схема (упрощённо)
definition document {
    relation owner: user
    relation viewer: user | group#member
    permission read = viewer + owner
    permission write = owner
}

Применяют, когда права определяются отношениями объектов, а не статичными ролями (общие папки, вложенные команды). Решает проблему консистентности и «горячих» проверок на масштабе (как Google для Docs/Drive/YouTube).

На senior полезно: эти модели не взаимоисключающие. Часто RBAC для грубых ролей + ABAC/ReBAC для тонких решений. Главный архитектурный выбор — выносить ли AuthZ в централизованный сервис/sidecar (PDP — Policy Decision Point) или оставить в приложении (PEP — Policy Enforcement Point).

6. API keys (machine-to-machine)#

API key — длинный непрозрачный секрет для аутентификации сервиса/интеграции (не человека, у которого есть пароль+MFA).

Правила:

  • Генерация: криптослучайный, с префиксом для опознания (sk_live_...) — помогает сканерам секретов и саппорту.
  • Хранение: только хэш (как пароль, но т.к. ключ высокоэнтропийный — достаточно быстрого SHA-256, KDF не обязателен). Показываем сырой ключ один раз при создании.
  • Скоупинг: ключ привязан к набору разрешений/ресурсов (наименьшие привилегии). Read-only ключ != admin ключ.
  • Ротация: поддержка нескольких активных ключей одновременно, чтобы менять без даунтайма (issue new → migrate → revoke old).
  • Метаданные: last_used, created_at, expiry — для аудита и протухания.
func newAPIKey() (display, hash string) {
    raw := randomToken(32)
    display = "sk_live_" + raw            // отдаём пользователю ОДИН раз
    sum := sha256.Sum256([]byte(raw))
    hash = hex.EncodeToString(sum[:])     // в БД храним только это
    return
}
// Проверка входящего ключа — найти по хэшу, constant-time на хэше

API key слабее OAuth/mTLS (один long-lived секрет, легко утекает в логи/git). Для M2M в проде предпочтительнее OAuth2 client credentials (короткие токены) или mTLS.

7. mTLS (взаимный TLS)#

Обычный TLS: клиент проверяет сертификат сервера. mTLS: ещё и сервер проверяет сертификат клиента. Обе стороны аутентифицируют друг друга криптографически.

// Сервер требует клиентский сертификат
caPool := x509.NewCertPool()
caPool.AppendCertsFromPEM(caPEM)
srv := &http.Server{
    TLSConfig: &tls.Config{
        ClientCAs:  caPool,
        ClientAuth: tls.RequireAndVerifyClientCert, // обязателен валидный клиентский cert
        MinVersion: tls.VersionTLS13,
    },
}
// Личность клиента — из r.TLS.PeerCertificates[0].Subject (CN / SAN)

Когда применять:

  • Service mesh (Istio/Linkerd): автоматический mTLS между подами, sidecar-прокси выпускает/ротирует короткоживущие сертификаты (SPIFFE/SPIRE) — zero-trust внутри кластера.
  • B2B-интеграции с высокими требованиями (финтех, банки).
  • Внутренние M2M, где не хотим раздавать долгоживущие секреты.

Плюсы: нет «секрета-пароля» в запросе, личность встроена в транспорт, сложно подделать. Минусы: управление сертификатами (выпуск, ротация, отзыв через CRL/OCSP), сложнее отлаживать, нагрузка на инфраструктуру PKI.

8. Где хранить секреты#

СпособПлюсыМинусы
Хардкод в кодеНикогда. Утечёт в git/историю навсегда
.env файлПросто локальноЛегко закоммитить, нет ротации/аудита
Env vars (в рантайме)Просто, 12-factorВидны в /proc, дампах, child-процессах, нет ротации
Secrets Manager (Vault, AWS/GCP)Ротация, аудит, encryption at rest, dynamic secrets, ACLСложнее, зависимость, латентность
KMS/HSMКлючи не покидают модульДорого, для крипто-операций

Best practices:

  • Не коммитить: .gitignore для .env, секрет-сканеры в CI (gitleaks, trufflehog), pre-commit hooks. Если секрет попал в git — он скомпрометирован, ротировать, а не просто удалять коммит (история остаётся).
  • Encryption at rest: секреты в хранилище шифруются (Vault использует master key/seal, облака — KMS).
  • Ротация: автоматическая, регулярная. Vault dynamic secrets выдают короткоживущие креды БД по запросу (выдал на 1 час — отозвал).
  • Доступ по наименьшим привилегиям: сервис читает только свои секреты (Vault policies, IAM роли). Аудит-лог каждого доступа.
  • Не логировать: маскировать секреты в логах/трейсах/ошибках.
  • Workload identity: вместо статичных кредов — IAM роль пода/инстанса (AWS IRSA, GCP Workload Identity), Vault auth по Kubernetes SA. Секрет «выдаётся» по доказанной личности, ничего не хардкодится.
// Антипаттерн
const dbPassword = "p@ssw0rd" // НИКОГДА

// Лучше: из секрет-менеджера в рантайме, с возможностью обновления
secret, err := vaultClient.KVv2("secret").Get(ctx, "myapp/db")
pwd := secret.Data["password"].(string)

9. Сквозные принципы#

  • Принцип наименьших привилегий (PoLP): каждый субъект (юзер, сервис, токен, IAM-роль) имеет минимум прав для задачи. Дефолт — deny. Скоупы у токенов/ключей.
  • Defense in depth: несколько независимых слоёв. Пример для cookie-auth: TLS + HttpOnly + Secure + SameSite + anti-CSRF token + rate limiting + короткий TTL. Падение одного слоя не компрометирует систему.
  • Fail closed: при ошибке/неопределённости — запрещать, а не разрешать.
  • Zero trust: не доверять сети по умолчанию, аутентифицировать каждый запрос (даже внутренний — отсюда mTLS в mesh).

Подводные камни / gotchas#

  • alg: none и alg-confusion в JWT: если не фиксировать допустимые алгоритмы при верификации, атакующий ставит alg: none (без подписи) или подменяет RS256HS256, используя публичный ключ как HMAC-секрет. Всегда jwt.WithValidMethods([...]).
  • JWT payload не зашифрован: base64, не base64-«шифр». Не класть пароли, PII, секреты — только идентификаторы.
  • «Stateless logout»: пользователь нажал «выйти», но JWT валиден до exp. Без денилиста/короткого TTL токен продолжает работать — частая дыра.
  • == для сравнения токенов/HMAC: timing attack. Используйте subtle.ConstantTimeCompare.
  • bcrypt 72 байта: длинные пароли молча обрезаются; пароль из менеджера паролей может «совпасть» с другим. Использовать argon2id или предхэшировать аккуратно.
  • math/rand для токенов/солей: предсказуемо. Только crypto/rand.
  • SameSite=None без Secure: браузер отбросит cookie. И None отключает CSRF-защиту самой cookie.
  • Хранение JWT в localStorage: один XSS = кража всех токенов. Лучше HttpOnly cookie или память.
  • Логирование токенов/ключей/паролей: утечка через логи/APM/Sentry — частый инцидент. Маскировать.
  • Отсутствие ротации refresh-токенов: без rotation украденный refresh живёт долго. Применять refresh token rotation + reuse detection (повторное использование старого = компрометация, отозвать всю цепочку).
  • 403 vs 404 для приватных ресурсов: иногда отдают 404 вместо 403, чтобы не раскрывать существование ресурса (enumeration). Зависит от модели угроз.
  • Доверие клиентским claims без проверки: роль/права в JWT валидны только если подпись проверена и issuer/audience совпадают (iss, aud, exp, nbf).
  • Секреты в env при docker inspect/crash dump: env-переменные видны многим; для high-security — секрет-менеджер с in-memory доставкой.

Вопросы на собеседовании#

В: В чём разница между аутентификацией и авторизацией, и какие HTTP-статусы им соответствуют? О: AuthN — проверка личности («кто ты»), AuthZ — проверка прав («что можно»). Сначала AuthN, потом AuthZ. 401 Unauthorized — про AuthN (нет/невалидны креды), 403 Forbidden — про AuthZ (аутентифицирован, но прав нет). Название 401 исторически вводит в заблуждение.

В: Когда выбрать сессии, а когда JWT? О: Сессии — когда нужен мгновенный отзыв, простая модель, и есть общий стор (Redis); особенно для классических web-приложений с cookie. JWT — когда нужно горизонтальное масштабирование без общего стора, межсервисная передача identity, мобильные/SPA клиенты. Но «чистый stateless» ломается на logout/ban — почти всегда добавляют stateful refresh-токены. Гибрид: короткий JWT access + stateful refresh.

В: Как отозвать JWT до истечения срока? О: По дизайну нельзя без состояния. Варианты: короткий TTL + отзываемый refresh-токен (стандарт); денилист jti в Redis (возвращает stateful); token_version в claims со сверкой в БД. Любой реальный отзыв требует серверного состояния — это компромисс с самой идеей stateless.

В: Чем отличаются угрозы XSS и CSRF, и как защититься? О: XSS — выполнение чужого JS на странице (крадёт токены, действует от имени юзера). Защита: CSP, экранирование, HttpOnly cookie (JS не прочитает). CSRF — браузер автоматически шлёт cookie на запрос, инициированный чужим сайтом. Защита: SameSite=Lax/Strict, anti-CSRF токены (synchronizer/double-submit), проверка Origin/Referer. Это разные угрозы — нужны оба слоя (defense in depth).

В: Как правильно хранить пароли и что не так с SHA-256? О: Только медленный KDF с уникальной солью: argon2id (предпочтительно), bcrypt, scrypt. SHA-256/MD5 быстрые и без соли → уязвимы к брутфорсу на GPU и радужным таблицам. Соль уникальна на пароль, хранится с хэшем. Сравнение constant-time. Параметры KDF растут со временем (rehash при логине). Опционально pepper в отдельном хранилище.

В: RBAC vs ABAC vs ReBAC — когда что? О: RBAC — статичные роли/разрешения, простые иерархии, но «role explosion» и слабо выражает контекст. ABAC — политики над атрибутами (субъект/ресурс/среда), хорош для динамических/контекстных решений (время, гео, статус), сложнее аудит. ReBAC (Zanzibar/SpiceDB/OpenFGA) — авторизация как граф отношений, идеален для sharing и иерархий ресурсов (Google Docs). На практике комбинируют; ключевое решение — централизованный PDP или логика в приложении.

В: Что такое mTLS и где он уместен? О: Взаимный TLS: не только клиент проверяет сервер, но и сервер проверяет клиентский сертификат — обе стороны криптографически аутентифицированы. Уместен в service mesh (Istio/Linkerd с SPIFFE/SPIRE, авто-ротация коротких сертов, zero-trust), B2B-интеграциях, внутреннем M2M. Личность встроена в транспорт, нет «секрета-пароля» в запросе; цена — управление PKI (выпуск/ротация/отзыв).

В: Где хранить секреты в проде и почему не в env-переменных? О: Лучше секрет-менеджер (Vault, AWS/GCP Secrets Manager): ротация, аудит, encryption at rest, dynamic secrets, ACL по наименьшим привилегиям. Env-переменные просты, но видны в /proc, дампах, child-процессах, без ротации/аудита. Никогда не коммитить (сканеры в CI, ротация при утечке). Идеал — workload identity (IAM-роль пода) вместо статичных кредов.

В: Как безопасно реализовать API keys для M2M? О: Криптослучайный ключ с префиксом (sk_live_), хранить только хэш (SHA-256 достаточно из-за высокой энтропии), показывать сырой ключ один раз. Скоупить под минимальные права, поддержать несколько активных ключей для бесшовной ротации, метаданные (last_used, expiry). Для прода предпочтительнее OAuth2 client credentials или mTLS — API key это один долгоживущий секрет, легко утекает.

В: Какие атаки на JWT-верификацию вы знаете? О: alg: none (подпись отключена), alg-confusion RS256HS256 (публичный ключ используется как HMAC-секрет). Защита: всегда фиксировать допустимые алгоритмы (WithValidMethods), не доверять alg из заголовка, проверять iss/aud/exp/nbf. Также: незашифрованный payload (не класть секреты), отсутствие отзыва.

На что копают на senior+#

  • Архитектура AuthZ на масштабе: где принимается решение — PEP в каждом сервисе vs централизованный PDP (OPA-sidecar, авторизационный сервис), как избежать «горячих» проверок и обеспечить консистентность (проблема, которую решает Zanzibar через зукиперы/тайм-стемпы).
  • Полный refresh-flow: rotation, reuse detection, привязка refresh к устройству/family, отзыв всей цепочки при компрометации, sliding vs absolute expiration.
  • Identity federation: OIDC поверх OAuth2, как валидировать токены IdP (JWKS, ротация ключей, кэш), SSO, SAML vs OIDC, audience/issuer изоляция между сервисами.
  • Zero trust / SPIFFE: workload identity, SVID, авто-ротация сертов в mesh, отказ от долгоживущих секретов вовсе.
  • Криптогигиена: выбор HS vs RS vs ES vs EdDSA, управление и ротация подписывающих ключей, key rolling без даунтайма (kid в заголовке), HSM/KMS для приватных ключей.
  • Threat modeling конкретного флоу: умение разложить логин/refresh на угрозы (STRIDE), назвать слои defense in depth и failure modes (fail closed).
  • Секреты как процесс, а не настройка: динамические креды, lease/TTL, аудит доступа, реакция на инцидент (ротация всего при утечке), отделение PoLP по сервисам.
  • Производительность и кэш авторизации: как кэшировать решения без ослабления отзыва, инвалидция при изменении прав, decision logs для аудита и отладки «почему доступ».