Skip to content

Dex

Since v0.43.0

Introduction

The Testcontainers module for Dex, a CNCF-sandbox OIDC provider. The module lets Go tests spin up a real Dex instance and exercise OAuth2/OIDC flows end-to-end instead of mocking the token endpoint.

Adding this module to your project dependencies

Please run the following command to add the Dex module to your Go dependencies:

go get github.com/testcontainers/testcontainers-go/modules/dex

Usage example

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("[email protected]", "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) }()

Module Reference

Run function

The Dex module exposes one entrypoint function to create the Dex container, and this function receives three parameters:

func Run(ctx context.Context, img string, opts ...testcontainers.ContainerCustomizer) (*Container, error)
  • context.Context, the Go context.
  • string, the Docker image to use.
  • testcontainers.ContainerCustomizer, a variadic argument for passing options.

Image

Use the second argument in the Run function to set a valid Docker image. In example: Run(context.Background(), "dexidp/dex:v2.45.1").

Warning

The client_credentials grant requires Dex ≥ v2.46.0 or the dexidp/dex:master image. On v2.45.x and earlier the token endpoint returns unsupported_grant_type even with WithEnableClientCredentials() set. The module does not validate the image tag — pin a compatible image when using that grant.

Container Options

When starting the Dex container, you can pass options in a variadic way to configure it.

Clients

Client is an opaque value type. Construct it with NewClient(id, opts...) so invalid values surface at call-site. NewClient returns (Client, error); client options accept variadic URIs and grant types.

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"),
)
// ...
c, err := dex.Run(ctx, "dexidp/dex:v2.45.1", dex.WithClient(app))

Additional client options: WithClientPublic() marks the client as public (PKCE, no secret).

Clients added at runtime via the child adapter's AddClient inherit Dex's defaults (authorization_code + refresh_token) because Dex's gRPC api.Client proto has no grant_types field. Clients needing other grants must be declared pre-start via WithClient.

Users

User is an opaque value type. NewUser(email, username, password, opts...) returns (User, error). Use WithUserID(id) to pin the stable sub claim; otherwise a UUIDv4 is generated at YAML render time.

user, err := dex.NewUser("[email protected]", "user", "password",
    dex.WithUserID("stable-sub-123"),
)
// ...
c, err := dex.Run(ctx, "dexidp/dex:v2.45.1", dex.WithUser(user))

Adding WithUser keeps Dex's built-in password DB connector enabled. Use WithDisablePasswordDB() to turn it off when the configuration only uses other connectors.

Connectors

WithConnector(type, id, name) enables a Dex connector. Supported types:

  • ConnectorPassword — Dex's built-in static password DB. Enabled by default; no-op when passed explicitly.
  • ConnectorMock — Dex's mockCallback test connector. Returns a fixed user ([email protected]) and bypasses the login form.

Blank id or name values are rejected at Run time.

Issuer

By default the issuer is derived from the host and mapped HTTP port: http://<host>:<mappedPort>/dex. WithIssuer(url) overrides the default. Use it when the issuer must be reachable from other containers (shared Docker network with a network alias); the caller owns reachability.

Storage

WithStorage(Storage) selects Dex's storage backend. Available constants:

  • StorageSQLite — on-disk SQLite database (default). Ephemeral; destroyed with the container.
  • StorageMemory — in-process. Fastest; not shared across replicas.

Logging

WithLogger(*slog.Logger) routes Dex container logs through a slog.Logger. Unset by default — container logs are discarded. Stderr lines are promoted to at least slog.LevelWarn because Dex writes runtime errors there.

WithLogLevel(slog.Level) sets Dex's own logger.level YAML key. Values between slog's fixed levels round down. Default: slog.LevelInfo.

Client credentials grant

WithEnableClientCredentials() sets the env var DEX_CLIENT_CREDENTIAL_GRANT_ENABLED_BY_DEFAULT=true, which Dex ≥ v2.46.0 reads to enable the OAuth2 client_credentials grant. Pair with a compatible image (see the Image warning above).

Approval screen

WithSkipApprovalScreen(bool) toggles Dex's oauth2.skipApprovalScreen. Default: true (tests rarely want a human-in-the-loop prompt).

The following options are exposed by the testcontainers package.

Basic Options

Lifecycle Options

Files & Mounts Options

Build Options

Logging Options

Image Options

Networking Options

Advanced Options

Experimental Options

Container Methods

The Dex container exposes the following methods:

IssuerURL

Returns Dex's issuer URL. Empty if Run has not started.

ConfigEndpoint

Returns the OIDC discovery document URL (/.well-known/openid-configuration).

JWKSEndpoint

Returns the JSON Web Key Set URL (/keys).

TokenEndpoint

Returns the OAuth2 token URL (/token).

AuthEndpoint

Returns the OAuth2 authorization URL (/auth).

GRPCEndpoint

Returns host:mappedPort for Dex's gRPC admin API. Takes a context.Context and returns an error if the container is not started or the Docker API call fails.

target, err := c.GRPCEndpoint(ctx)

Breaking change: admin adapter moved

Combined CockroachDB and Dex SDK extraction

AddClient, RemoveClient, AddUser, and RemoveUser have been removed from *dex.Container in github.com/testcontainers/testcontainers-go/modules/dex. They now belong to *dexexamples.AdminClient in the importable child package github.com/testcontainers/testcontainers-go/modules/dex/examples. This change ships together with the CockroachDB pgx adapter move.

import dexexamples "github.com/testcontainers/testcontainers-go/modules/dex/examples"

admin := dexexamples.NewAdminClient(c)
// Before: err := c.AddUser(ctx, user)
err := admin.AddUser(ctx, user)
// Likewise: admin.AddClient(ctx, client), admin.RemoveClient(ctx, id),
// and admin.RemoveUser(ctx, email).

Update method expressions, interfaces and stored function references that previously required those methods on *dex.Container. The adapter accepts the same parent dex.Client and dex.User arguments. It preserves per-operation plaintext gRPC connection/close behavior, context handling, error wrapping, and errors.Is identity for dex.ErrClientExists, dex.ErrClientNotFound, dex.ErrUserExists, and dex.ErrUserNotFound. The constructor opens no connection; no adapter-wide Close is required. Operations remain unsafe for concurrent use. Callers own the parent container's lifetime and access to its admin endpoint.

The parent retains Run, static WithClient/WithUser, constructors, validation, endpoints, options, YAML/bcrypt policy and logging without Dex protobuf, gRPC, OAuth2 or OIDC client dependencies. The new SDK-free Client.Registration() returns copied ID/secret/name/redirect URIs/public fields; treat its secret as credentials. User.PasswordRegistration() returns email/username/user ID and a freshly generated bcrypt hash at cost 10. Each call generates a UUIDv4 unless WithUserID pins the ID. Returned slices are independent of the original model. Runtime clients still inherit Dex's default grants: the admin protobuf has no grant_types; use static WithClient for non-default grants.

The adapter and OAuth/OIDC examples are optional support in a separate child module. For a local checkout, replace both modules in your consumer (substitute your checkout path), then add the adapter:

go mod edit -replace=github.com/testcontainers/testcontainers-go/modules/dex=/path/to/testcontainers-go/modules/dex
go mod edit -replace=github.com/testcontainers/testcontainers-go/modules/dex/examples=/path/to/testcontainers-go/modules/dex/examples
go get github.com/testcontainers/testcontainers-go/modules/dex/examples

Run the full RPC and OAuth/OIDC matrix and both runnable client examples from modules/dex/examples. Run the parent configuration, endpoint and log suite from modules/dex; parent go test ./... does not traverse the nested module. The child preserves Dex API/v2 v2.4.0, gRPC v1.83.2, go-oidc/v3 v3.18.0, and oauth2 v0.36.0. The floating-master feature-flag test retains its existing DEX_TEST_MASTER=1 opt-in.

ID token claims

Dex's password connector emits these standard claims in ID tokens:

  • sub — stable user ID (auto-generated UUIDv4 when NewUser is called without WithUserID).
  • email — user's email address.
  • email_verified — always true for static password entries.
  • name — the username.
  • iss — the issuer URL.
  • aud — the client ID.

Dex does NOT emit the preferred_username claim; use the name claim instead when a human-readable identifier is needed.

Known limitations

  • Runtime-added clients inherit Dex's default grants.
  • client_credentials requires WithEnableClientCredentials() and Dex ≥ v2.46.0 or the :master image tag.
  • gRPC admin API is plaintext. TLS not supported day 1.
  • mockCallback emits a fixed user; parameterized flows need the password connector with a pre-seeded user.
  • SQLite storage is container-local and ephemeral.
  • Bcrypt cost ≥ 10 is enforced by Dex for both YAML and gRPC password paths.