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

gosso

package module
v1.1.0 Latest Latest
Warning

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

Go to latest
Published: Oct 17, 2026 License: MIT Imports: 20 Imported by: 0

README

Go Reference CI

Shared OpenID Connect login package used by GoDash, GoCron and friends. One implementation of the browser handshake, cookie handling and session strategies so the apps behave identically: separate login page, /auth/callback, PKCE S256, state cookie, optional app_url for redirect- and proxy-safe callback URLs.

Install

go get github.com/flohoss/gosso@v1.0.1

Requires Go 1.27 or newer.

What it provides

  • Config — issuer, client credentials, session TTL, cookie_secure, app_url, cookie prefix. Validate() enforces issuer/secret rules.
  • Auth — Echo v5 middleware and login lifecycle:
    • StartLogin(ctx, redirect) — sets the *_oidc_state cookie (state + PKCE verifier + optional redirect target, HttpOnly, SameSite=Lax, 10 minute TTL) and 302s to the provider.
    • ConsumeLoginState(ctx) — validates and clears the state cookie, returns state, verifier and the sanitized redirect target.
    • Exchange(ctx, code, verifier) — code exchange and id_token verification, returns the subject.
    • Authenticate, ClearSession, SessionTTL, Middleware.
  • SessionManager interface — bring your own store (DB-backed jti, like GoCron's SQLite sessions) or use the built-in:
  • HMACSessions — stateless signed cookie (subject|expiry + HMAC-SHA256, base64url), sliding refresh once half the TTL has passed. Requires a SessionKey of at least 32 characters (gosso.MinSessionKeyLength).
  • LoginError codes (rejected, expired, unavailable, failed) and ClassifyLoginError(err) so handlers can redirect to the login page with the same error codes everywhere.
  • Lazy background discovery (provider.Warm) with a 10 second timeout; failed discovery is retried on the next request instead of being cached.

Cookies are <cookie_prefix>_oidc_state (from Config.CookiePrefix, which defaults to the client ID) and the session cookie named by your SessionManager. HMACSessions takes the name from HMACOptions.CookieName, or HMACOptions.Prefix + _session. Give each app a distinct client ID (or set the prefix/name) to avoid collisions on a shared domain.

Usage sketch

sessions := gosso.NewHMACSessions(gosso.HMACOptions{
	Prefix:   "myapp",
	Settings: func() gosso.Config { return config.SSO() },
})

auth := gosso.New(
	func() gosso.Config { return config.SSO() },
	sessions,
	"/auth/callback",
	"/login",
)

e.GET("/login", loginPage)
e.GET("/auth/oidc", func(c *echo.Context) error { return auth.StartLogin(c, c.QueryParam("redirect")) })
e.GET("/auth/callback", func(c *echo.Context) error {
	state, verifier, redirect, err := auth.ConsumeLoginState(c)
	if err != nil {
		return c.Redirect(http.StatusFound, "/login?error=expired")
	}
	if c.QueryParam("state") != state {
		return c.Redirect(http.StatusFound, "/login?error=expired")
	}
	subject, err := auth.Exchange(c, c.QueryParam("code"), verifier)
	if err != nil {
		return c.Redirect(http.StatusFound, "/login?error="+string(gosso.ClassifyLoginError(err)))
	}
	if err := auth.StartSession(c, subject); err != nil {
		return err
	}
	return c.Redirect(http.StatusFound, redirect)
})

e.Use(auth.Middleware())

Settings should come from the app config so hot reloads take effect; read them through the func() Config indirection.

Logout

ClearSession only clears the local session — no round-trip to a provider end_session_endpoint. For DB-backed stores also delete the stored session row before clearing the cookie.

License

MIT

Documentation

Overview

Package gosso implements the shared OpenID Connect login flow used by GoDash, GoCron and friends: the browser handshake, state and session cookies, PKCE S256 code exchange against a discovered provider, and pluggable session storage.

Start with Config for the issuer credentials and Auth for the Echo v5 routes and middleware. HandleCallback runs the whole callback choreography and returns either a LoginResult or a classified LoginFailure for the login page redirect; Auth.AuthorizeSubject can optionally refuse sessions for individual subjects. HMACSessions is the built-in signed-cookie SessionManager — its sessions are stateless and cannot be revoked before their expiry. Implement SessionManager to back sessions with your own store when immediate revocation matters.

Settings are read through a func() Config indirection so apps can reload configuration without rebuilding the Auth instance.

Index

Constants

View Source
const DefaultSessionTTL = 24 * time.Hour

DefaultSessionTTL is the session lifetime used when Config.SessionTTL is not positive.

View Source
const DiscoveryTimeout = 10 * time.Second

DiscoveryTimeout bounds a single OIDC discovery attempt.

View Source
const MinSessionKeyLength = 32

MinSessionKeyLength is the shortest accepted signing key for HMACSessions.

Variables

View Source
var (
	// ErrInvalidIssuer is returned when IssuerURL is not an absolute URL or has a trailing slash.
	ErrInvalidIssuer = errors.New("sso configuration error: issuer_url must be a valid absolute URL without a trailing slash")
	// ErrMissingOIDCFields is returned when a required credential is empty while SSO is enabled.
	ErrMissingOIDCFields = errors.New("sso configuration error: issuer_url, client_id and client_secret are required when SSO is enabled")
)
View Source
var (
	// ErrUnauthenticated is returned when no valid session is present.
	ErrUnauthenticated = errors.New("not authenticated")
	// ErrProviderUnavailable is returned when OIDC discovery could not reach the issuer.
	ErrProviderUnavailable = errors.New("identity provider is not reachable")
	// ErrInvalidSessionKey is returned by ValidateSessionKey for short signing keys.
	ErrInvalidSessionKey = errors.New("session signing key must be at least 32 characters")
	// ErrStateMismatch is returned when the callback state does not match the login state cookie.
	ErrStateMismatch = errors.New("callback state does not match the login state")
	// ErrSubjectUnauthorized is returned when AuthorizeSubject refuses a verified subject.
	ErrSubjectUnauthorized = errors.New("subject is not authorized to sign in")
)
View Source
var ErrInvalidSession = errors.New("invalid session cookie")

ErrInvalidSession is returned for a session cookie that is malformed, tampered with or expired.

Functions

func DefaultCookiePrefix added in v1.1.0

func DefaultCookiePrefix(config Config) string

DefaultCookiePrefix returns the cookie prefix from the config, or one derived from the ClientID.

func DefaultScopes

func DefaultScopes() []string

DefaultScopes returns the scopes requested when Config.Scopes is empty.

func RandomToken

func RandomToken(size int) (string, error)

RandomToken returns size cryptographically random bytes as a hex string.

func SanitizeRedirect

func SanitizeRedirect(raw string) string

SanitizeRedirect keeps only same-origin absolute paths and falls back to "/" for anything else. Percent-encoded and backslash variants that browsers normalize back into protocol-relative URLs, such as "/%2F%2Fevil.example.com", are rejected as well.

func SetLogger

func SetLogger(l *slog.Logger)

SetLogger replaces the package logger used for discovery diagnostics. A nil logger is ignored.

func SignHMAC

func SignHMAC(key, subject string, expiresAt time.Time) string

SignHMAC returns the base64url payload and HMAC-SHA256 signature for subject and expiresAt.

func ValidLoginErrorCode

func ValidLoginErrorCode(raw string) bool

ValidLoginErrorCode reports whether raw is one of the known LoginError codes.

func ValidateSessionKey

func ValidateSessionKey(key string) error

ValidateSessionKey reports ErrInvalidSessionKey when key is shorter than MinSessionKeyLength.

func VerifyHMAC

func VerifyHMAC(key, value string, now time.Time) (string, time.Time, error)

VerifyHMAC validates the signature of value and returns the subject and expiry it carries.

Types

type Auth

type Auth struct {
	Sessions SessionManager

	Settings     func() Config
	CallbackPath string
	LoginPath    string

	// AuthorizeSubject optionally gates logins: when it returns false for the verified
	// subject, the callback fails with LoginErrorRejected before a session is started.
	AuthorizeSubject func(subject string) bool
	// contains filtered or unexported fields
}

Auth runs the Echo v5 login lifecycle and the session middleware for a single app.

func New

func New(settings func() Config, sessions SessionManager, callbackPath, loginPath string) *Auth

New wires an Auth to settings and a session store. Enabled settings warm provider discovery in the background.

func (*Auth) Authenticate

func (a *Auth) Authenticate(echoContext *echo.Context) (*User, error)

Authenticate resolves the current user from the session store.

func (*Auth) CallbackURL

func (a *Auth) CallbackURL(c *echo.Context) string

CallbackURL returns the redirect URI registered with the provider for this request.

func (*Auth) ClearSession

func (a *Auth) ClearSession(echoContext *echo.Context)

ClearSession removes the local session, without contacting the provider.

func (*Auth) ConsumeLoginState

func (a *Auth) ConsumeLoginState(echoContext *echo.Context) (state, verifier, redirect string, err error)

ConsumeLoginState validates and clears the state cookie and returns the state, PKCE verifier and redirect target. The cookie is cleared before it is read on purpose: one-shot semantics come from the response expiring it, while the request copy stays readable for this handler.

func (*Auth) Enabled

func (a *Auth) Enabled() bool

Enabled reports whether SSO is switched on, tolerating a nil receiver.

func (*Auth) Exchange

func (a *Auth) Exchange(echoContext *echo.Context, code, verifier string) (string, error)

Exchange redeems the authorization code and returns the verified subject of the id_token.

func (*Auth) HandleCallback added in v1.1.0

func (a *Auth) HandleCallback(echoContext *echo.Context) (*LoginResult, *LoginFailure)

HandleCallback finishes the OIDC login for a callback request: it rejects provider-reported errors, consumes and checks the state cookie, exchanges the code, applies AuthorizeSubject and starts the session. It returns the login result or a classified failure for the login page redirect.

func (*Auth) Middleware

func (a *Auth) Middleware() echo.MiddlewareFunc

Middleware redirects unauthenticated requests to LoginPath, or answers 401 when no login path is set.

func (*Auth) ProviderResolve

func (a *Auth) ProviderResolve() (bool, error)

ProviderResolve resolves the provider now and reports whether discovery succeeded.

func (*Auth) Resolved

func (a *Auth) Resolved() bool

Resolved reports whether provider discovery has already completed successfully.

func (*Auth) SessionTTL

func (a *Auth) SessionTTL() time.Duration

SessionTTL returns the configured session lifetime, falling back to DefaultSessionTTL.

func (*Auth) StartLogin

func (a *Auth) StartLogin(echoContext *echo.Context, redirect string) error

StartLogin writes the state cookie and redirects the browser to the provider.

func (*Auth) StartSession

func (a *Auth) StartSession(echoContext *echo.Context, subject string) error

StartSession stores a session for subject through the configured SessionManager.

type Config

type Config struct {
	IssuerURL    string
	ClientID     string
	ClientSecret string
	SessionKey   string
	SessionTTL   time.Duration
	CookieSecure bool
	AppURL       string
	Scopes       []string
	CookiePrefix string
	Enabled      bool
}

Config holds the OpenID Connect settings shared by all apps using this package.

func (Config) EffectiveSessionTTL added in v1.1.0

func (c Config) EffectiveSessionTTL() time.Duration

EffectiveSessionTTL returns SessionTTL, falling back to DefaultSessionTTL when it is not positive.

func (Config) Validate

func (c Config) Validate() error

Validate reports whether the config carries usable issuer credentials and, when set, a parsable AppURL.

func (Config) WithDefaults

func (c Config) WithDefaults() Config

WithDefaults returns a copy with SessionTTL and Scopes filled in when they are empty.

type CookieOptions

type CookieOptions struct {
	Secure func() bool
}

CookieOptions builds the HttpOnly, SameSite=Lax cookies shared by the session implementations.

func (CookieOptions) Cookie

func (o CookieOptions) Cookie(name, value string, maxAge int, expires time.Time) *http.Cookie

Cookie returns the cookie with the shared attributes applied. A negative maxAge expires it.

type HMACOptions

type HMACOptions struct {
	Prefix     string
	CookieName string
	Settings   func() Config
}

HMACOptions configures NewHMACSessions. CookieName takes precedence over Prefix.

type HMACSessions

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

HMACSessions is a SessionManager backed by a signed, stateless cookie.

func NewHMACSessions

func NewHMACSessions(opts HMACOptions) *HMACSessions

NewHMACSessions returns the cookie name from opts.CookieName, or opts.Prefix with "_session" appended.

func (*HMACSessions) Authenticate

func (h *HMACSessions) Authenticate(echoContext *echo.Context) (*User, error)

Authenticate verifies the session cookie and refreshes it once half the TTL has passed. Sessions are stateless by design: they cannot be revoked before their expiry, so back them with a custom SessionManager when immediate revocation matters.

func (*HMACSessions) Clear

func (h *HMACSessions) Clear(echoContext *echo.Context) error

Clear expires the session cookie.

func (*HMACSessions) CookieName

func (h *HMACSessions) CookieName() string

CookieName returns the name of the session cookie.

func (*HMACSessions) Start

func (h *HMACSessions) Start(echoContext *echo.Context, subject string, expiresAt time.Time) error

Start writes the signed session cookie for subject, expiring at expiresAt.

type LoginError

type LoginError string

LoginError is the stable code an app redirects to its login page with.

const (
	// LoginErrorRejected marks a login the provider refused.
	LoginErrorRejected LoginError = "rejected"
	// LoginErrorExpired marks a missing, stale or mismatched login state.
	LoginErrorExpired LoginError = "expired"
	// LoginErrorUnavailable marks an unreachable identity provider.
	LoginErrorUnavailable LoginError = "unavailable"
	// LoginErrorFailed marks any other login failure.
	LoginErrorFailed LoginError = "failed"

	// LoginQueryParam is the query parameter carrying a LoginError code on the login page.
	LoginQueryParam = "error"
)

func ClassifyLoginError

func ClassifyLoginError(err error) LoginError

ClassifyLoginError maps an error to the LoginError code a handler should redirect with.

type LoginFailure added in v1.1.0

type LoginFailure struct {
	// Code is the stable LoginError code for the login page redirect.
	Code LoginError
	// Cause is the underlying error, safe to log but not to expose to the browser.
	Cause error
}

LoginFailure reports why an OIDC callback failed.

type LoginResult added in v1.1.0

type LoginResult struct {
	// Subject is the verified subject of the id_token.
	Subject string
	// Redirect is the sanitized, same-origin path requested before the login.
	Redirect string
}

LoginResult reports a successful OIDC callback.

type Provider

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

Provider caches OIDC discovery for the current issuer and client ID.

func NewProvider

func NewProvider(settings func() Config) *Provider

NewProvider returns a Provider reading its settings through the supplied function.

func (*Provider) Exchange

func (p *Provider) Exchange(ctx context.Context, settings Config, code, verifier, redirectURL string) (string, error)

Exchange redeems the code with PKCE, verifies the id_token and returns its subject.

func (*Provider) Login

func (p *Provider) Login(settings Config, redirectURL string) (oauth2.Config, error)

Login returns the oauth2.Config used to build the authorization redirect.

func (*Provider) Warm

func (p *Provider) Warm()

Warm starts discovery in the background so the first login request does not pay for it.

type SessionManager

type SessionManager interface {
	Start(echoContext *echo.Context, subject string, expiresAt time.Time) error
	Authenticate(echoContext *echo.Context) (*User, error)
	Clear(echoContext *echo.Context) error
	CookieName() string
}

SessionManager stores and reads the session behind a request so apps can swap in their own store.

type User

type User struct {
	Subject string
}

User is the authenticated identity derived from a session.

Jump to

Keyboard shortcuts

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