Skip to Content
ProvidersIdentity

Identity

Identity providers handle platform login for the Gestalt server. Identity is separate from authorization configuration, which decides what the caller may access, and External Credentials, which provide upstream credentials for configured connections.

How identity providers work

When a request arrives without a valid session, Gestalt asks the configured identity provider to start login through Authorize, redirects the user to the upstream identity provider, and then calls Token to complete the callback. After that, Gestalt issues and stores its own session.

For API access, Gestalt validates bearer tokens through the provider’s Introspect handler. Profile lookup and personal API-token management use UserInfo and the grant-management RPCs.

First-Party Identity Providers

Published first-party identity providers live under valon-technologies/gestalt-providers/auth. The auth/oidc source path is retained for registry compatibility.

ProviderUse case
github.com/valon-technologies/gestalt-providers/auth/oidcGeneric OpenID Connect providers such as Google, Okta, Auth0, Azure AD, or Keycloak

Configuring providers.identity

server: providers: identity: oidc providers: identity: oidc: source: package: github.com/valon-technologies/gestalt-providers/auth/oidc version: 0.0.1-alpha.1 config: issuerUrl: https://login.example.com clientId: ... clientSecret: ...

Structured secret refs such as clientSecret.secret.provider: default are resolved through providers.secrets before the identity provider receives its config.

To disable platform identity entirely, omit providers.identity.

For local development, omission is the simple default: every request is treated as the same anonymous user.

Local source during development

providers: identity: oidc: source: ./oidc-auth/manifest.yaml config: issuerUrl: https://login.example.com clientId: ... clientSecret: ...

Building your own identity provider

Manifest

An identity provider manifest declares kind: identity. You can optionally include a configSchemaPath so Gestalt validates provider config at startup.

kind: identity source: github.com/your-org/identity/google version: 0.0.1 displayName: Google Identity description: Issue and resolve identities with Google OAuth. spec: configSchemaPath: ./identity_config.json

Interface

The identity surface implements RFC 6749 authorization and token flows, RFC 7662 introspection, OIDC-style UserInfo, OIDF grant management for API tokens, and RFC 8693 token exchange where supported. Authorize returns a redirect URI that Gestalt sends the browser to. Token exchanges callback parameters or subject tokens for bearer credentials. Introspect resolves bearer tokens to canonical Gestalt subject IDs.

package googleidentity import ( "context" "fmt" gestalt "github.com/valon-technologies/gestalt/sdk/go" "golang.org/x/oauth2" googleoauth "golang.org/x/oauth2/google" ) type GoogleIdentity struct { clientID string clientSecret string } func New() *GoogleIdentity { return &GoogleIdentity{} } func (g *GoogleIdentity) Configure(_ context.Context, _ string, config map[string]any) error { g.clientID, _ = config["clientId"].(string) g.clientSecret, _ = config["clientSecret"].(string) if g.clientID == "" || g.clientSecret == "" { return fmt.Errorf("clientId and clientSecret are required") } return nil } func (g *GoogleIdentity) Authorize(_ context.Context, req *gestalt.AuthorizeRequest) (*gestalt.AuthorizeResponse, error) { cfg := &oauth2.Config{ ClientID: g.clientID, ClientSecret: g.clientSecret, RedirectURL: req.RedirectURI, Scopes: []string{"openid", "email", "profile"}, Endpoint: googleoauth.Endpoint, } return &gestalt.AuthorizeResponse{ RedirectURI: cfg.AuthCodeURL(req.State, oauth2.AccessTypeOffline), }, nil } func (g *GoogleIdentity) Token(ctx context.Context, req *gestalt.TokenRequest) (*gestalt.TokenResponse, error) { // Exchange req.Code for a token when req.GrantType is authorization_code, // then return the upstream access token and expiry. _ = ctx _ = req return &gestalt.TokenResponse{ AccessToken: "...", TokenType: "Bearer", ExpiresIn: 3600, }, nil } func (g *GoogleIdentity) Introspect(_ context.Context, req *gestalt.IntrospectRequest) (*gestalt.IntrospectResponse, error) { if req.Token == "" { return &gestalt.IntrospectResponse{Active: false}, nil } // Validate the bearer token with the upstream issuer, then map the // token claims into a canonical Gestalt subject ID. return &gestalt.IntrospectResponse{ Active: true, Subject: "user:user@example.com", }, nil } func (g *GoogleIdentity) UserInfo(_ context.Context, _ *gestalt.UserInfoRequest) (*gestalt.UserInfoResponse, error) { return &gestalt.UserInfoResponse{ SubjectID: "user:user@example.com", Email: "user@example.com", Name: "Example User", }, nil } func (g *GoogleIdentity) ListGrants(ctx context.Context, _ *gestalt.ListGrantsRequest) (*gestalt.ListGrantsResponse, error) { _ = gestalt.IdentityCallContextFromContext(ctx) return &gestalt.ListGrantsResponse{GrantIDs: []string{}}, nil } func (g *GoogleIdentity) GetGrant(ctx context.Context, _ *gestalt.GetGrantRequest) (*gestalt.GetGrantResponse, error) { _ = gestalt.IdentityCallContextFromContext(ctx) return &gestalt.GetGrantResponse{}, nil } func (g *GoogleIdentity) RevokeGrant(ctx context.Context, _ *gestalt.RevokeGrantRequest) (*gestalt.RevokeGrantResponse, error) { _ = gestalt.IdentityCallContextFromContext(ctx) return &gestalt.RevokeGrantResponse{}, nil } var _ gestalt.IdentityProvider = (*GoogleIdentity)(nil)

Serve with gestalt.ServeIdentityProvider(ctx, provider).

Optional interfaces

Two areas extend the base contract beyond the required RPCs.

Bearer token introspection

Bearer token introspection lets API clients present a token directly instead of going through the browser login flow. Return active: false when the token is not recognized. Gestalt routes both session cookies and Authorization: Bearer headers through Introspect.

func (g *GoogleIdentity) Introspect(_ context.Context, req *gestalt.IntrospectRequest) (*gestalt.IntrospectResponse, error) { if req.Token == "" { return &gestalt.IntrospectResponse{Active: false}, nil } // Validate the bearer token with the upstream issuer, then map the // token claims into a canonical Gestalt subject ID. return &gestalt.IntrospectResponse{ Active: true, Subject: "user:user-123", Scope: "openid email profile", }, nil }

Session lifetime

Session lifetime controls how long Gestalt persists a successful browser login. For first-party OIDC providers, set sessionTtl in providers.identity.<name>.config. The default is 24h. See providers.identity in the config reference for the full field list.

Grant-management RPCs receive caller-scoped metadata through IdentityCallContext. In Go, attach it with gestalt.WithIdentityCallContext before invoking grant handlers from host code.

Deploying the provider

Configure a custom identity provider through the same providers.identity block shown in Configuring providers.identity. That section is the source of truth for local manifest paths, published provider release URLs, and structured secret refs. For packaging and release mechanics, see Releasing provider packages.