Authsome

Custom Plugin

Build a custom Authsome plugin by implementing lifecycle hook interfaces.

Authsome's plugin system uses Go interfaces -- implement only the hooks you need and register your plugin with the engine. This guide walks through building a complete plugin from scratch.

The base interface

Every plugin must implement plugin.Plugin:

import "github.com/xraph/authsome/plugin"

type Plugin interface {
	Name() string
}

Available hooks

Authsome defines lifecycle and event hooks as separate interfaces. Implement any combination:

Lifecycle hooks

HookSignatureWhen called
OnInitOnInit(ctx, engine) errorDuring engine initialization
OnShutdownOnShutdown(ctx) errorDuring engine shutdown

Auth event hooks

HookSignatureWhen called
BeforeSignUpOnBeforeSignUp(ctx, *SignUpRequest) errorBefore account creation
AfterSignUpOnAfterSignUp(ctx, *User, *Session) errorAfter account creation
BeforeSignInOnBeforeSignIn(ctx, *SignInRequest) errorBefore authentication
AfterSignInOnAfterSignIn(ctx, *User, *Session) errorAfter authentication
BeforeSignOutOnBeforeSignOut(ctx, sessionID) errorBefore session end
AfterSignOutOnAfterSignOut(ctx, sessionID) errorAfter session end

User lifecycle hooks

HookSignatureWhen called
BeforeUserCreateOnBeforeUserCreate(ctx, *User) errorBefore user creation
AfterUserCreateOnAfterUserCreate(ctx, *User) errorAfter user creation
BeforeUserUpdateOnBeforeUserUpdate(ctx, *User) errorBefore user update
AfterUserUpdateOnAfterUserUpdate(ctx, *User) errorAfter user update
BeforeUserDeleteOnBeforeUserDelete(ctx, userID) errorBefore user deletion
AfterUserDeleteOnAfterUserDelete(ctx, userID) errorAfter user deletion

Session lifecycle hooks

HookSignatureWhen called
BeforeSessionCreateOnBeforeSessionCreate(ctx, *Session) errorBefore session creation
AfterSessionCreateOnAfterSessionCreate(ctx, *Session) errorAfter session creation
AfterSessionRefreshOnAfterSessionRefresh(ctx, *Session) errorAfter token refresh
AfterSessionRevokeOnAfterSessionRevoke(ctx, sessionID) errorAfter session revocation

Organization lifecycle hooks

HookSignatureWhen called
AfterOrgCreateOnAfterOrgCreate(ctx, *Organization) errorAfter org creation
AfterOrgUpdateOnAfterOrgUpdate(ctx, *Organization) errorAfter org update
AfterOrgDeleteOnAfterOrgDelete(ctx, orgID) errorAfter org deletion
AfterMemberAddOnAfterMemberAdd(ctx, *Member) errorAfter member added
AfterMemberRemoveOnAfterMemberRemove(ctx, memberID) errorAfter member removed
AfterMemberRoleChangeOnAfterMemberRoleChange(ctx, *Member) errorAfter role change

Provider interfaces

InterfacePurpose
RouteProviderRegister additional HTTP routes
MigrationProviderProvide database migration groups
StrategyProviderContribute an authentication strategy
AuthMethodContributorReport auth methods linked to a user
AuthMethodUnlinkerSupport unlinking an auth method
DataExportContributorContribute data for GDPR export

Example: API Token plugin

A complete plugin that adds API token authentication as an alternative to session tokens.

package apitoken

import (
	"context"
	"crypto/rand"
	"encoding/hex"
	"fmt"
	"time"

	"github.com/xraph/authsome/id"
	"github.com/xraph/authsome/plugin"
	"github.com/xraph/authsome/session"
	"github.com/xraph/authsome/strategy"
	"github.com/xraph/authsome/user"

	"github.com/xraph/grove/migrate"
)

// Compile-time interface checks.
var (
	_ plugin.Plugin              = (*Plugin)(nil)
	_ plugin.OnInit              = (*Plugin)(nil)
	_ plugin.MigrationProvider   = (*Plugin)(nil)
	_ plugin.RouteProvider       = (*Plugin)(nil)
	_ plugin.StrategyProvider    = (*Plugin)(nil)
	_ plugin.AfterUserDelete     = (*Plugin)(nil)
)

// Plugin provides API token authentication.
type Plugin struct {
	store  *TokenStore
	engine any // set during OnInit
}

func New() *Plugin {
	return &Plugin{}
}

func (p *Plugin) Name() string { return "api-token" }

// ── Lifecycle ──

func (p *Plugin) OnInit(ctx context.Context, engine any) error {
	p.engine = engine
	// Initialize the token store using the engine's store.
	// In practice, you would resolve a database connection here.
	return nil
}

// ── Migrations ──

func (p *Plugin) MigrationGroups(driverName string) []*migrate.Group {
	switch driverName {
	case "pg":
		return []*migrate.Group{pgMigrations()}
	case "sqlite":
		return []*migrate.Group{sqliteMigrations()}
	default:
		return nil
	}
}

func pgMigrations() *migrate.Group {
	g := migrate.NewGroup("api_token")
	g.Add("001_create_api_tokens", `
		CREATE TABLE IF NOT EXISTS api_tokens (
			id TEXT PRIMARY KEY,
			user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
			name TEXT NOT NULL,
			token_hash TEXT NOT NULL UNIQUE,
			prefix TEXT NOT NULL,
			scopes TEXT[] DEFAULT '{}',
			expires_at TIMESTAMPTZ,
			last_used_at TIMESTAMPTZ,
			created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
		);
		CREATE INDEX idx_api_tokens_user ON api_tokens(user_id);
		CREATE INDEX idx_api_tokens_hash ON api_tokens(token_hash);
	`, `DROP TABLE IF EXISTS api_tokens;`)
	return g
}

// ── Routes ──

func (p *Plugin) RegisterRoutes(router any) error {
	// Register POST /v1/auth/api-tokens (create)
	// Register GET  /v1/auth/api-tokens (list)
	// Register DELETE /v1/auth/api-tokens/:id (revoke)
	return nil
}

// ── Strategy ──

func (p *Plugin) Strategy() strategy.Strategy {
	return &apiTokenStrategy{plugin: p}
}

func (p *Plugin) StrategyPriority() int { return 100 }

// ── Cleanup on user deletion ──

func (p *Plugin) OnAfterUserDelete(ctx context.Context, userID id.UserID) error {
	// Delete all API tokens for the deleted user.
	return p.store.DeleteByUserID(ctx, userID)
}

// ── Strategy implementation ──

type apiTokenStrategy struct {
	plugin *Plugin
}

func (s *apiTokenStrategy) Name() string { return "api-token" }

func (s *apiTokenStrategy) Authenticate(ctx context.Context, token string) (*user.User, *session.Session, error) {
	// 1. Hash the token
	// 2. Look up the hash in api_tokens table
	// 3. Check expiry
	// 4. Resolve the user
	// 5. Update last_used_at
	// 6. Return user and a synthetic session
	return nil, nil, fmt.Errorf("not implemented")
}

// ── Token generation helper ──

func GenerateToken() (raw string, prefix string, err error) {
	b := make([]byte, 32)
	if _, err := rand.Read(b); err != nil {
		return "", "", err
	}
	raw = hex.EncodeToString(b)
	prefix = raw[:8]
	return raw, prefix, nil
}

Registering with the engine

Pass your plugin via authsome.WithPlugin:

import (
	authsome "github.com/xraph/authsome"
	"myapp/apitoken"
)

eng, err := authsome.NewEngine(
	authsome.WithStore(store),
	authsome.WithPlugin(apitoken.New()),
)

Or when using the Forge extension:

import "github.com/xraph/authsome/extension"

authExt := extension.New(
	extension.WithGroveDatabase(""),
	extension.WithPlugin(apitoken.New()),
)

How dispatch works

The engine's plugin registry uses type assertions at registration time to discover which hooks each plugin implements. When an event fires, only plugins implementing that specific hook interface are called. Hooks are invoked sequentially in registration order. If any hook returns an error:

  • Before hooks -- the error aborts the operation
  • After hooks -- the error is logged but does not abort the operation

Testing your plugin

package apitoken_test

import (
	"context"
	"testing"

	authsome "github.com/xraph/authsome"
	"github.com/xraph/authsome/store/memory"
	"myapp/apitoken"
)

func TestPluginRegisters(t *testing.T) {
	eng, err := authsome.NewEngine(
		authsome.WithStore(memory.New()),
		authsome.WithPlugin(apitoken.New()),
	)
	if err != nil {
		t.Fatal(err)
	}

	if err := eng.Start(context.Background()); err != nil {
		t.Fatal(err)
	}
	defer eng.Stop(context.Background())

	// Verify the plugin is registered
	found := false
	for _, p := range eng.Plugins().Plugins() {
		if p.Name() == "api-token" {
			found = true
			break
		}
	}
	if !found {
		t.Fatal("api-token plugin not registered")
	}

	// Verify the strategy is registered
	strategies := eng.Strategies().Strategies()
	if len(strategies) == 0 {
		t.Fatal("no strategies registered")
	}
}

Built-in plugins

Authsome ships several built-in plugins you can use as reference implementations:

PluginPackageHooks implemented
Social (OAuth)plugins/socialRouteProvider, MigrationProvider, StrategyProvider, AuthMethodContributor
MFA (TOTP)plugins/mfaRouteProvider, MigrationProvider, AuthMethodContributor
Passkey (WebAuthn)plugins/passkeyRouteProvider, MigrationProvider, StrategyProvider, AuthMethodContributor
Magic Linkplugins/magiclinkRouteProvider, MigrationProvider
Phone (SMS)plugins/phoneRouteProvider, MigrationProvider, AuthMethodContributor

On this page