Documentation
¶
Overview ¶
Package oidckit — OIDC BFF-контур для входа через внешнего OIDC-провайдера: relying-party (discovery, JWKS-кэш, verify ID-token, Authorization Code + PKCE), huma-роуты login/callback/me/logout, cookie-сессия HS256 (authnkit) и extractor для httpkit.HumaAuth. State логин-флоу — через StateStore (Redis/in-memory).
Index ¶
- Constants
- Variables
- func ExpiredSessionCookies(cfg CookieConfig) []http.Cookie
- func NewSecurityScheme() httpkit.SecuritySchemeEntry
- func PKCE() (verifier, challenge string, err error)
- func RandomToken() (string, error)
- func RegisterIdentityLogAttr()
- func RegisterRoutes(api huma.API, b *BFF)
- func SessionCookie(value string, cfg CookieConfig) http.Cookie
- func WithIdentity(ctx context.Context, id Identity) context.Context
- type BFF
- type Claims
- type Config
- type CookieConfig
- type Extractor
- type Hooks
- type Identity
- type MeView
- type RPConfig
- type StateStore
- type Verifier
Constants ¶
const ( // CookieAccess — httpOnly-cookie c session-JWT (единственная сессионная кука BFF). CookieAccess = "access_token" // CookieRefresh чистится на logout заодно с access: у сервисов с отдельным // refresh-контуром не должно оставаться живой пары после выхода. CookieRefresh = "refresh_token" )
const SecuritySchemeName = "Cookie"
SecuritySchemeName — имя openapi security-scheme cookie-сессии; операции защищаются `Security: []map[string][]string{{oidckit.SecuritySchemeName: {}}}`.
Variables ¶
var ( ErrRPConfig = errors.New("oidckit: issuer and client id required") ErrIssuerMismatch = errors.New("oidckit: discovery issuer mismatch") ErrNoJWKS = errors.New("oidckit: discovery has no jwks_uri") ErrNoEndpoints = errors.New("oidckit: discovery has no authorization/token endpoint") ErrInvalidToken = errors.New("oidckit: malformed id token") ErrUnknownKey = errors.New("oidckit: no signing key for kid") ErrNoSubject = errors.New("oidckit: id token has no subject") ErrNonceMismatch = errors.New("oidckit: id token nonce mismatch") ErrNoIDToken = errors.New("oidckit: token response has no id_token") ErrExchange = errors.New("oidckit: code exchange failed") )
var ErrNoSessionCookie = errors.New("oidckit: no auth cookie")
var Module = fx.Module( "oidckit", fx.Provide( newBFF, NewExtractor, func(e *Extractor) httpkit.AuthExtractor { return e.Extract }, fx.Annotate(NewSecurityScheme, fx.ResultTags(`group:"httpkit.security_scheme"`)), ), fx.Invoke(RegisterIdentityLogAttr), fx.Invoke(RegisterRoutes), )
Module — drop-in BFF: роуты login/callback/me/logout, security-scheme Cookie, httpkit.AuthExtractor (cookie → Identity в ctx), user_id в логах.
Module не provide'ит Config/CookieConfig (asymmetry note в pgkit/module.go — caller добавляет `fx.Provide(oidckit.NewConfig, oidckit.NewCookieConfig)`), StateStore (Redis: `fx.Provide(fx.Annotate(oidckit.NewCacheStateStore, ...))` поверх cachekit; либо NewMemoryStateStore) и JWTSigner/JWTParser (authnkit.Module). Hooks опциональны. Сервису с составным extractor'ом (несколько схем authn) Module не подходит — собирай из конструкторов, httpkit принимает один AuthExtractor.
Functions ¶
func ExpiredSessionCookies ¶
func ExpiredSessionCookies(cfg CookieConfig) []http.Cookie
ExpiredSessionCookies — Set-Cookie на сброс сессии (logout): access + refresh.
func NewSecurityScheme ¶
func NewSecurityScheme() httpkit.SecuritySchemeEntry
NewSecurityScheme — запись для `components.securitySchemes` (apiKey in cookie). Module кладёт её в group `httpkit.security_scheme` сам; без fx — передай в httpkit.NewServer.
func RandomToken ¶
RandomToken — криптослучайный URL-safe токен (state, nonce, PKCE-verifier).
func RegisterIdentityLogAttr ¶
func RegisterIdentityLogAttr()
RegisterIdentityLogAttr пробрасывает subject сессии в каждый log-record как user_id (logkit user-registered attr). Дергается один раз на старте (Module делает это сам).
func RegisterRoutes ¶
RegisterRoutes регистрирует BFF-роуты под Config.RoutePrefix (default /auth): GET <p>/login, GET <p>/callback, GET <p>/me (Security: Cookie), POST <p>/logout.
func SessionCookie ¶
func SessionCookie(value string, cfg CookieConfig) http.Cookie
SessionCookie — httpOnly session-cookie (SameSite=Strict; Secure по COOKIE_SECURE).
Types ¶
type BFF ¶
type BFF struct {
// contains filtered or unexported fields
}
BFF ведёт Authorization-Code-flow: login → idp → callback, после чего минтит session-cookie (HS256/JWT_SECRET), которую читает Extractor; плюс me/logout.
type Claims ¶
type Claims struct {
Subject string
Username string // preferred_username — логин (напр. n.sycheva)
Name string // name — ФИО (напр. Наталья Сычева)
Email string
}
Claims — то, что вытащили из проверенного ID-token'а.
type Config ¶
type Config struct {
Issuer string `envconfig:"OIDC_ISSUER"`
ClientID string `envconfig:"OIDC_CLIENT_ID"`
ClientSecret string `envconfig:"OIDC_CLIENT_SECRET"`
RedirectURL string `envconfig:"OIDC_REDIRECT_URL"`
PostLoginURL string `envconfig:"OIDC_POST_LOGIN_URL" default:"/"`
// SessionTTL — срок жизни cookie-сессии (exp session-JWT).
SessionTTL time.Duration `envconfig:"OIDC_SESSION_TTL" default:"12h"`
// RoutePrefix — префикс BFF-роутов (login/callback/me/logout); программный,
// не env: пути — часть API-контракта сервиса, пусто → "/auth".
RoutePrefix string `ignored:"true"`
}
Config — env-конфиг BFF-контура (имена OIDC_* / COOKIE_SECURE — public API kit'а).
type CookieConfig ¶
type CookieConfig struct {
// Secure — флаг Secure сессионной cookie. Дефолт true, как у hubkit
// (`HUB_SESSION_COOKIE_SECURE`): cookie без него ходит по plain HTTP, а
// http://localhost браузеры считают доверенным origin'ом и Secure-cookie на
// нём принимают — то есть строгий дефолт локальную разработку не ломает.
// Выключать приходится только на контуре, где HTTP идёт не через localhost.
Secure bool `envconfig:"COOKIE_SECURE" default:"true"`
// Domain пусто — host-only (строже: cookie не поедет на соседние поддомены).
// `.<домен>` нужен, когда сессию выдаёт один хост, а читает другой.
Domain string `envconfig:"COOKIE_DOMAIN"`
// Env — та же APP_ENV, что в logkit/telemetry. Читается здесь, чтобы
// комбинация «боевая среда + Secure=false» отказывала на старте, а не
// работала молча (см. Validate).
Env string `envconfig:"APP_ENV" default:"dev"`
}
func NewCookieConfig ¶
func NewCookieConfig() (CookieConfig, error)
func (CookieConfig) Validate ¶ added in v0.57.0
func (c CookieConfig) Validate() error
Validate отказывает на боевой среде без Secure. Отдельный метод, а не только проверка в NewCookieConfig: сервис, собирающий CookieConfig из своего App-конфига, обязан иметь тот же гейт под рукой.
type Extractor ¶
type Extractor struct {
// contains filtered or unexported fields
}
Extractor восстанавливает Identity защищённого запроса: cookie access_token → authnkit.JWTParser (тот же JWT_SECRET, что подписал BFF) → Identity в ctx. Сигнатура Extract совместима с httpkit.AuthExtractor.
func NewExtractor ¶
type Hooks ¶
type Hooks struct {
// SessionClaims маппит проверенные claims ID-token'а в дополнительные claims
// session-JWT (например "role" по списку админов). Ключи sub /
// preferred_username / name / email заполняет сам kit; hook может их переопределить.
SessionClaims func(c Claims) map[string]any
// OnLogin выполняется после успешного exchange+verify, до выдачи session-cookie
// (JIT-провижининг пользователя у консьюмера). Ошибка отменяет вход.
OnLogin func(ctx context.Context, c Claims) error
}
Hooks — точки расширения BFF; оба поля опциональны.
type Identity ¶
type Identity struct {
Subject string
Username string // preferred_username — логин (напр. n.sycheva)
Name string // name — ФИО (напр. Наталья Сычева)
Email string
Role string
}
Identity — principal cookie-сессии, восстановленный Extractor'ом из session-JWT. Role приезжает из claim'а "role" (его кладёт Hooks.SessionClaims консьюмера); семантика значений — на стороне сервиса, kit ролей не трактует.
func MustIdentityFrom ¶
type MeView ¶
type MeView struct {
Subject string `json:"sub"`
Username string `json:"username,omitempty"`
Name string `json:"name,omitempty"`
Email string `json:"email,omitempty"`
Role string `json:"role,omitempty"`
}
MeView — публичная проекция сессионной identity для фронта.
type RPConfig ¶
RPConfig — параметры relying-party для Verifier (программные; env-контур BFF читает Config и строит RPConfig сам).
type StateStore ¶
type StateStore interface {
Set(ctx context.Context, key string, value []byte, ttl time.Duration) error
GetDel(ctx context.Context, key string) ([]byte, bool, error)
}
StateStore хранит короткоживущий state логин-флоу (PKCE verifier / nonce / return-to) между /auth/login и /auth/callback. Записи one-time: GetDel отдаёт значение и сразу удаляет его (повторный callback с тем же state отвергается).
func NewCacheStateStore ¶
func NewCacheStateStore(c cachekit.Cache) StateStore
NewCacheStateStore — StateStore поверх cachekit.Cache (Redis): переживает рестарт приложения и работает при нескольких репликах.
func NewMemoryStateStore ¶
func NewMemoryStateStore() StateStore
NewMemoryStateStore — StateStore в памяти процесса: для тестов и single-replica-развёртываний без Redis (state не переживает рестарт).
type Verifier ¶
type Verifier struct {
// contains filtered or unexported fields
}
Verifier — OIDC relying-party: discovery /.well-known, JWKS-кэш с lazy-refresh по неизвестному kid, verify iss/aud/exp/nonce, обмен authorization code.
func NewVerifier ¶
func (*Verifier) AuthCodeURL ¶
AuthCodeURL строит redirect на authorization_endpoint (Authorization Code + PKCE S256).