goten

package module
v0.2.0 Latest Latest
Warning

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

Go to latest
Published: Jun 4, 2026 License: MIT Imports: 15 Imported by: 0

README

Goten

Go Language Otentikasi — composable authentication for Go, inspired by better-auth, Limen, and Go-better-auth.

CI Go Reference

Goten is a modular authentication library for Go with a multi-module plugin architecture — install only what you need, no unused code in your binary.

Status: 🚧 v0.1.0 — early release, API may change before v1.0

Features

✅ Email/password sign-up & sign-in ✅ Plugin system with capability interfaces
✅ Opaque session tokens (g10_ prefix) ✅ Username plugin
✅ Cookie + Bearer auth ✅ CLI migration tool
✅ GORM adapter (Postgres) ✅ CSRF origin check
✅ Anti-enumeration on sign-in ✅ OAuth plugin — Sign in with Google (PKCE + OIDC)
✅ Session list/revoke 🔜 Magic link, 2FA, JWT plugin

Quick Start

go get github.com/dnahilman/goten
go get github.com/dnahilman/goten/adapters/gorm

For a full walkthrough — database setup, migrations, runnable example, and end-to-end testing with curl — see the Quick Start guide on the Wiki.

Runnable examples in the repo:

Endpoints

Method Path Description
POST /api/auth/sign-up/email Register with email + password
POST /api/auth/sign-in/email Login with email + password
POST /api/auth/sign-out Revoke current session
GET /api/auth/get-session Get current session + user
GET /api/auth/list-sessions List all sessions for user
POST /api/auth/revoke-session Revoke a specific session
POST /api/auth/revoke-other-sessions Revoke all other sessions

With the OAuth plugin enabled:

Method Path Description
POST /api/auth/sign-in/social Start social sign-in → {redirect, url} (or sign in with an idToken)
GET /api/auth/callback/{provider} OAuth redirect callback
GET /api/auth/list-accounts List the user's linked accounts
POST /api/auth/link-social Link a provider to the current user
POST /api/auth/unlink-account Unlink a provider
POST /api/auth/get-access-token Get a valid provider access token
POST /api/auth/refresh-token Refresh the stored provider tokens

CLI

The CLI is generate-only (like better-auth's generate): it emits ORM model definitions from the core schema plus the active plugins' schema. You apply them with your ORM — for GORM, db.AutoMigrate(authmodels.AllModels()...).

go install github.com/dnahilman/goten/cmd/goten@latest

# Generate models into <generate.output_dir>/auth_models.go
goten generate

Config file goten.config.yaml:

plugins:
  - username
  - oauth          # adds the verification table + account token columns to the models

generate:
  output_dir: ./internal/auth
  package: authmodels
  orm: gorm

Then wire it into your app:

import authmodels "yourapp/internal/auth"

db.AutoMigrate(authmodels.AllModels()...) // create/upgrade goten's tables

AutoMigrate is additive (creates tables, adds columns/indexes/constraints). It does not drop/rename columns, change types destructively, or roll back. Destructive changes and data migrations are handled with your own SQL tooling.

Editor autocomplete: add # yaml-language-server: $schema=https://raw.githubusercontent.com/dnahilman/goten/main/goten.config.schema.json as the first line of your goten.config.yaml for autocomplete + inline validation in VS Code (Red Hat YAML extension), JetBrains, and other editors. See examples/basic/goten.config.yaml.

Plugins

Username Plugin

Login via username instead of (or alongside) email:

go get github.com/dnahilman/goten/plugins/username
import usernameplugin "github.com/dnahilman/goten/plugins/username"

auth, _ := goten.New(goten.Config{
    // ...
    Plugins: []goten.Plugin{
        usernameplugin.New(usernameplugin.Options{}),
    },
})

Adds endpoints: POST /api/auth/sign-up/username, POST /api/auth/sign-in/username.

OAuth Plugin (Sign in with Google)

Social sign-in via OAuth 2.0 Authorization Code + PKCE + OIDC, modeled after better-auth. Providers are registered in a map (like better-auth's socialProviders); Google is built in.

go get github.com/dnahilman/goten/plugins/oauth
import (
    oauthplugin "github.com/dnahilman/goten/plugins/oauth"
    "github.com/dnahilman/goten/plugins/oauth/providers"
)

auth, _ := goten.New(goten.Config{
    BaseURL:        "http://localhost:8080",
    TrustedOrigins: []string{"http://localhost:3000"}, // allowed callbackURL origins
    // ...
    Plugins: []goten.Plugin{
        oauthplugin.New(oauthplugin.Options{
            Providers: map[string]oauthplugin.Provider{
                "google": providers.Google(providers.GoogleOptions{
                    ClientID:     os.Getenv("GOOGLE_CLIENT_ID"),
                    ClientSecret: os.Getenv("GOOGLE_CLIENT_SECRET"),
                    AccessType:   "offline", // request a refresh token
                }),
            },
            // EncryptOAuthTokens: true, // opt-in AES-256-GCM token encryption (default off)
        }),
    },
})

Then add oauth to plugins in goten.config.yaml, run goten generate, and AutoMigrate the result. The generated models gain a core verification table (which stores the OAuth sign-in state) plus token columns on accounts.

Security: PKCE (S256), CSRF state (verification row + signed cookie), redirect-origin validation, and anti account-takeover linking (an existing user is auto-linked only when the provider's email is verified and, by default, the local account is verified too). Full walkthrough — including Google Cloud Console setup — in examples/oauth-google/.

Building Your Own Plugin
type MyPlugin struct{ auth *goten.Auth }

func (p *MyPlugin) ID() string          { return "my-plugin" }
func (p *MyPlugin) SetAuth(a *goten.Auth) { p.auth = a }
func (p *MyPlugin) Endpoints() []goten.Endpoint {
    return []goten.Endpoint{
        {Method: "GET", Path: "/my-endpoint", Handler: p.handle},
    }
}

Optional interfaces: Initializer, EndpointProvider, SchemaProvider (declares the columns the plugin adds, consumed by goten generate), UserCreateHookProvider, SessionCreateHookProvider.

Architecture

github.com/dnahilman/goten              ← core (Auth, session, crypto, plugin system)
github.com/dnahilman/goten/adapters/gorm  ← GORM adapter (separate module)
github.com/dnahilman/goten/plugins/username ← username plugin (separate module)
github.com/dnahilman/goten/plugins/oauth   ← OAuth plugin + Google provider (separate module)
github.com/dnahilman/goten/cmd/goten    ← CLI tool (separate module)

Each module is independently versioned — go get only what you use.

ID & Token Format

All IDs and tokens carry a g10_ prefix for easy identification in logs and secret scanning:

  • User/Session ID: g10_018f4a23-1234-7890-abcd-ef1234567890 (UUID v7, time-sortable)
  • Session token: g10_<base64url-32-bytes> (256-bit entropy)

CSRF Protection

Goten applies CSRF origin checking to all non-safe methods (POST, PUT, DELETE, etc.):

  • Bearer token present → bypass (mobile/API clients)
  • TrustedOrigins empty → allow requests without Origin (dev-friendly)
  • TrustedOrigins set → require Origin to match; reject others with 403
auth, _ := goten.New(goten.Config{
    BaseURL:        "https://myapp.com",
    TrustedOrigins: []string{"https://myapp.com", "https://www.myapp.com"},
    // ...
})

Comparison

Goten better-auth (TS) Limen Go-better-auth
Multi-module plugins ✅ (subpath)
CLI migration tool
Map-based adapter
OAuth providers ✅ (Google)
Language Go TypeScript Go Go

Contributing

See CONTRIBUTING.md. Security issues: SECURITY.md.

License

MIT — see LICENSE.

Documentation

Overview

Package goten provides composable, self-hosted authentication for Go.

Index

Constants

This section is empty.

Variables

View Source
var (
	ErrInvalidToken    = &APIError{Code: "INVALID_TOKEN", Message: "invalid session token", Status: 401}
	ErrInvalidEmail    = &APIError{Code: "INVALID_EMAIL", Message: "invalid email", Status: 400}
	ErrInvalidPassword = &APIError{Code: "INVALID_CREDENTIALS", Message: "invalid email or password", Status: 400}
	ErrEmailExists     = &APIError{Code: "EMAIL_EXISTS", Message: "email already exists", Status: 409}
	ErrUserNotFound    = &APIError{Code: "USER_NOT_FOUND", Message: "user not found", Status: 404}
	ErrSessionExpired  = &APIError{Code: "SESSION_EXPIRED", Message: "session expired", Status: 401}
	ErrSessionNotFound = &APIError{Code: "SESSION_NOT_FOUND", Message: "session not found", Status: 401}
	ErrUnauthorized    = &APIError{Code: "UNAUTHORIZED", Message: "unauthorized", Status: 401}
	ErrInternal        = &APIError{Code: "INTERNAL", Message: "internal server error", Status: 500}
	ErrNoSession       = &APIError{Code: "UNAUTHORIZED", Message: "no session", Status: 401}
)
View Source
var CoreTableOrder = []string{"users", "sessions", "accounts", "verification"}

CoreTableOrder lists the core tables in dependency order (referenced tables first), so generated models and AllModels() can be emitted/migrated safely.

View Source
var EQ = adp.EQ

EQ constructs an equality Where clause.

View Source
var ErrHookHandled = errors.New("hook handled the response")

ErrHookHandled signals that a hook already wrote the HTTP response. Callers must not write again; just return.

Functions

func CoreSchema added in v0.2.0

func CoreSchema() map[string]TableSchema

CoreSchema returns the declarative schema for goten's built-in tables (users, sessions, accounts, verification). It is the source of truth for the `goten generate` CLI, which merges it with each enabled plugin's SchemaProvider.Schema() to emit ORM models. The map-based runtime adapter reads/writes these same column names, kept in sync via this declaration.

func DecodeJSON

func DecodeJSON(r *http.Request, dst any) error

func GetClientIP

func GetClientIP(r *http.Request, ipHeader string) string

func SessionFromContext

func SessionFromContext(ctx context.Context) (*models.Session, bool)

func UserFromContext

func UserFromContext(ctx context.Context) (*models.User, bool)

func WithSession

func WithSession(ctx context.Context, sess *models.Session, user *models.User) context.Context

func WriteError

func WriteError(w http.ResponseWriter, status int, code, message string)

func WriteJSON

func WriteJSON(w http.ResponseWriter, status int, v any)

Types

type APIError

type APIError struct {
	Code    string `json:"code"`
	Message string `json:"message"`
	Status  int    `json:"status"`
}

func (*APIError) Error

func (e *APIError) Error() string

func (*APIError) WriteJSON

func (e *APIError) WriteJSON(w http.ResponseWriter)

type Account

type Account = models.Account

type Adapter

type Adapter = adp.Adapter

Type aliases so callers can use goten.Adapter, goten.Query, etc.

type Auth

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

Auth is the composition root. Create with New() and mount Handler() on your server.

func New

func New(cfg Config) (*Auth, error)

New creates an Auth instance. Returns error for invalid config — never panics.

func (*Auth) Adapter

func (a *Auth) Adapter() Adapter

func (*Auth) Config

func (a *Auth) Config() Config

func (*Auth) CurrentSession added in v0.2.0

func (a *Auth) CurrentSession(r *http.Request) (*models.Session, *models.User, error)

CurrentSession resolves the active session and user from the request (session cookie or Bearer token), applying sliding refresh. It is a plugin-friendly helper for endpoints that need the caller's identity without going through the RequireAuth middleware. Returns an error when there is no valid session.

func (*Auth) Handler

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

func (*Auth) InternalAdapter

func (a *Auth) InternalAdapter() *InternalAdapter

func (*Auth) IsTrustedOrigin added in v0.2.0

func (a *Auth) IsTrustedOrigin(origin string) bool

IsTrustedOrigin reports whether origin matches the configured BaseURL or one of the TrustedOrigins. Exposed so plugins can validate redirect/callback URLs (e.g. to prevent open-redirects in the OAuth flow).

func (*Auth) Plugins

func (a *Auth) Plugins() []Plugin

func (*Auth) RequireAuth

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

RequireAuth middleware validates the session and injects user+session into context. Use this to protect your own application endpoints.

func (*Auth) RunSessionCreateHooks

func (a *Auth) RunSessionCreateHooks(w http.ResponseWriter, r *http.Request, userID string) error

RunSessionCreateHooks runs all session-create veto hooks. Returns ErrHookHandled if a hook already wrote the response (caller must not write again).

func (*Auth) RunUserCreateHooks

func (a *Auth) RunUserCreateHooks(data map[string]any) map[string]any

RunUserCreateHooks applies all user-create hooks to data in registration order.

func (*Auth) Sessions

func (a *Auth) Sessions() *session.Manager

func (*Auth) SetSessionCookie

func (a *Auth) SetSessionCookie(w http.ResponseWriter, sess *models.Session)

SetSessionCookie is a plugin-friendly helper that sets the session cookie using Auth's configured cookie settings. Plugins call this instead of accessing the internal cookieConfig directly.

type AuthAware

type AuthAware interface {
	SetAuth(a *Auth)
}

AuthAware — plugin receives a reference to *Auth after New() sets up core state.

type Config

type Config struct {
	AppName        string
	BaseURL        string
	BasePath       string
	Secret         string
	Adapter        Adapter
	Plugins        []Plugin
	Session        SessionConfig
	Cookie         CookieConfig
	EmailPassword  EmailPasswordConfig
	TrustedOrigins []string
}

type CookieConfig

type CookieConfig struct {
	Name     string
	Domain   string
	Path     string
	Secure   *bool
	HTTPOnly *bool
	SameSite http.SameSite
}

type EmailPasswordConfig

type EmailPasswordConfig struct {
	Enabled           bool
	AutoSignIn        bool
	MinPasswordLength int
	MaxPasswordLength int
}

type Endpoint

type Endpoint struct {
	Method  string // "GET", "POST", etc.
	Path    string // relative path prefixed with BasePath in the router
	Handler http.HandlerFunc
}

Endpoint describes a single HTTP endpoint a plugin registers.

type EndpointProvider

type EndpointProvider interface {
	Endpoints() []Endpoint
}

EndpointProvider — plugin registers additional HTTP endpoints.

type FieldDef

type FieldDef struct {
	Name       string
	Type       string // "text", "boolean", "integer", "timestamp"
	Required   bool   // NOT NULL
	Unique     bool   // unique index
	Index      bool   // non-unique index
	PrimaryKey bool   // primary key column (e.g. id)
	Ref        string // foreign key target "table.column" (onDelete CASCADE assumed)
	Default    string // optional DDL default literal, e.g. "false", "”"
}

FieldDef describes a single column.

type Initializer

type Initializer interface {
	Init() error
}

Initializer — plugin needs a one-time init step (validate config, connect to external service, etc.).

type InternalAdapter

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

InternalAdapter provides typed CRUD methods on top of the raw Adapter interface.

func NewInternalAdapter

func NewInternalAdapter(a adp.Adapter) *InternalAdapter

func (*InternalAdapter) Adapter

func (ia *InternalAdapter) Adapter() adp.Adapter

Adapter returns the raw underlying adapter for plugins that need direct access.

func (*InternalAdapter) CreateAccount

func (ia *InternalAdapter) CreateAccount(ctx context.Context, userID, accountID, providerID string, extra map[string]any) (*models.Account, error)

func (*InternalAdapter) CreateUser

func (ia *InternalAdapter) CreateUser(ctx context.Context, email, name string, emailVerified bool) (*models.User, error)

func (*InternalAdapter) CreateUserWithExtra

func (ia *InternalAdapter) CreateUserWithExtra(ctx context.Context, email, name string, emailVerified bool, extra map[string]any) (*models.User, error)

func (*InternalAdapter) CreateVerificationValue added in v0.2.0

func (ia *InternalAdapter) CreateVerificationValue(ctx context.Context, identifier, value string, expiresAt time.Time) (*models.Verification, error)

CreateVerificationValue stores a key-value record with an expiry, keyed by identifier. Used by the OAuth plugin to persist sign-in state.

func (*InternalAdapter) DeleteUser

func (ia *InternalAdapter) DeleteUser(ctx context.Context, id string) error

func (*InternalAdapter) DeleteVerificationByIdentifier added in v0.2.0

func (ia *InternalAdapter) DeleteVerificationByIdentifier(ctx context.Context, identifier string) error

DeleteVerificationByIdentifier removes all verification records for the identifier.

func (*InternalAdapter) FindAccountByProviderAndID

func (ia *InternalAdapter) FindAccountByProviderAndID(ctx context.Context, providerID, accountID string) (*models.Account, error)

func (*InternalAdapter) FindAccountsByUserID

func (ia *InternalAdapter) FindAccountsByUserID(ctx context.Context, userID string) ([]*models.Account, error)

func (*InternalAdapter) FindUserByEmail

func (ia *InternalAdapter) FindUserByEmail(ctx context.Context, email string) (*models.User, error)

func (*InternalAdapter) FindUserByID

func (ia *InternalAdapter) FindUserByID(ctx context.Context, id string) (*models.User, error)

func (*InternalAdapter) FindVerificationValue added in v0.2.0

func (ia *InternalAdapter) FindVerificationValue(ctx context.Context, identifier string) (*models.Verification, error)

FindVerificationValue returns the verification record for the given identifier, or nil.

func (*InternalAdapter) UpdatePassword

func (ia *InternalAdapter) UpdatePassword(ctx context.Context, userID, hashedPassword string) error

func (*InternalAdapter) UpdateUser

func (ia *InternalAdapter) UpdateUser(ctx context.Context, id string, data map[string]any) (*models.User, error)

func (*InternalAdapter) WithTransaction added in v0.2.0

func (ia *InternalAdapter) WithTransaction(ctx context.Context, fn func(ctx context.Context) error) error

WithTransaction runs fn inside a database transaction when the underlying adapter supports it (adp.TxRunner); otherwise fn runs directly (best-effort, e.g. for in-memory test adapters). Calls made with the context passed to fn participate in the same transaction.

type Plugin

type Plugin interface {
	ID() string
}

Plugin is the base interface — all plugins must implement at minimum ID().

type Query

type Query = adp.Query

type SchemaProvider

type SchemaProvider interface {
	Schema() map[string]TableSchema
}

SchemaProvider — plugin declares the DB columns/tables it adds. This is the source of truth for the `goten generate` CLI, which merges these with the core schema to emit ORM models (e.g. GORM structs).

type Session

type Session = models.Session

type SessionConfig

type SessionConfig struct {
	ExpiresIn time.Duration
	UpdateAge time.Duration
}

type SessionCreateContext

type SessionCreateContext struct {
	UserID  string
	Request *http.Request
	Writer  http.ResponseWriter
}

SessionCreateContext carries request context for session-create hooks.

type SessionCreateHookFn

type SessionCreateHookFn func(ctx SessionCreateContext) error

SessionCreateHookFn is called before inserting a new session. Return a non-nil error to abort. Return ErrHookHandled if the hook already wrote the response.

type SessionCreateHookProvider

type SessionCreateHookProvider interface {
	SessionCreateHooks() []SessionCreateHookFn
}

SessionCreateHookProvider — plugin hooks into session creation.

type TableSchema

type TableSchema struct {
	Fields []FieldDef
	// UniqueTogether lists composite-unique column groups, e.g.
	// {{"provider_id", "account_id"}} for the accounts table.
	UniqueTogether [][]string
}

TableSchema describes the columns (and table-level constraints) for a table.

type User

type User = models.User

Type aliases so plugin authors only need to import the root package.

type UserCreateHookFn

type UserCreateHookFn func(data map[string]any) map[string]any

UserCreateHookFn is called before inserting a new user. Receives and returns the data map so plugins can add or transform fields.

type UserCreateHookProvider

type UserCreateHookProvider interface {
	UserCreateHooks() []UserCreateHookFn
}

UserCreateHookProvider — plugin hooks into user creation.

type Where

type Where = adp.Where

Directories

Path Synopsis
Package adapter defines the database adapter interface used by goten core and plugins.
Package adapter defines the database adapter interface used by goten core and plugins.
adapters
gorm module
cmd
goten module
plugins
admin module
oauth module
username module
Package session provides session lifecycle management and cookie utilities for goten.
Package session provides session lifecycle management and cookie utilities for goten.

Jump to

Keyboard shortcuts

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