bettergoth

package module
v0.0.0-...-d398788 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 22, 2026 License: MIT Imports: 29 Imported by: 0

README ¶

better-goth-banner

👀 Overview

Better Goth is a better-auth alternative, written purely in Go. It provides a complete OAuth 2.1 and OpenID Connect implementation as an embeddable library, giving you full control over your authentication flow without external dependencies or managed services.


✅ Standards & Compliance

Better Goth implements the following OAuth 2.0 and OpenID Connect RFCs:

  • RFC 6749 – OAuth 2.0 Authorization Framework. The core protocol for delegated authorization, enabling secure token-based access.
  • OAuth 2.1 – OAuth 2.1 Authorization Framework. The next-generation OAuth specification with security improvements and best practices baked in.
  • RFC 7009 – OAuth 2.0 Token Revocation. Endpoints to revoke access and refresh tokens, ensuring users can log out and invalidate sessions.
  • RFC 7517 – JSON Web Key (JWK). Support for public key distribution via JWKS endpoints, enabling clients to verify token signatures.
  • RFC 7523 – SAML 2.0 Bearer Assertion Profile for OAuth 2.0 Client Authentication. Implements private_key_jwt client authentication for high-security token exchanges.
  • RFC 7591 – OAuth 2.0 Dynamic Client Registration Protocol. Endpoints for programmatic client registration without manual setup.
  • RFC 7662 – OAuth 2.0 Token Introspection. Endpoints to validate and inspect token claims, useful for API gateways and resource servers.
  • RFC 9560 – OpenID Connect Federation 1.0. Support for federated identity across multiple issuers.
  • RFC 9700 – OpenID Connect Core 1.0. Full OpenID Connect support including ID tokens, user info endpoints, and discovery.

âš¡ Core Capabilities

  • Authorization & Token Exchange – Authorization code flow with PKCE (Proof Key for Code Exchange) for secure mobile and SPA applications.
  • Client Authentication – Support for client_secret_basic, client_secret_post, and private_key_jwt methods.
  • Token Management – Issue access tokens, refresh tokens, and ID tokens with configurable lifetimes and scopes.
  • Revocation & Introspection – Full token lifecycle management including revocation and introspection endpoints.
  • JWKS & Discovery – OpenID Connect discovery and JSON Web Key Set endpoints for seamless client integration.
  • External Provider Integration – Authenticate users against external OIDC providers and exchange their credentials.
  • Session Management – JWT-backed session tokens with secure cookie storage and validation.

🔌 Library Endpoints

The library exposes the following endpoints:

Authorization & Tokens

  • GET /authorize – Authorization endpoint for initiating the OAuth flow
  • POST /oauth/token – Token endpoint for exchanging authorization codes and refreshing tokens
  • POST /oauth/token/revocation – Token revocation endpoint per RFC 7009

Discovery & Keys

  • GET /.well-known/openid-configuration – OpenID Connect discovery document
  • GET /.well-known/jwks.json – JSON Web Key Set for token signature verification

User & Token Inspection

  • GET /userinfo – OpenID Connect userinfo endpoint for retrieving authenticated user claims
  • POST /oauth/token/introspection – Token introspection endpoint per RFC 7662

Administration

  • GET /admin/rotate – Key rotation endpoint for maintaining security posture

🎨 Customization

Better Goth is built for flexibility. While the library provides complete OAuth 2.1 and OpenID Connect implementations out of the box, you can override and customize critical parts of the flow:

  • User Flow – Replace the user authentication handler to integrate with your own user store, LDAP, or custom identity system.
  • Authorization Flow – Customize how authorization codes are generated, validated, and exchanged. Implement custom scopes, claim mapping, or consent flows.
  • Token Issuance – Override token generation to add custom claims, apply business logic, or integrate with external systems.
  • Storage Backend – Swap the default in-memory store for your own database layer (PostgreSQL, MySQL, SQLite) or custom implementation.
  • Session Management – Customize session cookie behavior, JWT claims, session duration, and validation logic.
  • Hooks & Middleware – Intercept requests at key points to add logging, metrics, audit trails, or custom validation.

The library's modular design ensures you can layer your own logic on top without forking or rewriting core OAuth flows.


🔗 Integration

Better Goth is designed to be embedded directly into your Go application. It handles OAuth 2.1 and OpenID Connect flows out of the box, with configuration through YAML. Wire it into your existing HTTP router to add authentication without external services.

The library manages its own key lifecycle, token storage, and client registry. Extend it with custom user stores, hooks, and business logic.

To see the library in action, check out the example app in the examples/ directory. It demonstrates a complete end-to-end flow with user login, authorization, and token exchange.


📜 License

This project is licensed under the MIT License – see the LICENSE file for details.

Documentation ¶

Index ¶

Constants ¶

View Source
const (
	TokenUseAccess   = "access"
	TokenUseDevice   = "device"
	TokenUseRecovery = "recovery"

	AccessTokenTTL   = time.Hour
	DeviceTokenTTL   = 30 * 24 * time.Hour
	RecoveryTokenTTL = 15 * time.Minute
)

Variables ¶

View Source
var (
	ErrMissingJWTSecret = errors.New("jwt secret is required")
	ErrWeakJWTSecret    = errors.New("jwt secret must be at least 32 bytes")
)
View Source
var (
	ErrPasswordTooShort   = store.ErrPasswordTooShort
	ErrUserExists         = store.ErrUserExists
	ErrUsernameExists     = store.ErrUsernameExists
	ErrInvalidCredentials = store.ErrInvalidCredentials
	ErrUserNotFound       = store.ErrUserNotFound
	ErrInvalidOTP         = store.ErrInvalidOTP
)
View Source
var (
	ErrMissingAuthHeader = errors.New("missing authorization header")
	ErrInvalidAuthHeader = errors.New("invalid authorization header")
	ErrInvalidToken      = errors.New("invalid token")
)

Functions ¶

func AuthFromCookie ¶

func AuthFromCookie(auth *Auth, cookieName, redirectPath string, next http.Handler) http.Handler

func ClearAuthCookie ¶

func ClearAuthCookie(w http.ResponseWriter, cookieName string, cookieSecure bool)

func NewBearerResourceHandler ¶

func NewBearerResourceHandler(auth *Auth) func(http.ResponseWriter, *http.Request)

func NewSessionResourceHandler ¶

func NewSessionResourceHandler(auth *Auth, cookieName, redirectPath string) http.Handler

func NewTokenGetHandler ¶

func NewTokenGetHandler(store *TokenStore) func(http.ResponseWriter, *http.Request)

func NewTokenStoreHandler ¶

func NewTokenStoreHandler(store *TokenStore) func(http.ResponseWriter, *http.Request)

func NewTokenUpdateHandler ¶

func NewTokenUpdateHandler(store *TokenStore) func(http.ResponseWriter, *http.Request)

func NewTokensListHandler ¶

func NewTokensListHandler(store *TokenStore) func(http.ResponseWriter, *http.Request)

func RegisterRoutes ¶

func RegisterRoutes(router RouteRegistrar, auth *Auth)

func SignOutHandler ¶

func SignOutHandler(cookieName string, cookieSecure bool, redirectPath string) func(http.ResponseWriter, *http.Request)

func WriteJSON ¶

func WriteJSON(w http.ResponseWriter, status int, payload interface{})

Types ¶

type Auth ¶

type Auth struct {
	Providers map[string]Provider

	CookieSecure bool
	// contains filtered or unexported fields
}

func NewAuth ¶

func NewAuth(secret []byte, db *database.Instance) (*Auth, error)

func (*Auth) AddProvider ¶

func (a *Auth) AddProvider(provider Provider)

func (*Auth) CallbackHandler ¶

func (a *Auth) CallbackHandler() func(http.ResponseWriter, *http.Request)

func (*Auth) LoginHandler ¶

func (a *Auth) LoginHandler() func(http.ResponseWriter, *http.Request)

func (*Auth) RequireAuth ¶

func (a *Auth) RequireAuth(next http.Handler) http.Handler

func (*Auth) SetAuthResultHandler ¶

func (a *Auth) SetAuthResultHandler(h AuthResultHandler)

func (*Auth) SetDevMode ¶

func (a *Auth) SetDevMode(devMode bool)

func (*Auth) SetUserHandler ¶

func (a *Auth) SetUserHandler(h UserHandler)

func (*Auth) VerifyRequest ¶

func (a *Auth) VerifyRequest(r *http.Request) (*VerifiedUser, error)

func (*Auth) VerifyToken ¶

func (a *Auth) VerifyToken(tokenString string) (*VerifiedUser, error)

type AuthResult ¶

type AuthResult struct {
	Provider     string
	User         *pb.User
	SignedToken  string
	OAuthToken   *oauth2.Token
	IDToken      string
	RawIDClaims  map[string]interface{}
	AccessToken  string
	RefreshToken string
	TokenType    string
	ExpiresAt    time.Time
}

type AuthResultHandler ¶

type AuthResultHandler interface {
	HandleAuthResult(ctx context.Context, w http.ResponseWriter, r *http.Request, result *AuthResult) error
}

func NewSessionAuthResultHandler ¶

func NewSessionAuthResultHandler(store *TokenStore, opts SessionAuthResultOptions) AuthResultHandler

type AuthResultHandlerFunc ¶

type AuthResultHandlerFunc func(context.Context, http.ResponseWriter, *http.Request, *AuthResult) error

func (AuthResultHandlerFunc) HandleAuthResult ¶

func (f AuthResultHandlerFunc) HandleAuthResult(ctx context.Context, w http.ResponseWriter, r *http.Request, result *AuthResult) error

type EmbedConfig ¶

type EmbedConfig struct {
	StorageType        string
	ConnectionString   string
	IssuerURL          string
	KeyDir             string
	ClientID           string
	ClientSecret       string
	RedirectURIs       []string
	CORSOrigins        []string
	DevMode            bool
	SMTP               SMTPConfig
	AdminToken         string
	PrivateKeyJWTHosts []string
	JWTSecret          []byte
}

EmbedConfig configures an in-process authorization server (no extra listener).

type Provider ¶

type Provider interface {
	Name() string
	Config() *oauth2.Config
	Verifier() *oidc.IDTokenVerifier
}

type RouteRegistrar ¶

type RouteRegistrar interface {
	Handle(pattern string, handler http.Handler)
	HandleFunc(pattern string, handler func(http.ResponseWriter, *http.Request))
}

RouteRegistrar is the minimum interface needed to register handlers.

type Runtime ¶

type Runtime struct {
	ListenAddr    string
	ConfigDir     string
	Auth          *Auth
	Store         *TokenStore
	CookieName    string
	CookieSecure  bool
	OAuthIssuer   string
	OAuthClientID string
	// contains filtered or unexported fields
}

Runtime exposes the configured auth runtime so callers can register their own routes.

func NewEmbedded ¶

func NewEmbedded(cfg EmbedConfig) (*Runtime, error)

NewEmbedded builds store, keys, and an OAuth HTTP handler without ListenAndServe.

func Setup ¶

func Setup(configPath string) (*Runtime, error)

Setup wires the oauth server and providers using the YAML config.

func (*Runtime) Authenticate ¶

func (r *Runtime) Authenticate(email, password string) (*User, error)

func (*Runtime) Close ¶

func (r *Runtime) Close() error

Close releases the embedded database.

func (*Runtime) ConsumePasswordReset ¶

func (r *Runtime) ConsumePasswordReset(email, code string) (*User, error)

func (*Runtime) CreatePasswordResetCode ¶

func (r *Runtime) CreatePasswordResetCode(email string) (string, error)

func (*Runtime) CreateUser ¶

func (r *Runtime) CreateUser(email, password string) (*User, error)

func (*Runtime) DeleteUser ¶

func (r *Runtime) DeleteUser(userID string) error

func (*Runtime) Handler ¶

func (r *Runtime) Handler() http.Handler

Handler returns the OAuth/OIDC HTTP handler to mount on the process mux.

func (*Runtime) IssueDeviceToken ¶

func (r *Runtime) IssueDeviceToken(userID string) (*TokenPair, error)

func (*Runtime) IssueRecoveryToken ¶

func (r *Runtime) IssueRecoveryToken(userID string) (*TokenPair, error)

func (*Runtime) IssueTokens ¶

func (r *Runtime) IssueTokens(userID string) (*TokenPair, error)

func (*Runtime) Refresh ¶

func (r *Runtime) Refresh(refreshToken string) (*TokenPair, error)

func (*Runtime) RequestPasswordReset ¶

func (r *Runtime) RequestPasswordReset(email string) error

func (*Runtime) Revoke ¶

func (r *Runtime) Revoke(accessOrRefresh string) error

func (*Runtime) SessionAuthResultHandler ¶

func (r *Runtime) SessionAuthResultHandler(redirectPath string) AuthResultHandler

func (*Runtime) UpdatePassword ¶

func (r *Runtime) UpdatePassword(userID, password string) error

func (*Runtime) UserByID ¶

func (r *Runtime) UserByID(id string) (*User, error)

func (*Runtime) VerifyAccessToken ¶

func (r *Runtime) VerifyAccessToken(raw string) (*VerifiedUser, error)

func (*Runtime) VerifyRequestAccess ¶

func (r *Runtime) VerifyRequestAccess(req *http.Request) (*VerifiedUser, error)

type SMTPConfig ¶

type SMTPConfig struct {
	Host     string
	Port     string
	Username string
	Password string
	From     string
}

SMTPConfig is the public mailer settings for the embedded server.

type SessionAuthResultOptions ¶

type SessionAuthResultOptions struct {
	CookieName   string
	CookieSecure bool
	RedirectPath string
}

type TokenPair ¶

type TokenPair struct {
	AccessToken  string
	RefreshToken string
	TokenType    string
	ExpiresIn    int
	TokenUse     string
	User         User
}

TokenPair is the mobile-session shape issued by the authorization server.

type TokenRecord ¶

type TokenRecord struct {
	Provider     string `json:"provider"`
	UserSub      string `json:"user_sub"`
	Email        string `json:"email"`
	Name         string `json:"name"`
	SignedJWT    string `json:"signed_jwt"`
	AccessToken  string `json:"access_token"`
	RefreshToken string `json:"refresh_token"`
	TokenType    string `json:"token_type"`
	ExpiresAt    string `json:"expires_at"`
}

type TokenStore ¶

type TokenStore struct {
	// contains filtered or unexported fields
}

TODO: convert to use db

func NewTokenStore ¶

func NewTokenStore() *TokenStore

func (*TokenStore) All ¶

func (s *TokenStore) All() map[string]TokenRecord

func (*TokenStore) Get ¶

func (s *TokenStore) Get(sub string) (TokenRecord, bool)

func (*TokenStore) Save ¶

func (s *TokenStore) Save(record TokenRecord)

type User ¶

type User struct {
	ID             string
	Email          string
	Name           string
	EmailConfirmed bool
}

User is the public user record returned by the embedded API.

type UserHandler ¶

type UserHandler interface {
	HandleUser(ctx context.Context, user *pb.User) error
}

type UserHandlerFunc ¶

type UserHandlerFunc func(context.Context, *pb.User) error

func (UserHandlerFunc) HandleUser ¶

func (f UserHandlerFunc) HandleUser(ctx context.Context, user *pb.User) error

type VerifiedUser ¶

type VerifiedUser struct {
	Subject  string
	Claims   jwt.RegisteredClaims
	Token    string
	TokenUse string
	Email    string
	Scope    string
}

func UserFromContext ¶

func UserFromContext(ctx context.Context) (*VerifiedUser, bool)

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL