Help us shape the future of Go! Take the 2026 Go Developer Survey

sso

package
v1.9.0 Latest Latest
Warning

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

Go to latest
Published: Nov 2, 2026 License: MIT Imports: 55 Imported by: 0

README

sso

SAML 2.0 and OIDC single sign-on, configured per organisation.

This is the plugin enterprise deals ask for. Each tenant points at its own identity provider, Okta, Entra, Ping, whatever they run, and their staff sign in with corporate credentials that you never see. Users get provisioned automatically on first login.

Domain-based routing means someone can type their work email on your sign-in page and get sent to the right identity provider without picking anything from a list.

Use it when

  • You are selling to companies with an identity team and an SSO line item in the contract
  • Offboarding has to be instant. Revoking access in the identity provider should end access to you
  • Each tenant needs its own identity provider, which is why connections are scoped per organisation

Skip it when

  • Your users are individuals. social is the right shape for personal accounts
  • You cannot serve a stable public HTTPS base URL. SAML entity IDs and ACS URLs derive from it
  • You have not decided what happens to an SSO user when their organisation is deleted

Wiring

authsome.WithPlugin(sso.New(sso.Config{
    PublicBaseURL:        "https://auth.example.com",
    AllowedReturnOrigins: []string{"https://app.example.com"},
}))

PublicBaseURL is required for SAML. AllowedReturnOrigins is the open-redirect guard on the post-ACS landing, so treat it as a security control and not a convenience.

Config

Field Type Default What it does
Providers []Provider empty Providers configured at startup
PublicBaseURL string none Externally reachable base URL. Required for SAML
AllowedReturnOrigins []string localhost Origins the browser may be redirected back to
SessionTokenTTL time.Duration 1h Lifetime of a session created by SSO
SessionRefreshTTL time.Duration 30d Lifetime of that session's refresh token

Settings

Key Default
sso.session_token_ttl_seconds 3600
sso.session_refresh_ttl_seconds 2592000

Endpoints

Method Path Purpose
POST /v1/sso/login Start SSO by email domain
POST /v1/sso/:provider/login Start SSO against a named provider
POST /v1/sso/:provider/acs SAML assertion consumer service
GET /v1/sso/:provider/metadata SAML service provider metadata
GET /v1/sso/:provider/callback OIDC redirect landing
POST /v1/sso/exchange Trade the one-time code for a session
GET /v1/admin/sso/connections List connections for an app
POST /v1/admin/sso/connections Create a connection
GET /v1/admin/sso/connections/:connectionId Read a connection
PUT /v1/admin/sso/connections/:connectionId Update a connection
DELETE /v1/admin/sso/connections/:connectionId Delete a connection

Lifecycle hooks

None. It is a RouteProvider, a SettingsProvider and a MigrationProvider for its connection tables.

scim is the other half of the enterprise story: SSO handles who can get in, SCIM keeps the user list in sync. organization provides the tenants that connections are scoped to.

Documentation

Overview

contract.go: Wires the sso plugin into the forge-dashboard contract surface via plugin.ContractContributor.

Package sso provides enterprise single sign-on authentication for AuthSome.

The SSO plugin enables organizations to authenticate users through SAML 2.0 and OpenID Connect (OIDC) identity providers. SSO connections are scoped per organization, allowing each tenant to configure its own identity provider. The plugin manages the full SSO flow including metadata exchange, assertion validation, and automatic user provisioning.

Usage:

eng, _ := authsome.NewEngine(
    authsome.WithStore(store),
    authsome.WithPlugin(sso.New(sso.Config{
        DefaultRedirectURL: "https://example.com/dashboard",
    })),
)

Package sso provides enterprise Single Sign-On (SSO) authentication via SAML 2.0 and OpenID Connect (OIDC) protocols.

Usage:

p := sso.New(sso.Config{
    Providers: []sso.Provider{
        sso.NewOIDCProvider(sso.OIDCConfig{
            Name:         "okta",
            Issuer:       "https://mycompany.okta.com",
            ClientID:     "client-id",
            ClientSecret: "client-secret",
            RedirectURL:  "https://app.com/api/auth/sso/okta/callback",
        }),
    },
})

Index

Constants

This section is empty.

Variables

View Source
var (
	// SettingSessionTokenTTLSeconds controls the session token lifetime for SSO sign-in.
	SettingSessionTokenTTLSeconds = settings.Define("sso.session_token_ttl_seconds", 3600,
		settings.WithDisplayName("Session Token TTL (seconds)"),
		settings.WithDescription("Lifetime of sessions created via SSO sign-in in seconds"),
		settings.WithCategory("SSO"),
		settings.WithScopes(settings.ScopeGlobal, settings.ScopeApp),
		settings.WithInputType(formconfig.FieldNumber),
		settings.WithUIValidation(formconfig.Validation{Required: true, Min: intPtr(300), Max: intPtr(86400)}),
		settings.WithHelpText("How long sessions created via SSO remain valid. Default: 3600 (1 hour)"),
		settings.WithOrder(10),
	)

	// SettingSessionRefreshTTLSeconds controls the refresh token lifetime for SSO sessions.
	SettingSessionRefreshTTLSeconds = settings.Define("sso.session_refresh_ttl_seconds", 2592000,
		settings.WithDisplayName("Refresh Token TTL (seconds)"),
		settings.WithDescription("Lifetime of refresh tokens for SSO sessions in seconds"),
		settings.WithCategory("SSO"),
		settings.WithScopes(settings.ScopeGlobal, settings.ScopeApp),
		settings.WithInputType(formconfig.FieldNumber),
		settings.WithUIValidation(formconfig.Validation{Required: true, Min: intPtr(3600), Max: intPtr(7776000)}),
		settings.WithHelpText("How long refresh tokens remain valid. Default: 2592000 (30 days)"),
		settings.WithOrder(20),
	)
)
View Source
var ErrConnectionNotFound = errors.New("sso: connection not found")

ErrConnectionNotFound is returned when an SSO connection is not found.

View Source
var ErrIdentityExists = errors.New("sso: identity already bound")

ErrIdentityExists is returned when a connection already binds the subject to a user.

View Source
var ErrIdentityNotFound = errors.New("sso: identity not found")

ErrIdentityNotFound is returned when a connection has never bound the presented subject to a user.

View Source
var PostgresMigrations = migrate.NewGroup("authsome-sso", migrate.DependsOn("authsome"))

PostgresMigrations is the postgres migration group for the SSO plugin.

View Source
var SqliteMigrations = migrate.NewGroup("authsome-sso", migrate.DependsOn("authsome"))

SqliteMigrations is the SQLite migration group for the SSO plugin.

Functions

This section is empty.

Types

type AdminConnectionPathRequest added in v1.9.0

type AdminConnectionPathRequest struct {
	ConnectionID string `path:"connectionId" description:"Connection identifier"`
}

AdminConnectionPathRequest binds :connectionId for GET / DELETE.

type AdminCreateConnectionRequest added in v1.9.0

type AdminCreateConnectionRequest struct {
	AppID        string `json:"app_id,omitempty" description:"Application ID; optional, must be the caller's own app"`
	OrgID        string `json:"org_id,omitempty" description:"Optional Org scope inside the App"`
	Provider     string `json:"provider" description:"Stable name for this IdP (e.g. 'studio', 'okta')"`
	Protocol     string `json:"protocol" description:"oidc or saml"`
	Domain       string `json:"domain" description:"Email-domain or IdP host this connection covers"`
	Issuer       string `json:"issuer,omitempty" description:"OIDC issuer URL (required for oidc)"`
	ClientID     string `json:"client_id,omitempty" description:"OIDC client ID"`
	ClientSecret string `json:"client_secret,omitempty" description:"OIDC client secret (omit for public flows)"`
	MetadataURL  string `json:"metadata_url,omitempty" description:"SAML metadata URL (one IdP source for saml)"`

	// SAML IdP configuration. Supply exactly one IdP source: metadata_url,
	// idp_metadata_xml, or idp_sso_url + idp_certificate.
	IDPMetadataXML    string            `json:"idp_metadata_xml,omitempty" description:"Pasted SAML IdP metadata XML"`
	IDPSSOURL         string            `json:"idp_sso_url,omitempty" description:"SAML IdP SSO (redirect) URL"`
	IDPCertificate    string            `json:"idp_certificate,omitempty" description:"SAML IdP signing certificate (PEM)"`
	EntityID          string            `json:"entity_id,omitempty" description:"SP EntityID override; defaults to the metadata URL"`
	ACSURL            string            `json:"acs_url,omitempty" description:"SP ACS URL override; defaults to the canonical /acs route"`
	SignRequests      bool              `json:"sign_requests,omitempty" description:"Sign outbound AuthnRequests with the SP key"`
	AttributeMappings map[string]string `json:"attribute_mappings,omitempty" description:"SAML attribute name → user field (email|first_name|last_name|groups)"`
	AllowedDomains    []string          `` /* 141-byte string literal not displayed */
	TrustedFederation bool              `` /* 136-byte string literal not displayed */
}

AdminCreateConnectionRequest is the body for POST /v1/admin/sso/connections. Caller specifies the target App + the IdP details. Domain is required and must be unique within the App so the dispatch path (`/v1/sso/:provider/login`) can resolve the right connection.

type AdminCreateConnectionResponse added in v1.9.0

type AdminCreateConnectionResponse struct {
	ID       string `json:"id"`
	AppID    string `json:"app_id"`
	Provider string `json:"provider"`
	Protocol string `json:"protocol"`
	Domain   string `json:"domain"`
	Active   bool   `json:"active"`
}

AdminCreateConnectionResponse is the response from POST /v1/admin/sso/connections.

type AdminDeleteConnectionResponse added in v1.9.0

type AdminDeleteConnectionResponse struct {
	Status string `json:"status"`
}

AdminDeleteConnectionResponse mirrors the StatusResponse shape used elsewhere in the admin surface so the SDK can model deletes uniformly.

type AdminListConnectionsRequest added in v1.9.0

type AdminListConnectionsRequest struct {
	AppID string `query:"app_id,omitempty" description:"App identifier; optional, must be the caller's own app"`
}

AdminListConnectionsRequest binds the query for GET /v1/admin/sso/connections.

type AdminListConnectionsResponse added in v1.9.0

type AdminListConnectionsResponse struct {
	Connections []*Connection `json:"connections"`
}

AdminListConnectionsResponse is the listing response. Returns [] instead of a paged envelope because per-app connection counts are expected to stay small (a few IdPs at most).

type AdminUpdateConnectionRequest added in v1.9.0

type AdminUpdateConnectionRequest struct {
	ConnectionID string `path:"connectionId" description:"Connection identifier"`

	Provider     string `json:"provider,omitempty" description:"Stable provider name"`
	Domain       string `json:"domain,omitempty" description:"Email-domain or IdP host"`
	Issuer       string `json:"issuer,omitempty" description:"OIDC issuer URL"`
	ClientID     string `json:"client_id,omitempty" description:"OIDC client ID"`
	ClientSecret string `json:"client_secret,omitempty" description:"OIDC client secret; pass empty string to leave unchanged"`
	MetadataURL  string `json:"metadata_url,omitempty" description:"SAML metadata URL"`

	// SAML fields. Empty strings leave the stored value unchanged; send
	// attribute_mappings/sign_requests to replace them wholesale.
	IDPMetadataXML    string            `json:"idp_metadata_xml,omitempty" description:"Pasted SAML IdP metadata XML"`
	IDPSSOURL         string            `json:"idp_sso_url,omitempty" description:"SAML IdP SSO URL"`
	IDPCertificate    string            `json:"idp_certificate,omitempty" description:"SAML IdP signing certificate (PEM)"`
	EntityID          string            `json:"entity_id,omitempty" description:"SP EntityID override"`
	ACSURL            string            `json:"acs_url,omitempty" description:"SP ACS URL override"`
	SignRequests      *bool             `json:"sign_requests,omitempty" description:"Toggle signing outbound AuthnRequests"`
	AttributeMappings map[string]string `json:"attribute_mappings,omitempty" description:"Replace the SAML attribute → user field map"`
	AllowedDomains    []string          `json:"allowed_domains,omitempty" description:"Replace the extra email domains this connection may assert"`
	TrustedFederation *bool             `json:"trusted_federation,omitempty" description:"Adopt the IdP subject as the local user id for new users"`

	Active *bool `json:"active,omitempty" description:"Activate/deactivate the connection without deleting it"`
}

AdminUpdateConnectionRequest binds the body for PUT /connections/:id. Empty/zero values are treated as "leave unchanged" so callers can PATCH a single field without re-sending the entire connection.

type CallbackRequest added in v1.9.0

type CallbackRequest struct {
	Provider string `path:"provider"`
	State    string `json:"state" query:"state,omitempty"`
	Code     string `json:"code" query:"code,omitempty"`
	Error    string `json:"error,omitempty" query:"error,omitempty"`
}

CallbackRequest contains the parameters for the OIDC callback.

type CallbackResponse added in v1.9.0

type CallbackResponse struct {
	User         any    `json:"user"`
	SessionToken string `json:"session_token"`
	RefreshToken string `json:"refresh_token"`
	ExpiresAt    any    `json:"expires_at"`
	Provider     string `json:"provider"`
	IsNewUser    bool   `json:"is_new_user"`
	// contains filtered or unexported fields
}

CallbackResponse is returned on successful SSO authentication.

type Config

type Config struct {
	// Providers is the list of configured SSO providers.
	Providers []Provider

	// SessionTokenTTL is the lifetime of sessions created via SSO sign-in (default: 1 hour).
	SessionTokenTTL time.Duration

	// SessionRefreshTTL is the lifetime of refresh tokens (default: 30 days).
	SessionRefreshTTL time.Duration

	// PublicBaseURL is the externally-reachable base URL of this SSO service
	// (e.g. "https://auth.muono.cloud"). SAML SP EntityID / ACS URL derive from
	// it when a connection doesn't override them. Required for SAML.
	PublicBaseURL string

	// AllowedReturnOrigins is the allowlist of scheme://host[:port] origins the
	// browser SSO landing may redirect back to after ACS. Guards against open
	// redirect. localhost/127.0.0.1 are always allowed (dev). The first entry is
	// used to derive a default `/sso/callback` landing when none is supplied.
	AllowedReturnOrigins []string
}

Config configures the SSO plugin.

type Connection added in v1.9.0

type Connection struct {
	ID       id.SSOConnectionID `json:"id"`
	AppID    id.AppID           `json:"app_id"`
	EnvID    string             `json:"env_id,omitempty"`
	OrgID    id.OrgID           `json:"org_id,omitempty"`
	Provider string             `json:"provider"`
	Protocol string             `json:"protocol"`
	Domain   string             `json:"domain"`
	// DisplayName is an optional admin-set label for the connection (e.g. "Okta",
	// "Google Workspace"), so multiple connections for the same domain can be told
	// apart. Cosmetic — never used for routing.
	DisplayName  string `json:"display_name,omitempty"`
	MetadataURL  string `json:"metadata_url,omitempty"`
	ClientID     string `json:"client_id,omitempty"`
	ClientSecret string `json:"-"`
	Issuer       string `json:"issuer,omitempty"`
	Active       bool   `json:"active"`
	// Enforced requires users on this connection's domain to sign in via SSO —
	// password login is blocked for them (workspace owners/admins excepted). See
	// the plugin's OnBeforeSignIn.
	Enforced bool `json:"enforced"`

	// AllowedDomains lists email domains, besides Domain, that this
	// connection may assert. A login whose email is outside Domain and this
	// list is refused before any account is touched.
	AllowedDomains []string `json:"allowed_domains,omitempty"`
	// TrustedFederation adopts the identity provider's subject as the local
	// user id when a user is first created through this connection. Off by
	// default: an IdP that can pick subjects can otherwise pick local ids.
	TrustedFederation bool `json:"trusted_federation,omitempty"`

	// SAML-specific configuration. Populated only for SAML connections.
	IDPMetadataXML    string            `json:"idp_metadata_xml,omitempty"`
	IDPSSOURL         string            `json:"idp_sso_url,omitempty"`
	IDPCertificate    string            `json:"idp_certificate,omitempty"`
	EntityID          string            `json:"entity_id,omitempty"`
	ACSURL            string            `json:"acs_url,omitempty"`
	SPCertificate     string            `json:"sp_certificate,omitempty"`
	SPPrivateKey      string            `json:"-"` // secret — never serialized
	SignRequests      bool              `json:"sign_requests,omitempty"`
	AttributeMappings map[string]string `json:"attribute_mappings,omitempty"`

	CreatedAt time.Time `json:"created_at"`
	UpdatedAt time.Time `json:"updated_at"`
}

func (*Connection) AllowsEmailDomain added in v1.9.0

func (c *Connection) AllowsEmailDomain(email string) bool

AllowsEmailDomain reports whether an email the provider asserted may sign in through this connection: its domain must be the connection's own or one of the listed allowed domains. A connection with neither configured accepts any domain, which is the shape code-configured providers have.

type CreateConnectionInput added in v1.9.0

type CreateConnectionInput struct {
	AppID    id.AppID
	OrgID    id.OrgID // zero value = app-wide (no org scope)
	Provider string
	Protocol string // "oidc" | "saml"
	Domain   string

	// OIDC
	Issuer       string
	ClientID     string
	ClientSecret string

	// SAML — supply one IdP source: MetadataURL, IDPMetadataXML, or
	// IDPSSOURL + IDPCertificate.
	MetadataURL       string
	IDPMetadataXML    string
	IDPSSOURL         string
	IDPCertificate    string
	EntityID          string
	ACSURL            string
	SignRequests      bool
	AttributeMappings map[string]string

	// Enforced requires users on this domain to sign in via SSO (password login
	// blocked). Usually toggled on later via UpdateConnection, not at create.
	Enforced bool
	// DisplayName is an optional admin-set label for the connection (cosmetic).
	DisplayName string
	// AllowedDomains and TrustedFederation confine and shape the identities
	// this connection may assert; see Connection.
	AllowedDomains    []string
	TrustedFederation bool
}

CreateConnectionInput describes an SSO connection to provision. It is the transport-agnostic form of AdminCreateConnectionRequest so host apps that expose their own tenant-scoped admin surface (instead of the platform-admin HTTP route) can create connections after their own authorization check.

type EncryptedStore added in v1.9.0

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

EncryptedStore wraps a Store, transparently encrypting the sensitive SSO connection secrets at rest — the OIDC ClientSecret and the SAML SP private key. Reads decrypt; bridge.AESGCMEncryptor tags its output with a version prefix and returns un-prefixed values unchanged on Decrypt, so pre-existing plaintext rows keep working and re-saving a connection upgrades it in place.

func NewEncryptedStore added in v1.9.0

func NewEncryptedStore(inner Store, enc bridge.Encryptor) *EncryptedStore

NewEncryptedStore wraps inner with secret encryption. A nil encryptor falls back to bridge.NoopEncryptor (no-op passthrough, e.g. dev without a key). Pass an AES-GCM encryptor (engine.TokenEncryptor()) in production.

func (*EncryptedStore) CreateConnection added in v1.9.0

func (s *EncryptedStore) CreateConnection(ctx context.Context, c *Connection) error

func (*EncryptedStore) CreateIdentity added in v1.9.0

func (s *EncryptedStore) CreateIdentity(ctx context.Context, ident *Identity) error

CreateIdentity delegates: identity bindings hold no secret.

func (*EncryptedStore) DeleteConnection added in v1.9.0

func (s *EncryptedStore) DeleteConnection(ctx context.Context, connID id.SSOConnectionID) error

func (*EncryptedStore) GetConnection added in v1.9.0

func (s *EncryptedStore) GetConnection(ctx context.Context, connID id.SSOConnectionID) (*Connection, error)

func (*EncryptedStore) GetConnectionByDomain added in v1.9.0

func (s *EncryptedStore) GetConnectionByDomain(ctx context.Context, appID id.AppID, domain string) (*Connection, error)

func (*EncryptedStore) GetConnectionByDomainAndOrg added in v1.9.0

func (s *EncryptedStore) GetConnectionByDomainAndOrg(ctx context.Context, appID id.AppID, orgID id.OrgID, domain string) (*Connection, error)

func (*EncryptedStore) GetConnectionByProvider added in v1.9.0

func (s *EncryptedStore) GetConnectionByProvider(ctx context.Context, appID id.AppID, provider string) (*Connection, error)

func (*EncryptedStore) GetIdentity added in v1.9.0

func (s *EncryptedStore) GetIdentity(ctx context.Context, connID id.SSOConnectionID, subject string) (*Identity, error)

GetIdentity delegates: identity bindings hold no secret.

func (*EncryptedStore) ListConnections added in v1.9.0

func (s *EncryptedStore) ListConnections(ctx context.Context, appID id.AppID) ([]*Connection, error)

func (*EncryptedStore) UpdateConnection added in v1.9.0

func (s *EncryptedStore) UpdateConnection(ctx context.Context, c *Connection) error

type ExchangeRequest added in v1.9.0

type ExchangeRequest struct {
	Code string `json:"code" description:"One-time code issued by the ACS redirect"`
}

ExchangeRequest is the body for POST /v1/sso/exchange.

type Identity added in v1.9.0

type Identity struct {
	ConnectionID id.SSOConnectionID `json:"connection_id"`
	Subject      string             `json:"subject"`
	UserID       id.UserID          `json:"user_id"`
	CreatedAt    time.Time          `json:"created_at"`
}

Identity binds an identity provider subject to a local user for one connection. A later login by the same subject lands on this user whatever email the provider asserts, and a login by a subject the connection has never seen falls back to email matching inside the connection's domains.

type LoginByDomainRequest added in v1.9.0

type LoginByDomainRequest struct {
	Email        string `json:"email" description:"Work email; its domain routes to the matching SSO connection"`
	ConnectionID string `` /* 143-byte string literal not displayed */
	ReturnURL    string `json:"return_url,omitempty" description:"Frontend URL to land on after login (must be allowlisted)"`
}

LoginByDomainRequest carries an email whose domain selects the IdP connection. ConnectionID pins the exact connection (multi-tenant SSO: the same domain can belong to several orgs, so home-realm discovery resolves the specific connection and passes its id here). When empty, the domain resolves the connection app-wide (single-tenant / legacy).

type LoginRequest added in v1.9.0

type LoginRequest struct {
	Provider  string `path:"provider"`
	ReturnURL string `` /* 128-byte string literal not displayed */
}

LoginRequest contains the path parameter for starting SSO.

type LoginResponse added in v1.9.0

type LoginResponse struct {
	LoginURL string `json:"login_url"`
	State    string `json:"state"`
}

LoginResponse is returned when the SSO flow is initiated.

type MemoryStore added in v1.9.0

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

MemoryStore is an in-memory Store for testing.

func NewMemoryStore added in v1.9.0

func NewMemoryStore() *MemoryStore

NewMemoryStore creates a new in-memory SSO connection store.

func (*MemoryStore) CreateConnection added in v1.9.0

func (s *MemoryStore) CreateConnection(_ context.Context, c *Connection) error

func (*MemoryStore) CreateIdentity added in v1.9.0

func (s *MemoryStore) CreateIdentity(_ context.Context, ident *Identity) error

func (*MemoryStore) DeleteConnection added in v1.9.0

func (s *MemoryStore) DeleteConnection(_ context.Context, connID id.SSOConnectionID) error

func (*MemoryStore) GetConnection added in v1.9.0

func (s *MemoryStore) GetConnection(_ context.Context, connID id.SSOConnectionID) (*Connection, error)

func (*MemoryStore) GetConnectionByDomain added in v1.9.0

func (s *MemoryStore) GetConnectionByDomain(_ context.Context, appID id.AppID, domain string) (*Connection, error)

func (*MemoryStore) GetConnectionByDomainAndOrg added in v1.9.0

func (s *MemoryStore) GetConnectionByDomainAndOrg(_ context.Context, appID id.AppID, orgID id.OrgID, domain string) (*Connection, error)

func (*MemoryStore) GetConnectionByProvider added in v1.9.0

func (s *MemoryStore) GetConnectionByProvider(_ context.Context, appID id.AppID, provider string) (*Connection, error)

func (*MemoryStore) GetIdentity added in v1.9.0

func (s *MemoryStore) GetIdentity(_ context.Context, connID id.SSOConnectionID, subject string) (*Identity, error)

func (*MemoryStore) ListConnections added in v1.9.0

func (s *MemoryStore) ListConnections(_ context.Context, appID id.AppID) ([]*Connection, error)

func (*MemoryStore) UpdateConnection added in v1.9.0

func (s *MemoryStore) UpdateConnection(_ context.Context, c *Connection) error

type MongoStore added in v1.9.0

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

MongoStore implements sso.Store using the Grove MongoDB driver.

func NewMongoStore added in v1.9.0

func NewMongoStore(db *grove.DB) *MongoStore

NewMongoStore creates a new MongoDB-backed SSO connection store.

func (*MongoStore) CreateConnection added in v1.9.0

func (s *MongoStore) CreateConnection(ctx context.Context, c *Connection) error

func (*MongoStore) CreateIdentity added in v1.9.0

func (s *MongoStore) CreateIdentity(ctx context.Context, ident *Identity) error

func (*MongoStore) DeleteConnection added in v1.9.0

func (s *MongoStore) DeleteConnection(ctx context.Context, connID id.SSOConnectionID) error

func (*MongoStore) GetConnection added in v1.9.0

func (s *MongoStore) GetConnection(ctx context.Context, connID id.SSOConnectionID) (*Connection, error)

func (*MongoStore) GetConnectionByDomain added in v1.9.0

func (s *MongoStore) GetConnectionByDomain(ctx context.Context, appID id.AppID, domain string) (*Connection, error)

func (*MongoStore) GetConnectionByDomainAndOrg added in v1.9.0

func (s *MongoStore) GetConnectionByDomainAndOrg(ctx context.Context, appID id.AppID, orgID id.OrgID, domain string) (*Connection, error)

func (*MongoStore) GetConnectionByProvider added in v1.9.0

func (s *MongoStore) GetConnectionByProvider(ctx context.Context, appID id.AppID, provider string) (*Connection, error)

func (*MongoStore) GetIdentity added in v1.9.0

func (s *MongoStore) GetIdentity(ctx context.Context, connID id.SSOConnectionID, subject string) (*Identity, error)

func (*MongoStore) ListConnections added in v1.9.0

func (s *MongoStore) ListConnections(ctx context.Context, appID id.AppID) ([]*Connection, error)

func (*MongoStore) UpdateConnection added in v1.9.0

func (s *MongoStore) UpdateConnection(ctx context.Context, c *Connection) error

type OIDCConfig added in v1.9.0

type OIDCConfig struct {
	// Name is the provider's stable name, used in routes and state.
	Name string
	// Issuer is the OpenID provider's issuer URL. Discovery is fetched from
	// Issuer + "/.well-known/openid-configuration".
	Issuer string
	// ClientID and ClientSecret are the relying party credentials.
	ClientID     string
	ClientSecret string
	// RedirectURL is the callback registered with the provider.
	RedirectURL string
	// Scopes defaults to openid, profile and email.
	Scopes []string
	// HTTPClient overrides the client used for discovery, the token exchange
	// and userinfo. Nil selects the hardened client, which refuses private
	// addresses and plain http; tests point it at a local server.
	HTTPClient *http.Client
	// contains filtered or unexported fields
}

OIDCConfig configures an OpenID Connect SSO provider.

type Plugin

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

Plugin is the SSO authentication plugin.

func New added in v1.9.0

func New(cfg ...Config) *Plugin

New creates a new SSO plugin.

func (*Plugin) Connections added in v1.9.0

func (p *Plugin) Connections() []string

Connections returns the list of configured SSO connection names for client config.

func (*Plugin) CreateConnection added in v1.9.0

func (p *Plugin) CreateConnection(ctx context.Context, in CreateConnectionInput) (*Connection, error)

CreateConnection provisions an SSO connection: it resolves the app's default environment (env_id is NOT NULL), validates the protocol-specific fields, and for SAML derives the SP EntityID/ACS URL from PublicBaseURL and generates a self-signed SP keypair when none is supplied. This is the shared core behind handleAdminCreateConnection; host apps call it directly behind their own authorization instead of the platform-admin HTTP route.

func (*Plugin) DeclareSettings added in v1.9.0

func (p *Plugin) DeclareSettings(m *settings.Manager) error

DeclareSettings implements plugin.SettingsProvider.

func (*Plugin) MigrationGroups added in v1.9.0

func (p *Plugin) MigrationGroups(driverName string) []*migrate.Group

MigrationGroups implements plugin.MigrationProvider.

func (*Plugin) Name added in v1.9.0

func (p *Plugin) Name() string

Name returns the plugin name.

func (*Plugin) OnBeforeSignIn added in v1.9.0

func (p *Plugin) OnBeforeSignIn(ctx context.Context, req *account.SignInRequest) error

OnBeforeSignIn enforces SSO: when a user's email domain has an active + enforced SSO connection, password sign-in is vetoed — they must use SSO. Workspace owners/admins are exempt (break-glass, so a misconfigured IdP can't lock out the people who administer it). Non-email sign-ins and domains without an enforced connection pass through untouched.

func (*Plugin) OnInit added in v1.9.0

func (p *Plugin) OnInit(_ context.Context, engine plugin.Engine) error

OnInit captures the store reference and bridges from the engine.

func (*Plugin) ProviderNames added in v1.9.0

func (p *Plugin) ProviderNames() []string

Providers returns the list of configured provider names.

func (*Plugin) RegisterContract added in v1.9.0

func (p *Plugin) RegisterContract(
	d *dispatcher.Dispatcher,
	reg contract.Registry,
	wreg contract.WardenRegistry,
	engine plugin.Engine,
) error

func (*Plugin) RegisterRoutes

func (p *Plugin) RegisterRoutes(router forge.Router) error

RegisterRoutes registers SSO HTTP endpoints on a forge.Router.

func (*Plugin) SSOStore added in v1.9.0

func (p *Plugin) SSOStore() Store

SSOStore returns the connection store so an embedding host app can read/manage connections directly (e.g. list by domain, toggle active) behind its own authorization. Returns nil before OnInit has wired a store.

func (*Plugin) SetAppID added in v1.9.0

func (p *Plugin) SetAppID(appID string)

SetAppID sets the default app ID.

func (*Plugin) SetSSOStore added in v1.9.0

func (p *Plugin) SetSSOStore(s Store)

SetSSOStore sets the SSO connection store.

func (*Plugin) SetStore added in v1.9.0

func (p *Plugin) SetStore(s store.Store)

SetStore sets the core store for testing.

type PostgresStore added in v1.9.0

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

PostgresStore implements sso.Store using the Grove ORM with PostgreSQL.

func NewPostgresStore added in v1.9.0

func NewPostgresStore(db *grove.DB) *PostgresStore

NewPostgresStore creates a new PostgreSQL-backed SSO connection store.

func (*PostgresStore) CreateConnection added in v1.9.0

func (s *PostgresStore) CreateConnection(ctx context.Context, c *Connection) error

func (*PostgresStore) CreateIdentity added in v1.9.0

func (s *PostgresStore) CreateIdentity(ctx context.Context, ident *Identity) error

func (*PostgresStore) DeleteConnection added in v1.9.0

func (s *PostgresStore) DeleteConnection(ctx context.Context, connID id.SSOConnectionID) error

func (*PostgresStore) GetConnection added in v1.9.0

func (s *PostgresStore) GetConnection(ctx context.Context, connID id.SSOConnectionID) (*Connection, error)

func (*PostgresStore) GetConnectionByDomain added in v1.9.0

func (s *PostgresStore) GetConnectionByDomain(ctx context.Context, appID id.AppID, domain string) (*Connection, error)

func (*PostgresStore) GetConnectionByDomainAndOrg added in v1.9.0

func (s *PostgresStore) GetConnectionByDomainAndOrg(ctx context.Context, appID id.AppID, orgID id.OrgID, domain string) (*Connection, error)

func (*PostgresStore) GetConnectionByProvider added in v1.9.0

func (s *PostgresStore) GetConnectionByProvider(ctx context.Context, appID id.AppID, provider string) (*Connection, error)

func (*PostgresStore) GetIdentity added in v1.9.0

func (s *PostgresStore) GetIdentity(ctx context.Context, connID id.SSOConnectionID, subject string) (*Identity, error)

func (*PostgresStore) ListConnections added in v1.9.0

func (s *PostgresStore) ListConnections(ctx context.Context, appID id.AppID) ([]*Connection, error)

func (*PostgresStore) UpdateConnection added in v1.9.0

func (s *PostgresStore) UpdateConnection(ctx context.Context, c *Connection) error

type Provider added in v1.9.0

type Provider interface {
	// Name returns the unique identifier for this provider (e.g. "okta", "azure-ad").
	Name() string

	// Protocol returns the SSO protocol ("oidc" or "saml").
	Protocol() string

	// LoginURL returns the URL to redirect the user to for authentication.
	LoginURL(state string) (string, error)

	// HandleCallback processes the callback from the identity provider
	// and returns the authenticated user's identity.
	HandleCallback(ctx context.Context, params map[string]string) (*User, error)
}

Provider is the interface for SSO identity providers.

func NewOIDCProvider added in v1.9.0

func NewOIDCProvider(cfg OIDCConfig) Provider

NewOIDCProvider creates an OIDC SSO provider. It uses OIDC discovery to find the authorization, token and userinfo endpoints and falls back to the conventional paths under the issuer when discovery is unavailable.

func NewSAMLProvider added in v1.9.0

func NewSAMLProvider(cfg SAMLConfig) (Provider, error)

NewSAMLProvider builds a SAML provider from cfg. It resolves the IdP metadata/cert up front so a misconfigured connection fails fast rather than at first login.

type SAMLConfig added in v1.9.0

type SAMLConfig struct {
	// Name is the unique provider identifier (e.g. "okta-saml").
	Name string

	// IdP description — resolved in this order: pasted metadata XML, a
	// metadata URL to fetch, or an explicit SSO URL + signing certificate.
	IDPMetadataXML    string
	MetadataURL       string
	IDPSSOURL         string
	IDPCertificatePEM string

	// Service Provider identity.
	EntityID         string // SP entity ID (assertion audience)
	ACSURL           string // SP Assertion Consumer Service URL
	SPCertificatePEM string // SP signing cert (PEM); optional
	SPPrivateKeyPEM  string // SP signing key (PEM); optional, enables signed AuthnRequests
	SignRequests     bool

	// AllowIDPInitiated permits unsolicited assertions (no prior AuthnRequest).
	AllowIDPInitiated bool

	// AttributeMap maps SAML attribute names to user fields
	// ("email" | "first_name" | "last_name" | "groups"). Empty uses defaults.
	AttributeMap map[string]string
	// contains filtered or unexported fields
}

SAMLConfig configures a SAML 2.0 identity provider connection.

type SAMLMetadataProvider added in v1.9.0

type SAMLMetadataProvider interface {
	Metadata() (xml []byte, contentType string, err error)
}

SAMLMetadataProvider is implemented by SAML providers that can emit SP metadata XML for IdP configuration.

type SqliteStore added in v1.9.0

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

SqliteStore implements sso.Store using the Grove ORM with SQLite.

func NewSqliteStore added in v1.9.0

func NewSqliteStore(db *grove.DB) *SqliteStore

NewSqliteStore creates a new SQLite-backed SSO connection store.

func (*SqliteStore) CreateConnection added in v1.9.0

func (s *SqliteStore) CreateConnection(ctx context.Context, c *Connection) error

func (*SqliteStore) CreateIdentity added in v1.9.0

func (s *SqliteStore) CreateIdentity(ctx context.Context, ident *Identity) error

func (*SqliteStore) DeleteConnection added in v1.9.0

func (s *SqliteStore) DeleteConnection(ctx context.Context, connID id.SSOConnectionID) error

func (*SqliteStore) GetConnection added in v1.9.0

func (s *SqliteStore) GetConnection(ctx context.Context, connID id.SSOConnectionID) (*Connection, error)

func (*SqliteStore) GetConnectionByDomain added in v1.9.0

func (s *SqliteStore) GetConnectionByDomain(ctx context.Context, appID id.AppID, domain string) (*Connection, error)

func (*SqliteStore) GetConnectionByDomainAndOrg added in v1.9.0

func (s *SqliteStore) GetConnectionByDomainAndOrg(ctx context.Context, appID id.AppID, orgID id.OrgID, domain string) (*Connection, error)

func (*SqliteStore) GetConnectionByProvider added in v1.9.0

func (s *SqliteStore) GetConnectionByProvider(ctx context.Context, appID id.AppID, provider string) (*Connection, error)

func (*SqliteStore) GetIdentity added in v1.9.0

func (s *SqliteStore) GetIdentity(ctx context.Context, connID id.SSOConnectionID, subject string) (*Identity, error)

func (*SqliteStore) ListConnections added in v1.9.0

func (s *SqliteStore) ListConnections(ctx context.Context, appID id.AppID) ([]*Connection, error)

func (*SqliteStore) UpdateConnection added in v1.9.0

func (s *SqliteStore) UpdateConnection(ctx context.Context, c *Connection) error

type Store added in v1.9.0

type Store interface {
	CreateConnection(ctx context.Context, c *Connection) error
	GetConnection(ctx context.Context, connID id.SSOConnectionID) (*Connection, error)
	GetConnectionByDomain(ctx context.Context, appID id.AppID, domain string) (*Connection, error)
	// GetConnectionByDomainAndOrg resolves the active connection for a domain
	// within a specific org (workspace). Multi-tenant SSO allows the same domain
	// in several orgs, so lookups that must land on one workspace use this.
	GetConnectionByDomainAndOrg(ctx context.Context, appID id.AppID, orgID id.OrgID, domain string) (*Connection, error)
	GetConnectionByProvider(ctx context.Context, appID id.AppID, provider string) (*Connection, error)
	ListConnections(ctx context.Context, appID id.AppID) ([]*Connection, error)
	UpdateConnection(ctx context.Context, c *Connection) error
	DeleteConnection(ctx context.Context, connID id.SSOConnectionID) error

	// GetIdentity resolves the user bound to a provider subject on one
	// connection. CreateIdentity records the binding and fails on a subject
	// the connection already holds.
	GetIdentity(ctx context.Context, connID id.SSOConnectionID, subject string) (*Identity, error)
	CreateIdentity(ctx context.Context, ident *Identity) error
}

Store persists SSO connections.

type User added in v1.9.0

type User struct {
	ProviderUserID string            `json:"provider_user_id"`
	Email          string            `json:"email"`
	FirstName      string            `json:"first_name"`
	LastName       string            `json:"last_name"`
	Groups         []string          `json:"groups,omitempty"`
	Attributes     map[string]string `json:"attributes,omitempty"`
	// EmailVerified is the provider's own statement about the address, when
	// it made one: OIDC's email_verified claim. Nil means the provider said
	// nothing; an explicit false refuses the login.
	EmailVerified *bool `json:"email_verified,omitempty"`
}

User represents the identity returned by an SSO provider.

Directories

Path Synopsis
Package contract wires the sso plugin's deep-link settings page into the forge-dashboard contract registry.
Package contract wires the sso plugin's deep-link settings page into the forge-dashboard contract registry.
Package ssotest provides a backend-agnostic conformance suite for the sso.Store interface.
Package ssotest provides a backend-agnostic conformance suite for the sso.Store interface.

Jump to

Keyboard shortcuts

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