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.
| Provider | Use case |
|---|---|
github.com/valon-technologies/gestalt-providers/auth/oidc | Generic 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.jsonInterface
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.
Go
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.
Go
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.
What To Read Next
- Secrets: secret managers for identity provider config
- Configuration: full config examples including
providers.identity - Built-in Providers: first-party identity reference