Documentation
¶
Overview ¶
Package authkit is a multi-tenant identity and authentication library for Go, backed by PostgreSQL. It provides users and tenants, JWT access tokens with rotating refresh tokens, API keys, OTP verification, invitations, and encrypted provider secrets.
Construct an AuthKit with New and a Config, then obtain the individual services (AuthService, UserService, etc.) from it:
kit, err := authkit.New(authkit.Config{ /* ... */ })
if err != nil {
return err
}
auth := kit.AuthService()
Authentication outcomes are returned as sentinel errors (authkit.Err*), so callers should branch with errors.Is rather than matching error strings.
Index ¶
- Constants
- Variables
- func Migrations(schema string) (fs.FS, error)
- func ParsePrivateKeyPEM(data []byte) (crypto.Signer, error)
- func ParsePublicKeyPEM(data []byte) (crypto.PublicKey, error)
- func WithTx(ctx context.Context, tx *sql.Tx) context.Context
- type APIKeyConfig
- type APIKeyService
- type AuthKit
- func (k *AuthKit) APIKeyService() APIKeyService
- func (k *AuthKit) AuthService() AuthService
- func (k *AuthKit) ExternalIdentityService() ExternalIdentityService
- func (k *AuthKit) InTx(ctx context.Context, fn func(ctx context.Context, tx *sql.Tx) error) error
- func (k *AuthKit) InstallationAuthService() InstallationAuthService
- func (k *AuthKit) InvitationService() InvitationService
- func (k *AuthKit) JSONWebKeySet() JSONWebKeySet
- func (k *AuthKit) JWTService() JWTService
- func (k *AuthKit) MembershipService() MembershipService
- func (k *AuthKit) OTPService() OTPService
- func (k *AuthKit) PasskeyService() PasskeyService
- func (k *AuthKit) PasswordService() PasswordService
- func (k *AuthKit) Ping(ctx context.Context) error
- func (k *AuthKit) SecretService() SecretService
- func (k *AuthKit) SessionService() SessionService
- func (k *AuthKit) SignInHistoryService() SignInHistoryService
- func (k *AuthKit) TenantService() TenantService
- func (k *AuthKit) TokenService() TokenService
- func (k *AuthKit) TwoFactorService() TwoFactorService
- func (k *AuthKit) UserService() UserService
- func (k *AuthKit) VerifiedLoginService() VerifiedLoginService
- type AuthService
- type Config
- type DatabaseConnection
- type ExternalIdentityService
- type InstallationAuthService
- type InvitationService
- type JSONWebKey
- type JSONWebKeySet
- type JWTConfig
- type JWTService
- type MembershipService
- type OTPConfig
- type OTPService
- type PasskeyService
- type PasswordConfig
- type PasswordService
- type RateLimiter
- type SecretConfig
- type SecretService
- type SessionConfig
- type SessionMode
- type SessionService
- type SignInHistoryService
- type SigningKey
- type TenantService
- type TokenService
- type TwoFactorConfig
- type TwoFactorService
- type UserService
- type VerificationKey
- type VerifiedLoginService
Constants ¶
const ( // SessionDisabled is the default: no session tables, refresh tokens only. SessionDisabled = model.SessionDisabled // SessionOptional allows logins with and without an installation. SessionOptional = model.SessionOptional // SessionRequired refuses logins that bind no installation. SessionRequired = model.SessionRequired )
The session modes, for SessionConfig.Mode.
const ( // DefaultAccessExpiry is how long an access token lives: 15 minutes. DefaultAccessExpiry = time.Minute * 15 // DefaultRefreshExpiry is how long a refresh token lives: 7 days. DefaultRefreshExpiry = time.Hour * 168 // DefaultOTPRateLimitPeriod is the built-in cooldown between two OTPs for the // same email and purpose: one minute. DefaultOTPRateLimitPeriod = time.Minute * 1 // MinSecretLength is the minimum length (in bytes) required for the JWT // signing secrets and HMAC secrets. HS256/HMAC-SHA256 security depends on a // key with at least the hash output size of entropy (256 bits = 32 bytes, // per RFC 7518). Shorter secrets are brute-forceable offline against any // captured token, so they are rejected at construction time. MinSecretLength = 32 // HighBcryptCostThreshold is the bcrypt cost above which New logs a warning. // Costs above ~15 take well over 1s per hash on typical hardware; because every // login runs at least one bcrypt comparison (a real one or DummyVerify), an // excessive cost is a self-inflicted denial-of-service rather than added safety. HighBcryptCostThreshold = 15 )
Defaults New applies to a zero JWTConfig.AccessExpiry, JWTConfig.RefreshExpiry and OTPConfig.RateLimitPeriod.
Variables ¶
var ( ErrSessionDisabled = model.ErrSessionDisabled ErrSessionRequired = model.ErrSessionRequired ErrSessionRevoked = model.ErrSessionRevoked )
Installation-session sentinels (docs/installation-sessions.md).
var ( ErrIdentityLinkRequired = model.ErrIdentityLinkRequired ErrIdentityAlreadyLinked = model.ErrIdentityAlreadyLinked ErrEmailNotVerified = model.ErrEmailNotVerified )
External-identity sentinels (docs/verified-sign-in.md).
var ( ErrPasskeyAlreadyRegistered = model.ErrPasskeyAlreadyRegistered ErrPasskeyCloned = model.ErrPasskeyCloned )
Passkey sentinels (docs/passkeys.md).
var ( ErrSecondFactorRequired = model.ErrSecondFactorRequired ErrInvalidSecondFactor = model.ErrInvalidSecondFactor ErrTwoFactorAlreadyEnabled = model.ErrTwoFactorAlreadyEnabled ErrTwoFactorNotConfigured = model.ErrTwoFactorNotConfigured )
Two-factor sentinels (docs/two-factor.md).
var ( ErrNotFound = model.ErrNotFound ErrInvalidCredentials = model.ErrInvalidCredentials ErrAccountDisabled = model.ErrAccountDisabled ErrAccountLocked = model.ErrAccountLocked ErrInvalidToken = model.ErrInvalidToken ErrTokenRevoked = model.ErrTokenRevoked ErrNoActiveMembership = model.ErrNoActiveMembership ErrLastRoleHolder = model.ErrLastRoleHolder ErrInvalidRefreshToken = model.ErrInvalidRefreshToken ErrRefreshTokenReuse = model.ErrRefreshTokenReuse ErrInvalidOTP = model.ErrInvalidOTP ErrOTPLockedOut = model.ErrOTPLockedOut ErrOTPRateLimited = model.ErrOTPRateLimited ErrOTPSecretMissing = model.ErrOTPSecretMissing ErrInvalidAPIKey = model.ErrInvalidAPIKey ErrIncorrectPassword = model.ErrIncorrectPassword ErrInvitationInvalid = model.ErrInvitationInvalid ErrUserExists = model.ErrUserExists ErrTenantExists = model.ErrTenantExists ErrPasswordTooWeak = model.ErrPasswordTooWeak ErrSecretEncryptionKeyMissing = model.ErrSecretEncryptionKeyMissing ErrRateLimited = model.ErrRateLimited ErrInvalidInput = model.ErrInvalidInput ErrValueTooLong = model.ErrValueTooLong ErrInternal = model.ErrInternal )
Sentinel errors re-exported from the model package for convenience, so callers can write errors.Is(err, authkit.ErrInvalidCredentials). These are the same values the services return (model.Err*), so matching against either works.
Functions ¶
func Migrations ¶ added in v0.21.0
Migrations returns authkit's SQL migrations as a flat file system of numbered pairs, 000001_identity_schema.up.sql and 000001_identity_schema.down.sql onwards: the layout golang-migrate reads (source/iofs) and other tools that run NNNNNN_name.up.sql files in order. Apply every up migration in order; the ones only some features need just add tables and columns that stay unused otherwise. See docs/migrations.md.
schema is the DatabaseConnection.IdentitySchema the application configures. "" and "authkit" return the files exactly as shipped. Any other valid identifier returns the same SQL with that schema, quoted, wherever it names the authkit schema; the optional authkit_rw and authkit_ro roles it grants to keep their names.
A migration that has shipped in a release never changes; schema changes arrive as new numbered migrations.
func ParsePrivateKeyPEM ¶ added in v0.21.0
ParsePrivateKeyPEM parses one PEM-encoded private key for JWTConfig.SigningKey: PKCS #8 ("PRIVATE KEY", what openssl genpkey writes), SEC 1 ("EC PRIVATE KEY") or PKCS #1 ("RSA PRIVATE KEY"). It refuses encrypted keys and anything after the key. New checks the key's type, curve and size.
func ParsePublicKeyPEM ¶ added in v0.21.0
ParsePublicKeyPEM parses one PEM-encoded public key for JWTConfig.VerificationKeys: PKIX ("PUBLIC KEY", what openssl pkey -pubout writes) or PKCS #1 ("RSA PUBLIC KEY"). New checks the key's type, curve and size.
func WithTx ¶ added in v0.21.0
WithTx returns a copy of ctx that makes authkit run its queries in tx, so what a call changes commits or rolls back with the caller's own writes in the same transaction: an outbox row recording the change, say. tx must be a transaction on the identity schema's database (Config.DBConnection). Where authkit would begin a transaction of its own, it sets a savepoint in tx instead, and reads go to tx too, so they see what the caller wrote before them.
Signing in and checking a credential ignore tx and commit on their own, because what they record (failed attempts, lockouts, revoked token families, used codes and signature counters) must stay recorded whatever the caller does with tx: the AuthService, InstallationAuthService and VerifiedLoginService sign-ins, AuthService.MintWorkspaceToken, RefreshToken, ValidateJWT and ChangePassword, the TokenService calls that issue or check a token, OTPService.Generate and Validate, TwoFactorService.ConfirmTOTP, Verify and CompleteSecondFactor, APIKeyService.Validate and PasskeyService.RecordUse.
An error from a call may leave tx unusable, since Postgres aborts a transaction whose statement fails, so roll tx back when a call fails. A transaction-scoped advisory lock authkit takes is held until tx ends. See docs/transactions.md.
Types ¶
type APIKeyConfig ¶
type APIKeyConfig struct {
HMACSecret string // Secret key for HMAC-SHA256 hashing of API keys
}
APIKeyConfig configures API key hashing. HMACSecret is required and must be at least MinSecretLength bytes of high-entropy random data.
type APIKeyService ¶
type APIKeyService interface {
Create(ctx context.Context, req model.CreateAPIKeyRequest) (*model.CreateAPIKeyResponse, error)
Validate(ctx context.Context, apiKey string) (*model.APIKeyContext, error)
List(ctx context.Context, tenantID string, includeExpired bool) ([]*model.APIKey, error)
Get(ctx context.Context, keyID, tenantID string) (*model.APIKey, error)
Revoke(ctx context.Context, keyID, tenantID string) error
Count(ctx context.Context, tenantID string) (int, error)
CleanupExpired(ctx context.Context) (int64, error)
}
APIKeyService manages tenant-scoped API keys (creation, validation, revocation).
type AuthKit ¶
type AuthKit struct {
// contains filtered or unexported fields
}
AuthKit is the library entrypoint created by New. It is safe for concurrent use and holds the configured connections, secrets, and options. Obtain the individual services through its accessor methods (AuthService, UserService, TokenService, etc.).
func New ¶
New validates the Config and returns a configured AuthKit. It fails fast if a required connection or secret is missing, a secret is shorter than MinSecretLength, a signing or verification key has an unsupported type, curve or size or reuses another key's id, the refresh grace period is negative, the identity schema is not a valid identifier, the secret encryption key (when provided) is not a base64-encoded 32-byte key, or the OTP HMAC secret (when provided) is shorter than MinSecretLength. The OTP HMAC secret is only required at use time (OTPService.Generate/Validate), not at construction.
func (*AuthKit) APIKeyService ¶
func (k *AuthKit) APIKeyService() APIKeyService
APIKeyService returns the API key service for managing tenant-scoped API keys (creation, validation, revocation).
func (*AuthKit) AuthService ¶
func (k *AuthKit) AuthService() AuthService
AuthService returns the authentication service for complete authentication flows (login, logout, token refresh, JWT validation).
func (*AuthKit) ExternalIdentityService ¶ added in v0.21.0
func (k *AuthKit) ExternalIdentityService() ExternalIdentityService
ExternalIdentityService links accounts at outside sign-in providers (Google, GitHub, enterprise OIDC) to users. Requires migration 000004.
func (*AuthKit) InTx ¶ added in v0.21.0
InTx runs fn in a new transaction on the read-write identity database, with a context that makes authkit's calls run in it (WithTx), and commits it if fn returns nil. Otherwise it rolls the transaction back and returns fn's error.
func (*AuthKit) InstallationAuthService ¶ added in v0.20.2
func (k *AuthKit) InstallationAuthService() InstallationAuthService
InstallationAuthService adds enrollment-bound login and verified bindings. Existing AuthService interfaces remain unchanged.
func (*AuthKit) InvitationService ¶
func (k *AuthKit) InvitationService() InvitationService
InvitationService returns the invitation service for invitation management, validation, and lifecycle operations.
func (*AuthKit) JSONWebKeySet ¶ added in v0.21.0
func (k *AuthKit) JSONWebKeySet() JSONWebKeySet
JSONWebKeySet returns the public keys that validate access tokens, as a JSON Web Key Set (RFC 7517) to serve at a jwks_uri such as /.well-known/jwks.json, so other services and API gateways can check tokens without a shared secret. The signing key comes first, then JWTConfig.VerificationKeys. The HS256 secret is never included: the set is empty for a kit that signs with AccessSecret alone.
A service that checks tokens this way sees only the signature, expiry, issuer and audience. It does not see revocation (token versions, disabled accounts, revoked sessions): AuthService.ValidateJWT checks those against the database.
func (*AuthKit) JWTService ¶
func (k *AuthKit) JWTService() JWTService
JWTService returns the JWT service for generating and validating JWT access tokens and refresh tokens.
func (*AuthKit) MembershipService ¶
func (k *AuthKit) MembershipService() MembershipService
MembershipService returns the membership service for managing the user<->tenant membership graph (grant/revoke, roles, member lists).
func (*AuthKit) OTPService ¶
func (k *AuthKit) OTPService() OTPService
OTPService returns the OTP service for OTP generation, validation, and lifecycle management with rate limiting.
func (*AuthKit) PasskeyService ¶ added in v0.21.0
func (k *AuthKit) PasskeyService() PasskeyService
PasskeyService stores WebAuthn credentials (passkeys and security keys) and the user handles they carry; the application runs the WebAuthn ceremonies. Requires migration 000008; see docs/passkeys.md.
func (*AuthKit) PasswordService ¶
func (k *AuthKit) PasswordService() PasswordService
PasswordService returns the password service for password hashing and verification using bcrypt.
func (*AuthKit) Ping ¶
Ping verifies connectivity to both the read-write and read-only identity databases by issuing a PingContext to each. It is intended for readiness/health probes. New performs this check at construction only when Config.VerifyConnectivity is set; otherwise connection errors surface lazily on the first query.
func (*AuthKit) SecretService ¶
func (k *AuthKit) SecretService() SecretService
SecretService returns the secret service for managing encrypted secrets (provider API keys, etc.).
IMPORTANT: SecretConfig.EncryptionKey or SecretConfig.TenantKey must be configured for this service to work. The encryption key must be a base64-encoded 32-byte (256-bit) key, and a tenant's key 32 bytes. Operations return ErrSecretEncryptionKeyMissing if it is absent or the wrong length.
func (*AuthKit) SessionService ¶ added in v0.20.2
func (k *AuthKit) SessionService() SessionService
SessionService manages sessions scoped to the configured application namespace. Callers must authorize session-management actions for the supplied user.
func (*AuthKit) SignInHistoryService ¶ added in v0.21.0
func (k *AuthKit) SignInHistoryService() SignInHistoryService
SignInHistoryService reads the sign-in history that Config.RecordSignIns records. Requires migration 000007; see docs/sign-in-history.md.
func (*AuthKit) TenantService ¶
func (k *AuthKit) TenantService() TenantService
TenantService returns the tenant service for tenant management, settings, and domain validation.
func (*AuthKit) TokenService ¶
func (k *AuthKit) TokenService() TokenService
TokenService returns the token service for combined JWT and refresh token management with token rotation.
func (*AuthKit) TwoFactorService ¶ added in v0.21.0
func (k *AuthKit) TwoFactorService() TwoFactorService
TwoFactorService sets up two-factor sign-in with an authenticator app and recovery codes, and finishes the password logins that wait for a second factor. Requires Config.TwoFactorConfig (without it every call returns ErrTwoFactorNotConfigured) and migration 000009; see docs/two-factor.md.
func (*AuthKit) UserService ¶
func (k *AuthKit) UserService() UserService
UserService returns the user service for global-identity management, queries, and account status operations.
func (*AuthKit) VerifiedLoginService ¶ added in v0.21.0
func (k *AuthKit) VerifiedLoginService() VerifiedLoginService
VerifiedLoginService opens sessions for users the application has verified itself (OAuth/OIDC, email links, OTPs, passkeys, device codes). It works in every session mode; see docs/verified-sign-in.md.
type AuthService ¶
type AuthService interface {
// Login authenticates into a known workspace (req.TenantID) and returns a
// workspace-scoped access token + identity-session refresh token. A user with
// two-factor on gets ErrSecondFactorRequired instead (TwoFactorService).
Login(ctx context.Context, req model.LoginRequest) (*model.LoginResponse, error)
// LoginByEmail authenticates the identity without a known workspace: it opens the
// session and either mints a token (when the workspace is given or the identity
// has exactly one active membership) or returns the membership list to choose from.
LoginByEmail(ctx context.Context, req model.LoginByEmailRequest) (*model.LoginResponse, error)
// MintWorkspaceToken mints a fresh workspace-scoped access token from an existing
// identity session (the refresh token) — the select-/switch-workspace call.
MintWorkspaceToken(ctx context.Context, refreshToken, tenantID string) (*model.WorkspaceTokenResponse, error)
Logout(ctx context.Context, refreshToken string) error
// LogoutAll invalidates every workspace's access tokens and revokes all refresh
// tokens for an identity (identity-level; the caller authorizes userID).
LogoutAll(ctx context.Context, userID string) error
RefreshToken(ctx context.Context, req model.RefreshRequest) (*model.RefreshResponse, error)
ValidateJWT(ctx context.Context, token string) (*model.JWTClaims, error)
// ChangePassword sets a new password after checking the old one, and signs the
// identity out everywhere. A wrong old password counts toward the failed-login
// lockout.
ChangePassword(ctx context.Context, req model.ChangePasswordRequest) error
}
AuthService provides the end-to-end authentication flows (login, logout, token refresh, JWT validation, password change).
Rate limiting is caller-owned. Login enforces only a per-account failed-login lockout, which does NOT stop password spraying (a few guesses each across many accounts) or distributed guessing, and RefreshToken has no built-in throttle. The embedding application MUST add IP-/client-based rate limiting in front of these methods. See SECURITY.md ("Caller-Owned Controls").
type Config ¶
type Config struct {
SessionConfig SessionConfig // Optional; disabled by default, requires migrations 000002 and 000003 when enabled.
DBConnection DatabaseConnection
JWTConfig JWTConfig
APIKeyConfig APIKeyConfig
PasswordConfig PasswordConfig
OTPConfig OTPConfig
SecretConfig SecretConfig // Optional: Configuration for SecretService
// TwoFactorConfig is optional; it turns on two-factor sign-in (TwoFactorService)
// and requires migration 000009.
TwoFactorConfig TwoFactorConfig
// Logger is optional and defaults to a no-op that discards everything. NOTE
// that several security signals are logger-only: OTP lockouts, refresh-token
// reuse, and sub-threshold failed logins are emitted only via the logger and
// are LOST if none is injected. (Account lock/disable/enable transitions, by
// contrast, are persisted to the user_status_changes audit table regardless.)
// Inject a Logger in production, and/or supply Metrics below to capture the
// theft/abuse counters that are not covered by the status-change audit.
Logger logger.Logger
// Metrics is an optional counter sink for auth security/operational events
// (login success/failure, account lockout, OTP failure/lockout, refresh-token
// reuse, rate-limit rejections/limiter errors, and best-effort write failures).
// Defaults to a no-op. See the metrics package Counter* constants for the full,
// authoritative list. Unlike the logger, these survive independently of log
// level, so they are the recommended basis for alerting.
Metrics metrics.Metrics
// VerifyConnectivity, when true, makes New ping both database handles (with a
// short internal timeout) and fail if either is unreachable, instead of
// deferring the first connection error to the first query. Off by default so
// construction stays cheap and offline-testable; turn it on for fail-fast
// startup, or call AuthKit.Ping from a readiness probe.
VerifyConnectivity bool
// RateLimiter is an optional pluggable throttle consulted by AuthService.Login,
// RefreshToken, OTPService.Generate, and TwoFactorService.CompleteSecondFactor
// before they do work. It is the
// recommended place to enforce the IP/global rate limiting the library does not
// do itself (the per-account lockout is not enough — see SECURITY.md). Defaults
// to no limiting. See the RateLimiter interface for the key convention.
RateLimiter RateLimiter
// RecordSignIns, when true, records every sign-in attempt on an existing
// account (password logins and VerifiedLoginService.OpenSession, successful or
// refused) with its method, workspace, session, and the IP address, user agent
// and device name from SessionInfo. Read it with SignInHistoryService. Off by
// default. Needs migration 000007. See docs/sign-in-history.md.
RecordSignIns bool
}
Config is the configuration passed to New to construct an AuthKit. The DBConnection and the JWT/API-key secrets are required; the remaining fields have sensible defaults. See each sub-struct for details.
type DatabaseConnection ¶
type DatabaseConnection struct {
// Identity schema connections (authkit)
IdentityRWDB *sql.DB // For user/session management and audit logging (read-write)
IdentityRODB *sql.DB // For auth lookups (SELECT only)
IdentitySchema string // Optional PostgreSQL schema name (defaults to authkit)
}
DatabaseConnection holds the PostgreSQL handles and schema for the identity store. The read-write and read-only handles may be the same *sql.DB.
type ExternalIdentityService ¶ added in v0.21.0
type ExternalIdentityService interface {
Resolve(ctx context.Context, req model.ResolveExternalIdentityRequest) (*model.ResolvedExternalIdentity, error)
Link(ctx context.Context, req model.LinkExternalIdentityRequest) (*model.ExternalIdentity, error)
Unlink(ctx context.Context, userID, provider, subject string) error
Get(ctx context.Context, provider, subject string) (*model.ExternalIdentity, error)
ListByUser(ctx context.Context, userID string) ([]*model.ExternalIdentity, error)
}
ExternalIdentityService links accounts at outside sign-in providers to authkit users and finds the user for a provider sign-in the application has verified. It does not implement OAuth or OIDC. Requires migration 000004.
type InstallationAuthService ¶ added in v0.20.2
type InstallationAuthService interface {
LoginWithInstallation(context.Context, model.InstallationLoginRequest) (*model.InstallationLoginResponse, error)
LoginByEmailWithInstallation(context.Context, model.InstallationLoginByEmailRequest) (*model.InstallationLoginResponse, error)
ValidateInstallationJWT(context.Context, string) (*model.InstallationClaims, error)
}
InstallationAuthService is additive: existing AuthService implementations and mocks need not implement these methods. Enrollment is verified by the caller.
type InvitationService ¶
type InvitationService interface {
Create(ctx context.Context, req model.CreateInvitationRequest) (*model.Invitation, error)
Get(ctx context.Context, code string) (*model.Invitation, error)
Validate(ctx context.Context, code string) (bool, error)
Accept(ctx context.Context, req model.AcceptInvitationRequest) (userID string, created bool, err error)
List(ctx context.Context, tenantID string) ([]*model.Invitation, error)
ListActive(ctx context.Context, tenantID string) ([]*model.Invitation, error)
Revoke(ctx context.Context, code string) error
Delete(ctx context.Context, code string) error
IncrementUseCount(ctx context.Context, code string) error
CleanupExpired(ctx context.Context) (int64, error)
HasPending(ctx context.Context, email, tenantID string) (bool, error)
}
InvitationService manages invitation lifecycle and redemption.
The by-code operations (Get, Validate, Accept, Revoke, Delete, IncrementUseCount) are keyed only by the invitation code, which is a high-entropy (~103-bit), non-enumerable bearer capability — possession of the code is the authorization to act on it, and the library does NOT additionally check the caller's tenant. Accept derives the new user's tenant and roles from the invitation row (not from caller input), so there is no privilege escalation. If your model requires that an admin may only manage invitations within their own tenant, resolve the code first with Get and check the returned TenantID before calling Revoke/Delete/IncrementUseCount.
type JSONWebKey ¶ added in v0.21.0
type JSONWebKey = model.JSONWebKey
JSONWebKey is one public key as a JSON Web Key.
type JSONWebKeySet ¶ added in v0.21.0
type JSONWebKeySet = model.JSONWebKeySet
JSONWebKeySet is what AuthKit.JSONWebKeySet returns, to serve at a jwks_uri.
type JWTConfig ¶
type JWTConfig struct {
// AccessSecret is the HS256 secret. With SigningKey set it only validates
// tokens signed before the switch; remove it once they have expired.
AccessSecret string
// SigningKey, when set, signs access tokens with an asymmetric key and stamps
// its key id. Optional.
SigningKey *SigningKey
// VerificationKeys are further public keys whose tokens validate: the next
// key, published before it signs, or the previous one, until its tokens have
// expired. Optional.
VerificationKeys []VerificationKey
RefreshTokenHMACSecret string // Secret for HMAC-SHA256 hashing of refresh tokens in storage
AccessExpiry time.Duration
RefreshExpiry time.Duration
// RefreshGracePeriod lets a client retry a refresh whose response it lost.
// Rotation consumes the presented refresh token, and presenting a consumed token
// again is treated as theft: the family is revoked and the user signed out
// everywhere. With a grace period, a consumed token can be exchanged again for
// up to this long, as long as the token that replaced it has never been used; the
// unused replacement is revoked. Once the replacement has been used, the old token
// is reuse as before, so whichever of a thief and the owner refreshes second still
// trips detection. Optional; zero keeps strict rotation. Needs migration 000006.
// See docs/refresh-tokens.md.
RefreshGracePeriod time.Duration
Issuer string // Optional: if set, stamped as the "iss" claim and required on validation
Audience string // Optional: if set, stamped as the "aud" claim and required on validation
}
JWTConfig configures access-token signing and refresh-token hashing. RefreshTokenHMACSecret is required, and so is AccessSecret unless SigningKey is set; each secret must be at least MinSecretLength bytes of high-entropy random data. Expiries default to DefaultAccessExpiry / DefaultRefreshExpiry when zero.
Access tokens are signed with SigningKey when it is set, and otherwise with AccessSecret (HS256, no key id). Validation accepts a token with a "kid" header only from the key with that id (SigningKey or one of VerificationKeys), and a token without one only as HS256 with AccessSecret. AuthKit.JSONWebKeySet publishes the public keys. See docs/signing-keys.md for moving off HS256 and for rotating keys without logging anyone out.
Note: refresh tokens are opaque random strings, not JWTs — they are not signed, only HMAC-hashed (with RefreshTokenHMACSecret) for storage. There is therefore no separate refresh-token signing secret.
type JWTService ¶
type JWTService interface {
GenerateAccessToken(user *model.User, membership *model.Membership) (string, error)
ValidateAccessToken(tokenString string) (*model.JWTClaims, error)
GenerateRefreshToken() (string, error)
GetAccessExpiry() time.Duration
GetRefreshExpiry() time.Duration
}
JWTService generates and validates JWT access tokens and opaque refresh tokens.
GenerateAccessToken copies the supplied user's identity fields (TokenVersion) and the membership's workspace fields (TenantID, Roles, membership TokenVersion) verbatim into the signed claims, so both must be complete, freshly loaded records — a partial/hand-built record can mint a token that ValidateJWT treats as revoked. Prefer the AuthService flows, which load both.
type MembershipService ¶
type MembershipService interface {
Add(ctx context.Context, req model.AddMembershipRequest) error
Remove(ctx context.Context, userID, tenantID string) error
// RemoveGuarded is Remove but refuses (model.ErrLastRoleHolder) to remove the
// tenant's last active member holding requireRole whose account is enabled (a
// disabled or deleted user does not count; a locked one does). The
// check-and-revoke is atomic and serialized per tenant, so a workspace can never
// be raced below one such holder of the role (e.g. its last Admin). Passing ""
// disables the guard. UserService.Disable is not guarded.
RemoveGuarded(ctx context.Context, userID, tenantID, requireRole string) error
UpdateRoles(ctx context.Context, userID, tenantID string, roles []string) error
// UpdateRolesGuarded is UpdateRoles with the same last-holder guard as
// RemoveGuarded: it refuses (model.ErrLastRoleHolder) to demote the tenant's last
// active holder of requireRole. Passing "" disables the guard.
UpdateRolesGuarded(ctx context.Context, userID, tenantID string, roles []string, requireRole string) error
Get(ctx context.Context, userID, tenantID string) (*model.Membership, error)
ListByUser(ctx context.Context, userID string) ([]*model.Membership, error)
ListByTenant(ctx context.Context, tenantID string) ([]*model.Membership, error)
CountByTenant(ctx context.Context, tenantID string) (int, error)
}
MembershipService manages the user<->tenant membership graph: which global identity belongs to which tenant, with which (opaque) roles. authkit owns the graph; the embedding application interprets the role strings. Remove is a soft-revoke (the membership is suspended and its token_version bumped); Add is an idempotent upsert that re-grants/reactivates.
type OTPConfig ¶
type OTPConfig struct {
RateLimitDuration time.Duration // Minimum time between OTP generation attempts (default: 1 minute)
HMACSecret string // Required for OTPService: HMAC-SHA256 hashing of OTP codes in storage
}
OTPConfig configures OTP generation behavior.
HMACSecret is required when OTPService is used: OTP codes are stored as a keyed HMAC-SHA256 (not in plaintext), so the secret must be at least MinSecretLength bytes of high-entropy random data. It is validated for length at construction when non-empty; OTPService.Generate/Validate return ErrOTPSecretMissing if it is absent. Use a value distinct from the JWT/API-key/encryption secrets.
type OTPService ¶
type OTPService interface {
Generate(ctx context.Context, req model.OTPGenerateRequest) (*model.GenerateResponse, error)
Validate(ctx context.Context, req model.OTPValidateRequest) error
Delete(ctx context.Context, email, purpose string) error
CleanupExpired(ctx context.Context) (int64, error)
}
OTPService manages one-time verification codes with rate limiting and lockout.
type PasskeyService ¶ added in v0.21.0
type PasskeyService interface {
// User returns the user, their user handle at rpID (created on first use) and
// their passkeys there, for a registration or a sign-in that names the user.
User(ctx context.Context, userID, rpID string) (*model.PasskeyUser, error)
// UserByHandle is User for the user a discoverable sign-in's user handle names.
UserByHandle(ctx context.Context, rpID string, userHandle []byte) (*model.PasskeyUser, error)
// Register stores a passkey from a verified registration ceremony.
Register(ctx context.Context, req model.RegisterPasskeyRequest) (*model.Passkey, error)
// RecordUse writes back a verified sign-in's counter and flags. It returns
// ErrPasskeyCloned, and records nothing, when the counter did not go up.
RecordUse(ctx context.Context, req model.PasskeyUseRequest) error
// List returns a user's passkeys at rpID, for display.
List(ctx context.Context, userID, rpID string) ([]*model.Passkey, error)
Rename(ctx context.Context, userID, rpID string, credentialID []byte, name string) error
Remove(ctx context.Context, userID, rpID string, credentialID []byte) error
}
PasskeyService stores WebAuthn credentials (passkeys and security keys) and the user handle each user carries at a relying party. It does not run WebAuthn ceremonies: the application verifies them with its own WebAuthn library and stores and reads the results here, then opens the session with VerifiedLoginService.OpenSession. The embedding application must authorize the actor before passing a userID. Requires migration 000008; see docs/passkeys.md.
type PasswordConfig ¶
type PasswordConfig struct {
BcryptCost int // Cost factor for bcrypt hashing (default: 12, range: 4-31)
}
PasswordConfig configures password hashing.
type PasswordService ¶
type PasswordService interface {
HashPassword(password string) (string, error)
VerifyPassword(password, hash string) error
DummyVerify(password string)
ValidatePasswordStrength(password string, requirements model.PasswordStrengthRequirements) error
}
PasswordService hashes and verifies passwords and validates password strength.
type RateLimiter ¶
type RateLimiter = ratelimit.RateLimiter
RateLimiter is the optional throttle injected via Config.RateLimiter. It is an alias for ratelimit.RateLimiter so callers can refer to authkit.RateLimiter.
type SecretConfig ¶
type SecretConfig struct {
EncryptionKey string // Base64-encoded 32-byte AES-256 key for encrypting every tenant's secrets
// TenantKey, in place of EncryptionKey, gives each tenant a 32-byte AES-256 key of
// its own, so a key that leaks exposes only its tenant's secrets, and a tenant's
// secrets are unreadable once its key is destroyed. SecretService calls it for every
// Save and Get, so keep keys in memory (unwrapping them from a KMS only now and
// then). Its error comes back from Save and Get, wrapped; a key that is not 32 bytes
// is ErrSecretEncryptionKeyMissing. Secrets are stored as with EncryptionKey, so
// returning the old EncryptionKey for each existing tenant keeps what it stored
// readable. See docs/secrets.md.
TenantKey func(ctx context.Context, tenantID string) ([]byte, error)
}
SecretConfig configures the optional SecretService, which needs EncryptionKey or TenantKey, not both.
type SecretService ¶
type SecretService interface {
Save(ctx context.Context, req model.SaveSecretRequest) (*model.SaveSecretResponse, error)
Get(ctx context.Context, tenantID, key string) (string, error)
List(ctx context.Context, tenantID string) ([]*model.Secret, error)
Delete(ctx context.Context, tenantID, key string) error
MarkVerified(ctx context.Context, tenantID, key string) error
Count(ctx context.Context, tenantID string) (int, error)
}
SecretService manages tenant-scoped encrypted secrets (provider API keys, etc.).
type SessionConfig ¶ added in v0.20.2
type SessionConfig = model.SessionConfig
SessionConfig turns on installation-bound sessions (docs/installation-sessions.md).
type SessionMode ¶ added in v0.20.2
type SessionMode = model.SessionMode
SessionMode is how installation sessions are enforced; see SessionConfig.
type SessionService ¶ added in v0.20.2
type SessionService interface {
Get(ctx context.Context, userID, sessionID string) (*model.Session, error)
List(ctx context.Context, userID string, limit, offset int) ([]model.Session, error)
Revoke(ctx context.Context, userID, sessionID string) error
RevokeInstallation(ctx context.Context, userID, installationID string) error
CleanupExpired(ctx context.Context) (int64, error)
}
SessionService is scoped to Config.SessionConfig.Namespace. The embedding application must authorize the actor before passing a target userID. List/Get return records, including expired/revoked ones, not authentication.
type SignInHistoryService ¶ added in v0.21.0
type SignInHistoryService interface {
// List returns a page of the user's sign-in attempts, newest first. limit is 1
// to 200; anything else is ErrInvalidInput.
List(ctx context.Context, userID string, limit, offset int) ([]*model.SignInEvent, error)
// Cleanup deletes attempts older than olderThan (which must be positive) and
// returns how many it removed. Run it periodically to bound the history.
Cleanup(ctx context.Context, olderThan time.Duration) (int64, error)
}
SignInHistoryService reads the sign-in history recorded when Config.RecordSignIns is set: one SignInEvent per attempt on an existing account, successful or refused. It is identity-wide, covering every application that shares the identity database. The embedding application must authorize the actor before passing a target userID. Requires migration 000007; see docs/sign-in-history.md.
type SigningKey ¶ added in v0.21.0
type SigningKey = model.SigningKey
SigningKey is the private key JWTConfig.SigningKey signs access tokens with.
type TenantService ¶
type TenantService interface {
Get(ctx context.Context, tenantID string) (*model.Tenant, error)
GetByDomain(ctx context.Context, domain string) (*model.Tenant, error)
Create(ctx context.Context, req model.CreateTenantRequest) (*model.Tenant, error)
// UpdateSettings replaces the tenant's settings, stored as JSONB. The map is
// an open, application-defined key/value bag (keys are arbitrary strings;
// values must be JSON-serializable) — the library does not interpret or
// validate its contents beyond requiring that it marshals to JSON. It is
// returned verbatim as Tenant.Settings (a map[string]any) by Get.
UpdateSettings(ctx context.Context, tenantID string, settings map[string]interface{}) error
UpdateName(ctx context.Context, tenantID, name string) error
SetAllowedDomains(ctx context.Context, tenantID string, domains []string) error
AddAllowedDomain(ctx context.Context, tenantID, domain string) error
List(ctx context.Context) ([]*model.Tenant, error)
Exists(ctx context.Context, tenantID string) (bool, error)
ValidateDomainAccess(ctx context.Context, email, tenantID string) error
}
TenantService manages tenants, their settings, and allowed email domains.
Get, GetByDomain, and List return the tenant's full Settings map (the application-defined JSONB bag) and apply no caller authorization — they are global/admin reads. GetByDomain in particular resolves a tenant from a caller-supplied email domain, so do not wire it (or the Settings it returns) into an unauthenticated lookup; treat Settings as potentially sensitive.
type TokenService ¶
type TokenService interface {
GenerateTokenPair(ctx context.Context, user *model.User, membership *model.Membership, sessionInfo *model.SessionInfo) (*model.TokenPairResponse, error)
ValidateRefreshToken(ctx context.Context, tokenString string) (*model.User, error)
RefreshTokenPair(ctx context.Context, refreshToken string, tenantID string, sessionInfo *model.SessionInfo) (*model.TokenPairResponse, error)
RevokeRefreshToken(ctx context.Context, tokenString string) error
// RevokeAllUserTokens is intentionally NOT exposed here: it revokes every refresh
// token for a user id without any authorization check. Use AuthService.LogoutAll(ctx,
// userID), which is identity-level — it bumps the identity token_version (invalidating
// every workspace's access tokens) and then revokes the user's refresh tokens; the
// caller is responsible for authorizing that it may act on userID.
CleanupExpired(ctx context.Context) (int64, error)
}
TokenService provides combined JWT + refresh-token management with rotation.
As with JWTService, GenerateTokenPair stamps the supplied *model.User's fields into the access token, so pass a complete, freshly loaded record (see JWTService).
type TwoFactorConfig ¶ added in v0.21.0
type TwoFactorConfig struct {
// EncryptionKey is a base64-encoded 32-byte random key, used for nothing else
// (not SecretConfig.EncryptionKey). authkit derives from it the AES-256-GCM key
// that encrypts authenticator secrets and the HMAC key that recovery codes are
// stored under. Keep it as you keep your other keys: with a different key, users
// with two-factor on cannot finish a password login until it is turned off for
// them (TwoFactorService.Disable).
EncryptionKey string
// Issuer names your service in people's authenticator apps, such as "Acme".
// It is required with EncryptionKey and may not contain a colon.
Issuer string
}
TwoFactorConfig turns on two-factor sign-in with an authenticator app (TOTP) and recovery codes. Two-factor is off while EncryptionKey is empty: then no login asks for a second factor and TwoFactorService returns ErrTwoFactorNotConfigured. Requires migration 000009. See docs/two-factor.md.
type TwoFactorService ¶ added in v0.21.0
type TwoFactorService interface {
// EnrollTOTP makes a new authenticator secret for the user, not yet on, and
// returns it to show as a QR code.
EnrollTOTP(ctx context.Context, userID string) (*model.TOTPEnrollment, error)
// ConfirmTOTP turns two-factor on with a first code from the app and returns
// the user's recovery codes, which cannot be read back later.
ConfirmTOTP(ctx context.Context, userID, code string) ([]string, error)
// Verify checks a code or recovery code for a signed-in user with two-factor
// on, before a sensitive change. A wrong one counts toward the failed-login
// lockout.
Verify(ctx context.Context, userID, code string) error
// Disable turns two-factor off and deletes the user's secret and codes.
Disable(ctx context.Context, userID string) error
// RegenerateRecoveryCodes replaces the user's recovery codes.
RegenerateRecoveryCodes(ctx context.Context, userID string) ([]string, error)
// Status reports whether two-factor is on and how many recovery codes are left.
Status(ctx context.Context, userID string) (*model.TwoFactorStatus, error)
// CompleteSecondFactor finishes a login that returned ErrSecondFactorRequired.
CompleteSecondFactor(ctx context.Context, req model.CompleteSecondFactorRequest) (*model.InstallationLoginResponse, error)
// CleanupExpired deletes logins that expired waiting for a code.
CleanupExpired(ctx context.Context) (int64, error)
}
TwoFactorService sets up two-factor sign-in with an authenticator app (TOTP) and recovery codes, and finishes the password logins that then wait for a second factor: Login, LoginByEmail and their installation variants return ErrSecondFactorRequired, carrying a challenge, for a user who has it on. Sessions opened with VerifiedLoginService.OpenSession are not affected. The embedding application must authorize the actor before passing a userID. Requires Config.TwoFactorConfig and migration 000009; see docs/two-factor.md.
type UserService ¶
type UserService interface {
Create(ctx context.Context, req model.CreateUserRequest) (string, error)
Update(ctx context.Context, req model.UpdateUserRequest) error
Delete(ctx context.Context, userID string) error
GetByID(ctx context.Context, userID string) (*model.User, error)
GetByEmail(ctx context.Context, email string) (*model.User, error)
Exists(ctx context.Context, email string) (bool, error)
// List is a global (cross-tenant) admin listing; for a tenant's members use
// MembershipService.ListByTenant.
List(ctx context.Context, limit, offset int) ([]*model.User, error)
ExistsActive(ctx context.Context, email string) (bool, error)
GetDisplayName(ctx context.Context, userID string) (string, error)
GetActiveUserIDByEmail(ctx context.Context, email string) (string, error)
Enable(ctx context.Context, userID string) error
Disable(ctx context.Context, userID string) error
Lock(ctx context.Context, userID string, lockedUntil *time.Time) error
Unlock(ctx context.Context, userID string) error
SetPassword(ctx context.Context, userID, hashedPassword string) error
VerifyPassword(ctx context.Context, userID string) (string, error)
// MarkEmailVerified records that the user proved they own their email address
// (a code or link sent to it). Needs migration 000005. See
// docs/verified-sign-in.md.
MarkEmailVerified(ctx context.Context, userID string) error
UpdateLastLogin(ctx context.Context, userID string) error
IncrementFailedLoginCount(ctx context.Context, userID string) error
IncrementTokenVersion(ctx context.Context, userID string) error
// StatusHistory returns a user's account status-change audit trail (newest
// first). CleanupStatusChanges deletes audit rows older than olderThan (which must
// be positive) and returns the count removed; run it periodically to bound the
// otherwise unbounded audit table.
StatusHistory(ctx context.Context, userID string) ([]*model.UserStatusChange, error)
CleanupStatusChanges(ctx context.Context, olderThan time.Duration) (int64, error)
}
UserService manages GLOBAL identities, their queries, and account status operations. A user carries no tenant or roles (those live on memberships — see MembershipService); every operation is keyed by the global user id or the globally-unique email.
Convention: create/update operations take a keyed request struct with the identity inside (Create, Update, plus AuthService.ChangePassword); simple get/delete/status operations take a positional userID (or email for lookups).
type VerificationKey ¶ added in v0.21.0
type VerificationKey = model.VerificationKey
VerificationKey is a public key JWTConfig.VerificationKeys accepts tokens from.
type VerifiedLoginService ¶ added in v0.21.0
type VerifiedLoginService interface {
OpenSession(ctx context.Context, req model.OpenSessionRequest) (*model.OpenSessionResponse, error)
}
VerifiedLoginService opens sessions for users the application has verified itself: through an OAuth or OIDC provider, a signed email link, an OTP, a passkey or an approved device code. authkit checks no credential here, so OpenSession must only ever receive a user id from the application's own verification, never one taken from a request. It is additive, like InstallationAuthService. See docs/verified-sign-in.md.
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
internal
|
|
|
Package metrics defines a minimal, optional instrumentation seam for authkit-go.
|
Package metrics defines a minimal, optional instrumentation seam for authkit-go. |
|
Package model holds the data types that cross authkit-go's public API — the domain entities returned to callers (User, Tenant, APIKey, Invitation, Secret, JWTClaims, UserStatusChange) and the request/response structs (including SessionInfo) used to call the services.
|
Package model holds the data types that cross authkit-go's public API — the domain entities returned to callers (User, Tenant, APIKey, Invitation, Secret, JWTClaims, UserStatusChange) and the request/response structs (including SessionInfo) used to call the services. |
|
Package ratelimit defines the optional throttle seam for authkit-go.
|
Package ratelimit defines the optional throttle seam for authkit-go. |