Documentation
¶
Overview ¶
Package dex provides a testcontainers module for the Dex OIDC provider.
Supported grants: authorization_code, refresh_token, password. The client_credentials grant requires Dex ≥ v2.46.0 (or dexidp/dex:master) together with WithEnableClientCredentials() — this sets the DEX_CLIENT_CREDENTIAL_GRANT_ENABLED_BY_DEFAULT=true env var that gates the feature. Earlier releases return unsupported_grant_type.
Example:
ctx := context.Background()
app, err := dex.NewClient("my-app",
dex.WithClientSecret("s3cr3t"),
dex.WithClientRedirectURIs("http://localhost/callback"),
)
if err != nil { log.Fatal(err) }
user, err := dex.NewUser("u@example.com", "u", "p")
if err != nil { log.Fatal(err) }
c, err := dex.Run(ctx, "dexidp/dex:v2.45.1",
dex.WithClient(app),
dex.WithUser(user),
)
defer testcontainers.TerminateContainer(c)
Index ¶
- Variables
- type Client
- type ClientOption
- type ConnectorType
- type Container
- func (c *Container) AddClient(ctx context.Context, cl Client) error
- func (c *Container) AddUser(ctx context.Context, u User) error
- func (c *Container) AuthEndpoint() string
- func (c *Container) ConfigEndpoint() string
- func (c *Container) GRPCEndpoint(ctx context.Context) (string, error)
- func (c *Container) IssuerURL() string
- func (c *Container) JWKSEndpoint() string
- func (c *Container) RemoveClient(ctx context.Context, id string) error
- func (c *Container) RemoveUser(ctx context.Context, email string) error
- func (c *Container) TokenEndpoint() string
- type Option
- func WithClient(c Client) Option
- func WithConnector(t ConnectorType, id, name string) Option
- func WithDisablePasswordDB() Option
- func WithEnableClientCredentials() Option
- func WithIssuer(url string) Option
- func WithLogLevel(level slog.Level) Option
- func WithLogger(logger *slog.Logger) Option
- func WithSkipApprovalScreen(skip bool) Option
- func WithStorage(s Storage) Option
- func WithUser(u User) Option
- type Storage
- type User
- type UserOption
Examples ¶
Constants ¶
This section is empty.
Variables ¶
var ( // ErrClientExists is returned by AddClient when a client with the given // ID is already registered. ErrClientExists = errors.New("dex: client already exists") // ErrClientNotFound is returned by RemoveClient when no client matches // the given ID. ErrClientNotFound = errors.New("dex: client not found") // ErrUserExists is returned by AddUser when a user with the given email // is already registered. ErrUserExists = errors.New("dex: user already exists") // ErrUserNotFound is returned by RemoveUser when no user matches the // given email. ErrUserNotFound = errors.New("dex: user not found") // ErrNoAuthSource is returned when the rendered Dex config would boot // with no working authentication source — neither the password DB nor // any connector. The password DB is enabled by default; callers must // explicitly disable it via WithDisablePasswordDB to trigger this error. ErrNoAuthSource = errors.New("dex: no auth source configured") )
Functions ¶
This section is empty.
Types ¶
type Client ¶
type Client struct {
// contains filtered or unexported fields
}
Client is a static OAuth2 client registered with Dex at boot time via WithClient. Construct with NewClient so invalid configuration surfaces at call-site rather than at Run.
type ClientOption ¶
ClientOption configures a Client during NewClient.
func WithClientGrantTypes ¶
func WithClientGrantTypes(grants ...string) ClientOption
WithClientGrantTypes appends to the allowed OAuth2 grants. Defaults to ["authorization_code", "refresh_token"] when unset. Accepted values: authorization_code, refresh_token, client_credentials, password.
Only takes effect for clients registered via WithClient (YAML). Clients added at runtime via AddClient inherit Dex's defaults because the gRPC api.Client proto has no grant_types field.
func WithClientName ¶
func WithClientName(n string) ClientOption
WithClientName sets the human-readable display name shown on Dex's consent screen.
func WithClientPublic ¶
func WithClientPublic() ClientOption
WithClientPublic marks the client as public — no secret, intended for PKCE flows from untrusted clients (mobile, SPA).
func WithClientRedirectURIs ¶
func WithClientRedirectURIs(uris ...string) ClientOption
WithClientRedirectURIs appends to the list of allowed redirect URIs. At least one is required for authorization_code clients. Values are appended across calls; blank entries are rejected.
func WithClientSecret ¶
func WithClientSecret(s string) ClientOption
WithClientSecret sets the client secret. Required for confidential clients; omit for public (PKCE) clients via WithClientPublic.
type ConnectorType ¶
type ConnectorType string
ConnectorType selects a Dex connector kind.
const ( // ConnectorPassword enables Dex's built-in static password connector. // Users must be registered via WithUser or AddUser. ConnectorPassword ConnectorType = "password" // ConnectorMock enables Dex's mockCallback connector — a test-only // connector that bypasses the login form and returns a fixed user. ConnectorMock ConnectorType = "mockCallback" )
type Container ¶
type Container struct {
testcontainers.Container
// contains filtered or unexported fields
}
Container is a running Dex OIDC provider.
func Run ¶
func Run(ctx context.Context, img string, opts ...testcontainers.ContainerCustomizer) (*Container, error)
Run starts Dex. The image is required (tc-go convention). Module options (WithClient, WithUser, WithIssuer, ...) and generic tc-go customizers may be mixed in the opts slice.
Example (AuthorizationCode) ¶
// runContainer {
ctx := context.Background()
app, err := dex.NewClient("my-app",
dex.WithClientSecret("secret"),
dex.WithClientRedirectURIs("http://localhost:8080/callback"),
dex.WithClientGrantTypes("authorization_code", "refresh_token"),
dex.WithClientName("My App"),
)
if err != nil {
log.Fatalf("new client: %v", err)
}
user, err := dex.NewUser("u@example.com", "u", "p")
if err != nil {
log.Fatalf("new user: %v", err)
}
c, err := dex.Run(ctx, "dexidp/dex:v2.45.1",
dex.WithClient(app),
dex.WithUser(user),
)
if err != nil {
log.Fatalf("run: %v", err)
}
defer func() { _ = testcontainers.TerminateContainer(c) }()
// }
_ = oauth2.Config{
ClientID: "my-app",
ClientSecret: "secret",
RedirectURL: "http://localhost:8080/callback",
Endpoint: oauth2.Endpoint{AuthURL: c.AuthEndpoint(), TokenURL: c.TokenEndpoint()},
Scopes: []string{"openid", "email"},
}
fmt.Println("has issuer:", c.IssuerURL() != "")
Output: has issuer: true
Example (PasswordGrant) ¶
// Dex's recommended machine-to-machine pattern: ROPC with a dedicated
// service-account user. (client_credentials requires an upstream
// connector — see module README.)
ctx := context.Background()
svc, err := dex.NewClient("svc",
dex.WithClientSecret("s"),
dex.WithClientName("Service"),
dex.WithClientGrantTypes("password"),
)
if err != nil {
log.Fatalf("new client: %v", err)
}
user, err := dex.NewUser("svc@svc.local", "svc", "svc-secret")
if err != nil {
log.Fatalf("new user: %v", err)
}
c, err := dex.Run(ctx, "dexidp/dex:v2.45.1",
dex.WithClient(svc),
dex.WithUser(user),
)
if err != nil {
log.Fatalf("run: %v", err)
}
defer func() { _ = testcontainers.TerminateContainer(c) }()
cfg := oauth2.Config{
ClientID: "svc", ClientSecret: "s",
Endpoint: oauth2.Endpoint{TokenURL: c.TokenEndpoint()},
Scopes: []string{"openid"},
}
tok, err := cfg.PasswordCredentialsToken(ctx, "svc@svc.local", "svc-secret")
if err != nil {
panic(fmt.Errorf("token: %w", err))
}
fmt.Println("has access token:", tok.AccessToken != "")
Output: has access token: true
func (*Container) AddClient ¶
AddClient creates a client via Dex's gRPC admin API.
Note: Dex's api.Client proto has no grant_types field — clients added this way inherit Dex's defaults (authorization_code + refresh_token). For custom grants, register the client pre-start via WithClient.
Not safe for concurrent use.
func (*Container) AddUser ¶
AddUser registers a user in Dex's password DB via gRPC.
Not safe for concurrent use.
func (*Container) AuthEndpoint ¶
AuthEndpoint returns the OAuth2 authorization URL.
func (*Container) ConfigEndpoint ¶
ConfigEndpoint returns the OIDC discovery document URL.
func (*Container) GRPCEndpoint ¶
GRPCEndpoint returns host:mappedPort for Dex's gRPC admin API. Errors propagate from the Docker API, or report that the container has not been started.
func (*Container) JWKSEndpoint ¶
JWKSEndpoint returns the JSON Web Key Set URL.
func (*Container) TokenEndpoint ¶
TokenEndpoint returns the OAuth2 token URL.
type Option ¶
type Option func(*options) error
Option is a functional option for the Dex module. Options return an error so user-supplied values can be validated at Run time rather than failing silently in the rendered YAML.
NOTE: Options must be passed directly to dex.Run. They satisfy the testcontainers.ContainerCustomizer interface only so the Run signature can accept them alongside generic tc-go customizers (e.g. network.With*) — Option.Customize is a no-op, so an Option forwarded through any wrapper that dispatches via Customize (instead of type-asserting to Option) is silently dropped.
func WithClient ¶
WithClient registers a static client in Dex's YAML config. Unlike gRPC-added clients, these may declare custom grant types.
func WithConnector ¶
func WithConnector(t ConnectorType, id, name string) Option
WithConnector enables a Dex connector by type. For ConnectorPassword this is a no-op — the password DB is enabled by default and the template handles it separately; id and name are ignored and blank-field validation is skipped in that case. For other connectors (e.g. ConnectorMock) the entry is added to the rendered YAML, and blank id or name returns an error.
func WithDisablePasswordDB ¶
func WithDisablePasswordDB() Option
WithDisablePasswordDB disables Dex's built-in password connector. The caller must then configure at least one other connector via WithConnector, otherwise Run returns ErrNoAuthSource.
func WithEnableClientCredentials ¶
func WithEnableClientCredentials() Option
WithEnableClientCredentials enables Dex's OAuth2 client_credentials grant via the DEX_CLIENT_CREDENTIAL_GRANT_ENABLED_BY_DEFAULT=true environment variable.
Requires Dex ≥ v2.46.0 or the dexidp/dex:master image tag. Earlier releases silently ignore the flag and token exchanges fail with unsupported_grant_type. This module does not validate the image tag — the caller must pin a compatible image.
func WithIssuer ¶
WithIssuer overrides the default host:mappedPort-derived issuer. When set, Run uses the fast-path (direct YAML bind-mount). Callers are responsible for ensuring the URL is reachable from every client (tests and sibling containers).
func WithLogLevel ¶
WithLogLevel sets Dex's own --log-level flag. Accepts a standard library slog.Level; values are mapped to Dex's level vocabulary (debug, info, warn, error). Default: slog.LevelInfo.
func WithLogger ¶
WithLogger routes Dex container logs through the supplied slog.Logger. When unset, Dex container logs are discarded. Calling WithLogger(nil) is a no-op; to discard logs again after setting a logger, drop the option rather than passing nil.
func WithSkipApprovalScreen ¶
WithSkipApprovalScreen toggles Dex's oauth2.skipApprovalScreen. Default: true.
func WithStorage ¶
WithStorage sets Dex's storage backend. Default: StorageSQLite.
func WithUser ¶
WithUser registers a static password entry. The password DB connector is enabled by default, so no extra option is needed to consume the entry.
func (Option) Customize ¶
func (o Option) Customize(*testcontainers.GenericContainerRequest) error
Customize is a no-op; real state mutation happens inside Run. See the Option type-level doc for why this is a no-op.
type Storage ¶
type Storage string
Storage selects Dex's storage backend. Defaults to StorageSQLite.
const ( // StorageSQLite uses an on-disk SQLite database inside the container. // Ephemeral — destroyed when the container is removed. StorageSQLite Storage = "sqlite3" // StorageMemory keeps all state in process memory. Fastest; unsuitable // when multiple Dex replicas need to share state. StorageMemory Storage = "memory" )
type User ¶
type User struct {
// contains filtered or unexported fields
}
User is a static password entry in Dex's password connector. Construct with NewUser.
type UserOption ¶
UserOption configures a User during NewUser.
func WithUserID ¶
func WithUserID(id string) UserOption
WithUserID pins the stable subject claim. When unset, NewUser leaves userID blank and a UUIDv4 is generated at YAML render time.