# How to Set Up OIDC Authentication in Listmonk with External Identity Providers

> Learn to set up OIDC authentication in Listmonk with external identity providers like Keycloak or Google. Integrate SSO seamlessly and enhance your authentication flow.

- Repository: [Kailash Nadh/listmonk](https://github.com/knadh/listmonk)
- Tags: how-to-guide
- Published: 2026-05-19

---

**Listmonk supports OpenID Connect (OIDC) authentication through its built-in `auth` package, enabling Single Sign-On (SSO) via standards-compliant identity providers like Keycloak, Authentik, or Google Workspace by configuring the `OIDC` struct in [`models/settings.go`](https://github.com/knadh/listmonk/blob/main/models/settings.go) and exposing the `/auth/oidc` endpoints.**

Listmonk provides native OIDC support that integrates external identity providers directly into the authentication flow. This implementation allows administrators to delegate user verification to corporate IdPs while automatically provisioning accounts with specific role assignments. Setting up Listmonk OIDC authentication requires configuring provider endpoints, credentials, and optional auto-user creation settings in the application's configuration layer.

## OIDC Configuration Structure

The **OIDC settings** are defined in [`models/settings.go`](https://github.com/knadh/listmonk/blob/main/models/settings.go) (lines 55-64) as part of the global Settings model. This structure stores all parameters required to negotiate with external identity providers.

The configuration includes the provider URL, client credentials, redirect URL, and flags for automatic user provisioning:

- `ProviderURL`: The OIDC issuer URL (e.g., `https://keycloak.example.com/realms/<realm>`)
- `ClientID` and `ClientSecret`: Credentials obtained from your IdP
- `RedirectURL`: The callback endpoint (typically `https://<your-host>/auth/oidc`)
- `AutoCreateUsers`: Boolean flag to provision new accounts automatically
- `DefaultUserRoleID` and `DefaultListRoleID`: Role assignments for auto-created users

## Initialization Flow

When Listmonk starts with OIDC enabled (`cfg.OIDC.Enabled == true`), the `auth.New` function calls `initOIDC` ([`internal/auth/auth.go`](https://github.com/knadh/listmonk/blob/main/internal/auth/auth.go) lines 48-73). This initialization performs three critical operations:

1. Creates an **OpenID Connect provider** using `oidc.NewProvider` to connect to the discovery endpoint
2. Builds an **OAuth2 configuration** with scopes `openid`, `profile`, and `email`
3. Prepares an **ID token verifier** to validate incoming authentication tokens

This setup establishes the secure handshake between Listmonk and your external identity provider before any user attempts to log in.

## Authentication Flow

The OIDC authentication process uses two HTTP endpoints registered in [`cmd/handlers.go`](https://github.com/knadh/listmonk/blob/main/cmd/handlers.go) (lines 254-256). These handlers orchestrate the redirect to the IdP and the subsequent callback processing.

### Initiating Login

The `/auth/oidc` endpoint triggers the `OIDCLogin` handler (defined in [`cmd/auth.go`](https://github.com/knadh/listmonk/blob/main/cmd/auth.go)). This handler generates a cryptographically secure **state and nonce** payload, stores it in a browser cookie for CSRF protection, and redirects the user to the provider's authorization URL via `Auth.GetOIDCAuthURL`.

```go
func (a *App) OIDCLogin(c echo.Context) error {
    nonce, _ := uuid.NewRandom()
    state := OIDCState{
        RedirectURL: c.QueryParam("redirect"),
        Nonce:       nonce.String(),
    }
    b, _ := json.Marshal(state)
    // Store nonce in a cookie, then redirect:
    return c.Redirect(http.StatusFound,
        a.auth.GetOIDCAuthURL(base64.URLEncoding.EncodeToString(b), nonce.Value()))
}

```

The user authenticates with the external provider, which then redirects back to the callback URL with an authorization `code`.

### Handling the Callback

The callback request hits `OIDCFinish` (also in [`cmd/auth.go`](https://github.com/knadh/listmonk/blob/main/cmd/auth.go)), which invokes `auth.ExchangeOIDCToken` to exchange the authorization code for an access token. This function verifies the ID token against the configured provider and extracts user claims (email, name, sub).

```go
func (a *App) OIDCFinish(c echo.Context) error {
    // Retrieve and validate the stored state/nonce
    oidcToken, claims, err := a.auth.ExchangeOIDCToken(c.QueryParam("code"), nonce.Value)
    if err != nil { 
        return err 
    }
    // Find or create the user
    user, err := a.getOrCreateUser(claims)
    // Save session for subsequent requests
    return a.auth.SaveSession(user, oidcToken, c)
}

```

If the email from the claims matches an existing Listmonk account, the user is logged in immediately. If no account exists and `AutoCreateUsers` is enabled, `createOIDCUser` creates a new database user and assigns the configured default roles.

## Session Persistence

After successful token exchange, `auth.SaveSession` persists the user ID and OIDC token in a **server-side session cookie**. This session is then validated by `Auth.Middleware` on subsequent requests, maintaining the authenticated state without repeated IdP redirects.

## Configuration Examples

You can configure OIDC via the Listmonk UI at **Settings → Security → OIDC** or by editing the configuration file directly.

### config.toml Setup

Add the following section to your [`config.toml`](https://github.com/knadh/listmonk/blob/main/config.toml) file:

```toml
[security.oidc]
enabled = true
provider_url = "https://keycloak.example.com/realms/myrealm"
client_id = "listmonk"
client_secret = "YOUR_CLIENT_SECRET"
redirect_url = "https://listmonk.example.com/auth/oidc"
auto_create_users = true
default_user_role_id = 2   # e.g., "Editor" role ID

default_list_role_id = 1   # e.g., "Subscriber" role ID

```

### Programmatic Configuration

If embedding Listmonk as a library, configure OIDC programmatically before initializing the auth provider:

```go
cfg := auth.Config{
    OIDC: auth.OIDCConfig{
        Enabled:           true,
        ProviderURL:       "https://keycloak.example.com/realms/myrealm",
        RedirectURL:       "https://listmonk.example.com/auth/oidc",
        ClientID:          "listmonk",
        ClientSecret:      "YOUR_CLIENT_SECRET",
        AutoCreateUsers:   true,
        DefaultUserRoleID: 2,
        DefaultListRoleID: 1,
    },
}
a, err := auth.New(cfg, db, callbacks, logger)

```

## Summary

- **OIDC configuration** resides in [`models/settings.go`](https://github.com/knadh/listmonk/blob/main/models/settings.go) and supports all standard OIDC parameters including provider URLs, credentials, and auto-provisioning flags.
- **Initialization** occurs in [`internal/auth/auth.go`](https://github.com/knadh/listmonk/blob/main/internal/auth/auth.go) via `initOIDC`, which sets up the provider connection and token verifier.
- **Login flow** uses `/auth/oidc` endpoints defined in [`cmd/handlers.go`](https://github.com/knadh/listmonk/blob/main/cmd/handlers.go) and implemented in [`cmd/auth.go`](https://github.com/knadh/listmonk/blob/main/cmd/auth.go), handling state management and CSRF protection via nonce cookies.
- **Token exchange** happens through `ExchangeOIDCToken`, which validates ID tokens and extracts user claims before `SaveSession` creates the persistent browser session.
- **User provisioning** automatically creates Listmonk accounts for authenticated OIDC users when `AutoCreateUsers` is enabled, applying the specified default roles.

## Frequently Asked Questions

### What identity providers work with Listmonk OIDC?

Any standards-compliant **OpenID Connect provider** works with Listmonk, including Keycloak, Authentik, Google Workspace, Azure AD, and Okta. The implementation follows the OIDC discovery protocol (`/.well-known/openid-configuration`) to automatically retrieve provider metadata, requiring only the base provider URL in the configuration.

### How does Listmonk handle user account creation for OIDC logins?

When `AutoCreateUsers` is enabled in the OIDC configuration, Listmonk automatically creates a new user account if the email address from the OIDC claims does not match an existing account. The new user receives the roles specified in `DefaultUserRoleID` and `DefaultListRoleID`. If auto-creation is disabled, authentication fails for unknown users.

### Where is the OIDC redirect URL configured in Listmonk?

The **redirect URL** is configured in the OIDC settings as `RedirectURL` (or `redirect_url` in TOML), which must match the callback URL registered with your identity provider. By default, Listmonk expects this endpoint at `/auth/oidc` on your host (e.g., `https://listmonk.example.com/auth/oidc`). This value is displayed in the UI at **Settings → Security → OIDC** for easy copying into IdP configurations.

### What happens if OIDC token verification fails?

If `ExchangeOIDCToken` cannot verify the ID token (due to signature mismatch, expiration, or issuer mismatch), the authentication flow returns an error and the user is not logged in. The system logs the specific verification failure, and the user sees an authentication error page. The session cookie is only set after successful token validation and user retrieval or creation.