SystemForge is a batteries-included Go platform module providing reusable identity, session, authorization, and feature flags for multi-tenant SaaS applications. Think of it as Django/Laravel-style conveniences for Go.
Within the plexusone org, SystemForge is the shared identity/session/authorization foundation the Forge platform family (ActionForge, AgentForge, DashForge) builds on — one contract instead of each app inventing its own. See INIT-SYSTEMFORGE-001 for the unified authorization vocabulary, IAM console, and shell composition work this drives.
Moved from
github.com/grokify/systemforgetogithub.com/plexusone/systemforgein v0.10.0 — the old path still resolves via GitHub's repository-transfer redirect for existing pins, but won't receive new tags. See the v0.10.0 release notes for the full migration.
v0.11.0 renamed
coreauth→systemauth,cf_tables/API-keys →sf_, and unified the principal-link field tosf_principal_id— a hard cutover with no dual-name compatibility. See the v0.11.0 release notes for the full migration.
- 👤 Principals - One identity root for humans, applications, agents, and services; humans carry email, names, and a platform admin flag
- 🏢 Organizations - Multi-tenant with name, slug, settings, marketplace-derived entitlements
- 🔗 Memberships - Principal-organization relationships with flexible roles
- 👑 Ownership Transfer - Transaction-safe organization ownership transfers
- 🔐 Credentials - Passwords (Argon2id), API keys, keypairs, and client secrets owned by a principal
- 🔗 External Identities - Upstream logins (GitHub, Google, federation) linked to a principal by provider and subject
- 🌐 Social Login - GitHub/Google login served centrally by SystemAuth (
__Host-sf_loginsession, provider-subject account linking, logout, consent) - 🧒 Managed Principals - People without an email (a child, a dependent) whose accounts managers create and control: guardian and educator roles, QR-friendly login tokens, claims, and manager invitations; relying-party apps use SystemAuth's
/managed-principalsAPI throughmanagedprincipal/mpclient - 📇 SCIM 2.0 Provisioning -
identity/scim: tenant-scoped SCIM for enterprise identity providers, with a built-in per-tenant bearer token store (identity/scim/scimtoken) whose rotation keeps the previous token valid and revokes older ones - 🤝 Relying Parties -
identity/relyingparty: apps federate to SystemAuth via OIDC (ID-token/JWKS verification,sub→sf_principal_idlinking), mount the/bff/*cookie-session surface, and accept SystemAuth JWTs or API keys from programmatic clients - 🧩 Embeddable SystemAuth -
identity/systemauthsvcbuilds a production-checked SystemAuthhttp.Handlera host binary can mount next to its own apps, with provider metadata for discovery-free in-process relying parties - 🤖 AI Agent Requests -
identity/requestprincipal: one protocol-neutral request principal for people and agents;aauthrsverifies AAuth-signed agent requests (HTTP message signatures, person/auth tokens from trusted issuers, resource-token challenges) next to the relying-party human paths - 🔑 API Keys - Scoped machine-to-machine credentials on a principal
- 💌 Invite-Only Sign-Up - A sign-up gate for social login: only verified emails with an invite create accounts, refused people reach a consent-recorded waitlist, and
systemauth inviteandsystemauth waitlistmanage both - 🧭 App Catalog - Hosts publish the applications they run at
/.well-known/sf-apps, and/bff/sessionlists them so each shell shows the ones a person holds a role in, for a product switcher - 🏷️ Per-Application Roles in Tokens - Embedded SystemAuth emits the
orgsclaim with each organization's application roles, limited to default entitlements
- 📜 Authorization Code + PKCE - Secure browser-based auth
- 🤖 Client Credentials - Service-to-service auth
- 🔄 Refresh Token - Rotation with reuse detection (family revocation) and absolute lifetime
- 🪪 OpenID Connect - RS256 ID tokens, UserInfo, JWKS with key ID, optional JWT access tokens
- ⚙️ Service Principals - Non-human identities with scoped credentials and keypairs (the RFC 7523 JWT Bearer grant is not yet implemented)
- 🔍 Token Introspection & Revocation - RFC 7662/7009
- 🎫 JWT Service - Access/refresh token generation with HS256/RS256/ES256
- 🔒 DPoP (RFC 9449) - Proof-of-possession token binding
- 🖥️ BFF Pattern - Backend for Frontend with server-side sessions
- 💾 OmniStorage Backend - Production session storage with Redis, size limits, and observability
- 🛡️ Middleware - JWT Bearer and API key authentication
- 👥 RBAC/ReBAC - Role and relationship-based access control
- 🔐 SpiceDB Provider - Zanzibar-style fine-grained authorization
- ✨ Simple Provider - Lightweight permission checking
- 🧩 Query Policy Bridge - Use
authz.Authorizerto feed safe query languages such as GrokifyQL - 🚧 HTTP Middleware - Route protection for Chi and stdlib
- 🗝️ Key Management -
keyswires keyforge as the platform's one key library: envelope encryption, rotation without re-encrypting, the root key read through omnivault, and key events in the audit trail - 🔌 Connections -
connectionscomposes connectforge so organizations connect accounts at external services; credentials are sealed under managed keys and applications get an authenticated HTTP client, never the secret - 🌐 Connections API -
/api/v1/organizations/{org}/connectionswithsystemforge:connections:readand:writescopes, served by SystemAuth whenconnectionsis configured
- 📊 Vendor-Agnostic - Integrates with omniobserve
- 🔌 Multiple Backends - OTLP, Datadog, New Relic, Dynatrace
- 📈 Pre-Built Metrics - SystemAuth, rate limiting, JWT/API key validation
- 🔍 Distributed Tracing - Automatic span creation for OAuth flows
- 📝 slog Integration - Trace-correlated structured logging
- 🛒 Listings - Product catalog with tiers and pricing
- 💳 Stripe Integration - Subscription billing and webhooks
- 📜 Licensing - Per-seat, unlimited, and time-limited licenses
- 👥 Seat Management - Assign and revoke user access
- 🚩 Flag Engine - Boolean, percentage, and user list flags
- 🏢 Organization Scoping - Per-org flag evaluation
- 💾 In-Memory Store - Development and testing
- 🗃️ PostgreSQL RLS - Policy generation and session variables
- 🏠 Tenant Isolation - Multi-tenant data separation
- 🔗 Ent Integration - Transaction helpers with tenant context
- 🏗️ Multi-App Server - Run multiple SaaS apps on shared infrastructure
- 📦 Schema per App - Each app gets its own PostgreSQL schema
- 🔐 Database Login per App - Each app connects with its own role, confined to its schema and checked at startup; migrations use a separate owner login, and
cmd/multiappdbprovisions and verifies the roles (see Database isolation) - 🔀 X-App-ID Routing - Header-based request routing to app backends
- 🔌 AppBackend Interface - Composable app registration with lifecycle hooks
- 💾 Shared Caching - Redis or in-memory with app-scoped prefixes
go get github.com/plexusone/systemforgeSystemForge provides Ent schemas with the sf_ table prefix for side-by-side migration.
package main
import (
"context"
"log"
"github.com/plexusone/systemforge/identity/ent"
"github.com/plexusone/systemforge/identity/principal"
_ "github.com/lib/pq"
)
func main() {
client, err := ent.Open("postgres", "postgres://...")
if err != nil {
panic(err)
}
defer client.Close()
// Run migrations
if err := client.Schema.Create(context.Background()); err != nil {
panic(err)
}
// Create a human principal
person, err := principal.NewService(client).CreateHuman(context.Background(), principal.CreateHumanInput{
Email: "user@example.com",
DisplayName: "Example User",
})
if err != nil {
panic(err)
}
log.Printf("created principal %s", person.ID) // the ID used everywhere else in SystemForge
}Compose SystemForge mixins into your own schemas:
// your-app/ent/schema/user.go
package schema
import (
"entgo.io/ent"
"entgo.io/ent/schema/field"
cfmixin "github.com/plexusone/systemforge/identity/ent/mixin"
)
type User struct {
ent.Schema
}
func (User) Mixin() []ent.Mixin {
return []ent.Mixin{
cfmixin.UUIDMixin{}, // UUID primary key
cfmixin.TimestampMixin{}, // created_at, updated_at
}
}
func (User) Fields() []ent.Field {
return []ent.Field{
field.String("username").Unique(),
// App-specific fields...
}
}import (
"github.com/plexusone/systemforge/session/jwt"
"github.com/plexusone/systemforge/session/middleware"
)
// Create JWT service
svc, err := jwt.NewService(&jwt.Config{
Secret: secret, // at least 32 random bytes for HS256
KeyID: "k1", // optional "kid" header, enables key rotation
AccessTokenExpiry: 15 * time.Minute,
RefreshTokenExpiry: 7 * 24 * time.Hour,
Issuer: "your-app", // required; tokens from other issuers are refused
Audience: []string{"your-app"}, // required; tokens without it are refused
})
// Generate tokens
pair, err := svc.GenerateTokenPair(userID, email, name)
// Middleware for protected routes
r.Use(middleware.JWT(svc))DPoP (RFC 9449) lives in the standalone github.com/grokify/goauth/dpop package (moved out of SystemForge in v0.9.0). See the goauth DPoP guide.
import "github.com/grokify/goauth/dpop"
// Generate DPoP key pair (BFF side)
keyPair, err := dpop.GenerateKeyPair()
// Create proof for API request
proof, err := dpop.CreateProofWithOptions(keyPair, "POST", "https://api.example.com/data", dpop.ProofOptions{
AccessToken: accessToken,
})
// Enforce DPoP on the API side
auth, err := middleware.HTTPAuth(jwtService, middleware.DPoPConfig{
ReplayCache: middleware.NewRedisReplayCache(redisClient, "dpop-jti:"), // or NewMemoryReplayCache()
BaseURL: "https://api.example.com", // the scheme and host clients use
})
r.Use(auth)DPoP is the default. SystemAuth issues DPoP-bound tokens to every client:
token requests must carry a DPoP proof, access tokens carry cnf.jkt, and
refresh tokens work only with a proof by the same key. A client gets bearer
tokens only when its registration sets the legacy allow_bearer_tokens
exception. HTTPAuth accepts a DPoP-bound access token only with the DPoP
authorization scheme and a proof signed by the bound key that matches the
request method and URI, carries the token hash (ath), is fresh, and has not
been seen before; unbound tokens are refused unless DPoPConfig.AllowBearer
is set. The legacy HTTPAuthBearer middlewares accept only unbound tokens.
Relying parties of SystemAuth get the same checks from
relyingparty.BearerConfig.DPoP, and relyingparty.Client binds the tokens
it obtains to its DPoPKey. See DPoP Enforcement.
import "github.com/plexusone/systemforge/session/bff"
// Create BFF proxy
proxy := bff.NewProxy(bff.ProxyConfig{
Backend: "https://api.internal.example.com",
AllowedOrigins: []string{"https://app.example.com"},
SessionStore: bff.NewMemoryStore(),
})
// Mount proxy handler
r.Handle("/api/*", proxy.Handler())import (
"github.com/plexusone/systemforge/authz"
"github.com/plexusone/systemforge/authz/rbac"
)
// Create authorization provider
provider := simple.NewProvider(simple.Config{
AllowOwnerFullAccess: true,
AllowPlatformAdminAll: true,
})
// Add role permissions
provider.AddRolePermissions("admin", []string{
"users:read", "users:write",
"settings:read", "settings:write",
})
provider.AddRolePermissions("member", []string{
"users:read",
})
// Use middleware
mw := authz.NewMiddleware(provider)
r.With(mw.RequireAction(authz.ResourceType("users"), authz.ActionRead)).Get("/users", listUsers)github.com/plexusone/systemforge/
├── identity/ # User, Organization, Membership, OAuth
│ ├── ent/schema/ # Ent schemas with sf_ prefix
│ ├── apikey/ # API key service
│ ├── oauth/ # OAuth 2.0 server (Fosite)
│ ├── oauthclient/ # GitHub/Google/SystemAuth OAuth client primitives
│ ├── publicclient/ # Command-line and other public clients: loopback login, DPoP-bound API calls
│ ├── relyingparty/ # OIDC relying party for apps federating to SystemAuth (/bff, bearer)
│ ├── requestprincipal/ # Protocol-neutral request principal and authenticator chain (aauthrs/: AAuth agents)
│ ├── systemauth/ # SystemAuth OAuth 2.0 / OIDC server (social login, userinfo; cmd/systemauth binary)
│ ├── systemauthsvc/ # Embeddable SystemAuth service setup for host binaries (config, production checks, migrations)
│ ├── password.go # Argon2id hashing
│ └── service.go # Identity service interfaces
│
├── keys/ # keyforge wired to the platform (omnivault root key, audit trail)
├── connections/ # connectforge composed with the platform (store, RLS, authz, audit, HTTP API)
│
├── session/ # Session management
│ ├── jwt/ # JWT service with DPoP claims
│ ├── bff/ # Backend for Frontend pattern (DPoP via goauth/dpop)
│ ├── middleware/ # Auth middleware
│ └── ratelimit/ # Rate limiting with observability
│
├── observability/ # Vendor-agnostic observability
│ ├── observability.go # Core wrapper for omniobserve
│ ├── middleware.go # HTTP request tracing
│ └── metrics.go # Pre-defined metric names
│
├── authz/ # Authorization
│ ├── simple/ # Simple RBAC provider
│ ├── spicedb/ # SpiceDB ReBAC provider
│ ├── noop/ # No-op syncer for testing
│ ├── providertest/ # Provider test suite
│ └── middleware.go # HTTP middleware
│
├── marketplace/ # SaaS marketplace
│ ├── listing.go # Product listings and tiers
│ ├── license.go # License management
│ ├── subscription.go # Subscription handling
│ └── stripe/ # Stripe billing integration
│
├── featureflags/ # Feature flag engine
│ └── stores/ # Flag stores
│
├── multiapp/ # Multi-app platform
│ ├── server.go # Multi-app server with routing
│ ├── app.go # AppBackend interface
│ ├── database.go # Schema-per-app pools, one login per app
│ ├── provision.go # Per-app role provisioning SQL
│ ├── isolation.go # Startup database isolation check
│ ├── cache.go # Redis and memory cache
│ └── context.go # App context helpers
│
└── rls/ # PostgreSQL Row-Level Security
├── rls.go # Policy generation
└── middleware.go # HTTP middleware
| Decision | Choice | Rationale |
|---|---|---|
| Table prefix | sf_ |
Avoids conflicts, enables side-by-side migration |
| Role storage | String field | Apps define own vocabularies (owner/admin/member) |
| OAuth pattern | Fosite library | Production-ready, RFC-compliant OAuth 2.0 |
| Refresh tokens | Database-backed | Enables revocation, theft detection |
| Primary keys | UUID | Modern, distributed-friendly |
| Password hashing | Argon2id | OWASP recommended, memory-hard |
SystemForge creates the following tables (all prefixed with sf_):
| Table | Description |
|---|---|
sf_principals |
Identity root for humans, applications, agents, and services |
sf_humans |
Human extension: email, names, avatar, verification |
sf_applications |
Application extension for OAuth-client principals |
sf_agents |
Agent extension for AI-agent principals |
sf_service_principals |
Service extension for non-human service identities |
sf_credentials |
Passwords, API keys, keypairs, and secrets owned by principals |
sf_principal_tokens |
Tokens issued to principals |
sf_organizations |
Multi-tenant organizations |
sf_principal_memberships |
Principal-organization relationships |
sf_invites |
Organization invitations |
sf_external_identities |
Upstream login identities (GitHub/Google/federation) linked to principals |
sf_oauth_apps |
OAuth client applications |
sf_oauth_app_secrets |
Client secrets (hashed) |
sf_oauth_tokens |
Issued OAuth tokens |
sf_oauth_auth_codes |
Authorization codes |
sf_oauth_consents |
Principal consent records |
sf_login_sessions |
SystemAuth login sessions (token hashes only) |
sf_login_states |
In-flight upstream social logins (single-use) |
sf_consent_grants |
Consent-page grants per principal, client and scope |
sf_listings, sf_licenses, sf_subscriptions, sf_seat_assignments |
Marketplace catalog, entitlements, billing, and seats |
Applications federating to SystemAuth create two more tables in their own
database with identity/relyingparty/pgstore.EnsureSchema:
| Table | Description |
|---|---|
sf_rp_sessions |
Relying-party browser sessions (token hashes; SystemAuth tokens encrypted at rest) |
sf_rp_login_states |
In-flight logins to SystemAuth (single-use; nonce and PKCE verifier encrypted) |
For existing apps, SystemForge supports side-by-side migration:
- Side-by-Side: Create
sf_*tables alongside existing tables - Dual-Write: Write to both old and new tables
- Cutover: Switch reads to SystemForge tables
- Cleanup: Remove old tables
Full documentation is available via MkDocs:
# Install MkDocs
pip install mkdocs mkdocs-material
# Serve locally
mkdocs serve
# Build static site
mkdocs buildRelevant authorization guides:
docs/authorization/spicedb-setup.mddocs/authorization/spicedb-schema.mddocs/authorization/integration.mddocs/authorization/query-policy-engines.md
Guides for organizations, roles and access invalidation are in docs/guides/:
docs/guides/invites.mddocs/guides/application-roles.mddocs/guides/collaborators.mddocs/guides/organization-slugs.mddocs/guides/personal-to-team.mddocs/guides/access-change-invalidation.mddocs/guides/authorization-mode.mddocs/guides/connections.mddocs/guides/key-management.md
Contributions are welcome! Please read the contributing guidelines before submitting PRs.
MIT License - see LICENSE file for details.