Documentation
¶
Overview ¶
Package authkit provides plug-and-play OAuth authentication with RBAC for Go HTTP services. It wraps markbates/goth for the OAuth dance, gorilla/sessions for encrypted cookie sessions, and a YAML-based role policy for access control.
Quick start:
auth, err := authkit.New(authkit.Config{
Providers: []authkit.ProviderConfig{
{Name: "github", ClientID: os.Getenv("GITHUB_CLIENT_ID"), ClientSecret: os.Getenv("GITHUB_CLIENT_SECRET")},
},
CallbackBaseURL: "https://example.com",
SessionSecret: os.Getenv("SESSION_SECRET"),
RBAC: authkit.RBACConfig{FilePath: "policy.yaml"},
})
mux.Handle("GET /auth/{provider}", http.HandlerFunc(auth.BeginAuth))
mux.Handle("GET /auth/{provider}/callback", http.HandlerFunc(auth.Callback))
mux.Handle("POST /auth/logout", http.HandlerFunc(auth.Logout))
mux.Handle("GET /auth/me", http.HandlerFunc(auth.Me))
// Protected routes
mux.Handle("GET /api/reports", auth.RequireAuth(reportsHandler))
mux.Handle("POST /api/projects", auth.Require("projects:write")(createHandler))
Index ¶
- Constants
- Variables
- func CheckPassword(hashedPassword, password string) bool
- func HashPassword(password string) (string, error)
- func WithLogger(l Logger) func(*LayeredPolicyProvider)
- type APIKeyValidator
- type Auth
- func (a *Auth) BeginAuth(w http.ResponseWriter, r *http.Request)
- func (a *Auth) Callback(w http.ResponseWriter, r *http.Request)
- func (a *Auth) Login(w http.ResponseWriter, r *http.Request)
- func (a *Auth) Logout(w http.ResponseWriter, r *http.Request)
- func (a *Auth) Me(w http.ResponseWriter, r *http.Request)
- func (a *Auth) Register(w http.ResponseWriter, r *http.Request)
- func (a *Auth) Require(permission string) func(http.Handler) http.Handler
- func (a *Auth) RequireAuth(next http.Handler) http.Handler
- func (a *Auth) RequireSession(permission string) func(http.Handler) http.Handler
- func (a *Auth) RequireSessionAuth(next http.Handler) http.Handler
- func (a *Auth) WatchRBAC(ctx context.Context, interval time.Duration)
- type AuthMode
- type Config
- type LayeredPolicyProvider
- func (l *LayeredPolicyProvider) DeleteOverride(ctx context.Context, email string) error
- func (l *LayeredPolicyProvider) PermissionsForRole(role string) []string
- func (l *LayeredPolicyProvider) Reload() error
- func (l *LayeredPolicyProvider) RoleFor(ctx context.Context, email string) (string, []string)
- func (l *LayeredPolicyProvider) SetOverride(ctx context.Context, email, role string, permissions []string) error
- func (l *LayeredPolicyProvider) Store() UserRoleStore
- type Logger
- type PasswordPolicy
- type PasswordUser
- type Policy
- type PolicyProvider
- type PolicyReloader
- type ProviderConfig
- type RBACConfig
- type RolePolicy
- type User
- type UserRoleStore
- type UserStore
Constants ¶
const ( // PermAll grants every permission check. Use "*" in policy.yaml to assign // it to a role. All other permission strings are user-defined. PermAll = "*" )
Variables ¶
var ( ErrUserExists = errors.New("authkit: user already exists") ErrUserNotFound = errors.New("authkit: user not found") )
Sentinel errors for UserStore implementations.
Functions ¶
func CheckPassword ¶
CheckPassword compares a plaintext password against a bcrypt hash.
func HashPassword ¶
HashPassword hashes a plaintext password using bcrypt. Exported so consumers can use it in admin/seed tooling.
func WithLogger ¶ added in v0.0.4
func WithLogger(l Logger) func(*LayeredPolicyProvider)
WithLogger configures a Logger for LayeredPolicyProvider. When set, DB errors during role lookups are logged so operators can detect store outages.
Example:
provider, err := authkit.NewLayeredProvider("policy.yaml", store,
authkit.WithLogger(myLogger),
)
Types ¶
type APIKeyValidator ¶ added in v0.0.3
APIKeyValidator validates a raw API key string and returns the associated user. Implementations look up the key hash in a store and return a *User with Email, Name, Provider, and Role populated. Authkit then resolves the user's permissions from the RBAC policy based on the returned Role.
Return nil, nil when the key is not found, expired, or inactive. Return nil, err only for unexpected infrastructure failures.
type Auth ¶
type Auth struct {
// contains filtered or unexported fields
}
Auth is the central object. Create one with New() and attach its methods as HTTP handlers and middleware.
func New ¶
New validates the config, registers the OAuth providers with goth, loads the RBAC policy, and returns a ready-to-use Auth instance.
func (*Auth) BeginAuth ¶
func (a *Auth) BeginAuth(w http.ResponseWriter, r *http.Request)
BeginAuth starts the OAuth flow for the provider named in the URL path. Mount this on: GET /auth/{provider}
The provider name is extracted from the Go 1.22+ path value {provider}.
func (*Auth) Callback ¶
func (a *Auth) Callback(w http.ResponseWriter, r *http.Request)
Callback completes the OAuth handshake, resolves the user's role from the RBAC policy, stores the user in the session, then redirects to AfterLoginURL. Mount this on: GET /auth/{provider}/callback
func (*Auth) Login ¶
func (a *Auth) Login(w http.ResponseWriter, r *http.Request)
Login authenticates a user with email and password. Mount this on: POST /auth/login
Expects form values: email and password.
func (*Auth) Logout ¶
func (a *Auth) Logout(w http.ResponseWriter, r *http.Request)
Logout clears the session and redirects to AfterLogoutURL. Mount this on: POST /auth/logout
func (*Auth) Me ¶
func (a *Auth) Me(w http.ResponseWriter, r *http.Request)
Me returns the currently authenticated user as JSON. Returns 401 if the request has no valid session. Mount this on: GET /auth/me
func (*Auth) Register ¶
func (a *Auth) Register(w http.ResponseWriter, r *http.Request)
Register creates a new user account with email and password, then logs them in automatically. Mount this on: POST /auth/register
Expects form values: email, password, and optionally name.
func (*Auth) Require ¶
Require is middleware that enforces both a valid credential (API key or OAuth session) AND that the authenticated user holds the given permission. Returns 401 when there is no credential, 403 when the user lacks the permission.
func (*Auth) RequireAuth ¶
RequireAuth is middleware that enforces a valid credential — either an API key (via APIKeyValidator, if configured) or an OAuth session cookie. Responds 401 when neither is present or valid. On success it injects the User into the request context.
func (*Auth) RequireSession ¶ added in v0.0.3
RequireSession is like Require but rejects API key credentials. Use this for permission-gated management routes that must use OAuth sessions.
func (*Auth) RequireSessionAuth ¶ added in v0.0.3
RequireSessionAuth is like RequireAuth but rejects API key credentials. Use this for routes that must only be accessed via an OAuth session (e.g. /auth/me, UI-only management actions).
type AuthMode ¶
type AuthMode string
AuthMode controls which authentication methods are enabled.
const ( // AuthModeOAuth enables only OAuth providers (default). AuthModeOAuth AuthMode = "oauth" // AuthModePassword enables only email/password authentication. AuthModePassword AuthMode = "password" // AuthModeBoth enables both OAuth and email/password authentication. AuthModeBoth AuthMode = "both" )
type Config ¶
type Config struct {
// Mode controls which authentication methods are enabled.
// Defaults to AuthModeOAuth for backward compatibility.
Mode AuthMode
// Providers is the list of OAuth providers to enable.
// Required when Mode is AuthModeOAuth or AuthModeBoth.
Providers []ProviderConfig
// CallbackBaseURL is the externally-reachable base URL of the service
// (e.g. "https://example.com"). The OAuth callback URLs are derived as
// {CallbackBaseURL}/auth/{provider}/callback.
// Required when Mode is AuthModeOAuth or AuthModeBoth.
CallbackBaseURL string
// SessionSecret is used to sign and encrypt session cookies.
// Must be at least 32 bytes of random data.
SessionSecret string
// SecureCookie controls the Secure flag on session cookies.
// Set to true in production (HTTPS only). Defaults to false.
SecureCookie bool
// AfterLoginURL is the URL the user is redirected to after a successful login.
// Defaults to "/".
AfterLoginURL string
// AfterLogoutURL is the URL the user is redirected to after logout.
// Defaults to "/".
AfterLogoutURL string
// RBAC configures the role policy. If FilePath is empty, all authenticated
// users receive an empty role with no permissions.
RBAC RBACConfig
// UserStore provides user persistence for password-based authentication.
// Required when Mode is AuthModePassword or AuthModeBoth.
UserStore UserStore
// PasswordPolicy configures password validation rules.
// If nil, defaults are used (minimum 8 characters).
PasswordPolicy *PasswordPolicy
// Logger is used for diagnostic output. If nil, logs are written to the
// standard library log package.
Logger Logger
// APIKeyValidator enables API key authentication alongside OAuth sessions.
// When set, Require and RequireAuth middleware check the Authorization:
// Bearer (or X-API-Key) header first. RequireSession and RequireSessionAuth
// skip API key auth entirely (session-only routes).
// If nil, API key auth is disabled and only sessions are accepted.
APIKeyValidator APIKeyValidator
}
Config holds all configuration needed to create an Auth instance.
type LayeredPolicyProvider ¶ added in v0.0.4
type LayeredPolicyProvider struct {
// contains filtered or unexported fields
}
LayeredPolicyProvider combines a YAML baseline with per-user database overrides. The YAML file defines roles and their initial members. A management UI can call SetOverride to change individual users without editing the file.
Lookup order per request:
- Database override for this email (UI-managed, takes precedence)
- YAML baseline (file-managed, fallback)
Implements PolicyProvider and PolicyReloader.
func NewLayeredProvider ¶ added in v0.0.4
func NewLayeredProvider(filePath string, store UserRoleStore, opts ...func(*LayeredPolicyProvider)) (*LayeredPolicyProvider, error)
NewLayeredProvider creates a LayeredPolicyProvider that reads the initial policy from filePath and looks up per-user overrides from store.
Example:
provider, err := authkit.NewLayeredProvider("policy.yaml", myDBStore,
authkit.WithLogger(myLogger),
)
auth, err := authkit.New(authkit.Config{
RBAC: authkit.RBACConfig{Provider: provider},
...
})
go auth.WatchRBAC(ctx, 30*time.Second) // reloads YAML baseline
func (*LayeredPolicyProvider) DeleteOverride ¶ added in v0.0.4
func (l *LayeredPolicyProvider) DeleteOverride(ctx context.Context, email string) error
DeleteOverride removes the role override for email, reverting that user to the YAML baseline on their next login.
func (*LayeredPolicyProvider) PermissionsForRole ¶ added in v0.0.4
func (l *LayeredPolicyProvider) PermissionsForRole(role string) []string
PermissionsForRole implements PolicyProvider. Resolves permissions for a named role from the YAML baseline — role definitions are always file-managed.
func (*LayeredPolicyProvider) Reload ¶ added in v0.0.4
func (l *LayeredPolicyProvider) Reload() error
Reload implements PolicyReloader. Re-reads the YAML baseline without affecting any database overrides. Called automatically by WatchRBAC.
func (*LayeredPolicyProvider) RoleFor ¶ added in v0.0.4
RoleFor implements PolicyProvider. Checks the DB override first, then falls back to the YAML baseline. If the DB returns an error the fallback is used and the error is logged (if a Logger was configured). This prevents a store outage from locking users out, but means a user whose DB override was a demotion will temporarily regain their YAML role. Monitor store errors.
func (*LayeredPolicyProvider) SetOverride ¶ added in v0.0.4
func (l *LayeredPolicyProvider) SetOverride(ctx context.Context, email, role string, permissions []string) error
SetOverride validates and stores a per-user role override. Returns an error if the email format is invalid, the role name is not defined in the current YAML policy, or any permission string is invalid.
NOTE: role changes take effect on the user's next login — existing sessions retain their current permissions until they expire or the user logs out and back in. There is no built-in session invalidation.
func (*LayeredPolicyProvider) Store ¶ added in v0.0.4
func (l *LayeredPolicyProvider) Store() UserRoleStore
Store returns the raw UserRoleStore for operations not covered by SetOverride/DeleteOverride (e.g. listing all overrides in a management UI).
type Logger ¶
type Logger interface {
// Info logs informational messages (e.g. startup warnings).
Info(msg string, args ...any)
// Error logs error messages (e.g. failed OAuth callbacks, session errors).
Error(msg string, args ...any)
}
Logger is the interface authkit uses for diagnostic output. Provide your own implementation to route authkit logs into your application's logging system (e.g. slog, zap, zerolog). If not set in Config, a default logger that writes to the standard library log package is used.
type PasswordPolicy ¶
type PasswordPolicy struct {
// MinLength is the minimum password length. Defaults to 8.
MinLength int
}
PasswordPolicy configures password validation constraints.
type PasswordUser ¶
PasswordUser is the record returned by UserStore.GetUserByEmail.
type Policy ¶
type Policy struct {
// Roles maps role names to their definition.
Roles map[string]RolePolicy `yaml:"roles"`
// DefaultRole is assigned to authenticated users whose email is not listed
// under any role. Leave empty to deny access to unlisted users.
DefaultRole string `yaml:"default_role"`
}
Policy is the top-level structure of an rbac.yaml file.
type PolicyProvider ¶ added in v0.0.4
type PolicyProvider interface {
// RoleFor returns the role name and permission list for the given email.
// Called on every login and API key auth. Return empty strings/nil when
// the user has no assigned role.
RoleFor(ctx context.Context, email string) (role string, permissions []string)
// PermissionsForRole returns the permissions for a named role.
// Used to resolve permissions for API key users whose validator returns
// a role name rather than an email lookup.
PermissionsForRole(role string) []string
}
PolicyProvider resolves a user's role and permissions at login time. Implement this interface to back RBAC with any storage system. The built-in implementations are the YAML file provider (default) and LayeredPolicyProvider (YAML baseline + database overrides).
type PolicyReloader ¶ added in v0.0.4
type PolicyReloader interface {
Reload() error
}
PolicyReloader is an optional interface that PolicyProvider implementations can satisfy to support live policy reloading via WatchRBAC.
type ProviderConfig ¶
type ProviderConfig struct {
// Name is the provider identifier: "github", "google", or "gitlab".
Name string
// ClientID and ClientSecret are the OAuth application credentials.
ClientID string
ClientSecret string
// Scopes overrides the default scopes for the provider.
// Leave nil to use the sensible defaults (email + profile).
Scopes []string
}
ProviderConfig holds the OAuth credentials for a single provider.
type RBACConfig ¶
type RBACConfig struct {
// FilePath is the path to the rbac.yaml policy file.
// Used when Provider is nil.
FilePath string
// Provider supplies a custom PolicyProvider implementation (e.g. a
// database-backed or layered provider). When set, FilePath is ignored.
Provider PolicyProvider
}
RBACConfig tells the Auth instance how to load the role policy.
type RolePolicy ¶
type RolePolicy struct {
// Permissions is the list of allowed permissions for this role.
// Use "*" to grant all permissions (admin).
Permissions []string `yaml:"permissions"`
// Members is the list of email addresses assigned to this role.
Members []string `yaml:"members"`
}
RolePolicy defines the permissions and member emails for a single role.
type User ¶
type User struct {
Email string `json:"email"`
Name string `json:"name"`
AvatarURL string `json:"avatarUrl"`
Provider string `json:"provider"`
Role string `json:"role"`
// contains filtered or unexported fields
}
User represents an authenticated user resolved from an OAuth session. It is injected into every request context after successful authentication.
func UserFromCtx ¶
UserFromCtx returns the authenticated User stored in ctx, or nil if the request has not passed through RequireAuth middleware.
type UserRoleStore ¶ added in v0.0.4
type UserRoleStore interface {
// GetOverride returns the role and permissions for email if a UI-managed
// override exists. Return found=false when no override has been set for
// this user — authkit will fall back to the YAML baseline.
// The email argument is always lower-cased and trimmed before being passed.
GetOverride(ctx context.Context, email string) (role string, permissions []string, found bool, err error)
// SetOverride creates or replaces the role override for a user.
// Prefer calling LayeredPolicyProvider.SetOverride instead — it validates
// the role name and permission strings before writing to the store.
SetOverride(ctx context.Context, email, role string, permissions []string) error
// DeleteOverride removes the override for email, reverting that user to
// whatever the YAML baseline assigns them.
DeleteOverride(ctx context.Context, email string) error
}
UserRoleStore persists per-user role overrides to a database. Implement this interface against your preferred database (Postgres, SQLite, etc.) and pass it to NewLayeredProvider.
Only users whose roles have been changed via your UI need a row in the store. Everyone else falls through to the YAML baseline automatically.
type UserStore ¶
type UserStore interface {
// CreateUser persists a new user with the given email and bcrypt-hashed
// password. Implementations MUST return ErrUserExists if the email is
// already taken.
CreateUser(ctx context.Context, email, name, hashedPassword string) error
// GetUserByEmail retrieves a user by email. Returns ErrUserNotFound if
// no user matches.
GetUserByEmail(ctx context.Context, email string) (*PasswordUser, error)
}
UserStore is the interface consumers implement to provide user persistence for password-based authentication. authkit is storage-agnostic — the consumer chooses the backing store (PostgreSQL, SQLite, in-memory, etc.).