How to set up OpenID Connect for Tuwunel with Pocket ID

Introduction

Authentication has always been a central component of Matrix server deployments, yet for a long time it was also one of its weaker architectural aspects. Traditionally, Matrix relied on client-driven authentication flows, where each client was responsible for handling login logic, often resulting in fragmented implementations, inconsistent user experiences, and limited flexibility when integrating with modern identity systems. This approach also posed challenges from a security perspective, as it made it harder to enforce centralized policies, adopt advanced authentication mechanisms, or integrate with enterprise-grade identity providers.

To address these shortcomings, the Matrix ecosystem began evolving towards a more robust, standardized authentication model based on OpenID Connect (OIDC). A key milestone in this transition was the introduction of the Matrix Authentication Service (MAS) within Synapse, accompanied by several Matrix Spec Change proposals (MSCs) that laid the groundwork for a server-centric authentication architecture. This shift decouples authentication from individual clients and instead places it at the server level, allowing for consistent, secure, and extensible identity management across all clients and platforms.

From a security standpoint, this transition is highly significant. By leveraging OIDC, server operators can integrate with well-established identity providers, enforce stronger authentication policies such as multi-factor authentication, and benefit from standardized, battle-tested protocols. It also enables clearer separation of concerns: the Matrix homeserver focuses on messaging, while authentication is delegated to specialized identity systems designed for that purpose.

Tuwunel, the successor to conduwuit, has rapidly embraced this evolution. With version 1.5, it introduced support for Single Sign-On (SSO) and OIDC via external identity providers, effectively implementing what is often referred to in Matrix terminology as “legacy SSO.” However, with the release of version 1.6 on April 12, 2026, Tuwunel took a significant step further. It now includes a built-in OIDC server, building upon and extending the existing SSO infrastructure. This development enables Tuwunel to act not only as a client of external identity providers, but also as a fully-fledged authentication authority for Matrix clients, aligning closely with the direction established by MAS. The underlying implementation and its evolution can be explored in the corresponding pull request, while the release notes provide additional context on the changes and new capabilities.

The purpose of this guide is to walk through the complete process of integrating an OpenID Connect identity provider with a Tuwunel homeserver. Using Pocket ID as a concrete example, it demonstrates how to set up and configure an external identity provider, how to connect it to Tuwunel, and how to ensure a fully functional authentication flow for both web and mobile clients. Beyond the basic setup, this guide also highlights practical considerations such as handling existing users already stored in the Tuwunel database, as well as recommended workflows for onboarding new accounts in a clean and maintainable way.

By the end of this guide, you will have a working OIDC-based authentication setup that is aligned with the modern Matrix authentication model, improves security, and provides a consistent login experience across all clients.

Pocket ID Setup

Before integrating an OpenID Connect provider with Tuwunel, we first need to choose and deploy an identity provider. In this guide, Pocket ID is used as the reference implementation. The choice is intentional: Pocket ID aligns very well with the overall philosophy of the Tuwunel project. It is written in Go, lightweight, and designed with minimal overhead in mind. This makes it particularly attractive for self-hosted environments where efficiency and simplicity matter.

Compared to other popular identity providers, Pocket ID takes a very different approach. Solutions such as Authelia or Authentik are powerful, but they come with trade-offs. Authelia, for example, typically requires an additional user directory such as LLDAP or another backend to manage identities. While this setup is certainly manageable, it introduces additional complexity and moving parts. Authentik, on the other hand, is a full-fledged enterprise identity platform. It offers a vast feature set, but for a lightweight Matrix homeserver deployment, it can easily feel like overkill — both in terms of resource usage and operational complexity.

Pocket ID deliberately avoids this complexity. It is distributed as a single container and does not require an external database or directory service. This makes deployment straightforward and reduces the maintenance burden significantly.

One of the most distinctive features of Pocket ID is its authentication model. Unlike traditional identity providers, it does not rely on passwords. Instead, it is built around passkeys. At first glance, this may seem unusual, but it is arguably one of its strongest advantages. Most users already carry devices capable of secure authentication—smartphones, hardware keys such as YubiKeys, or password managers like Bitwarden or Vaultwarden that support passkeys. Leveraging biometric authentication (fingerprint or face recognition) or hardware-backed credentials results in a more secure and user-friendly experience than traditional passwords.

For convenience, Pocket ID also supports login via magic links sent by email. While this is generally less secure than passkeys, it provides a useful fallback mechanism, especially during onboarding.

Deployment via Docker

Pocket ID can be deployed using Docker with a very minimal configuration. A typical compose.yaml might look like this:

services:
  pocket-id:
    image: ghcr.io/pocket-id/pocket-id:latest
    container_name: pocket-id
    restart: unless-stopped
    user: "1026:100"
    ports:
      - "6168:1411"
    env_file: .env
    volumes:
      - './data:/app/data'
    networks:
      - pocket-id

networks:
  pocket-id:
    external: true

Most of the relevant configuration is handled through a .env file, which keeps the compose file clean and allows for easier adjustments. Official examples and documentation can be found here.

A minimal .env file could look like this:

# See the documentation for more information: https://pocket-id.org/docs/configuration/environment-variables

APP_URL=https://auth.domain.ltd

# Encryption key (choose one method):
# Generate with: openssl rand -base64 32
ENCRYPTION_KEY=

# Optional but recommended:
TRUST_PROXY=true
MAXMIND_LICENSE_KEY=

The most important parameter here is APP_URL. This defines the public URL under which Pocket ID will be reachable. In this guide, we assume a dedicated subdomain such as:

https://auth.domain.ltd

This domain will later serve as the issuer for OpenID Connect and must therefore be reachable from both clients and your Tuwunel instance.

Another important point is to set TRUST_PROXY to true. Reason beeing that in most deployments the container will sit behind a reverse proxy. When TRUST_PROXY is set to true, it calls gin.Engine.SetTrustedProxies(nil) to clear the list of trusted proxy CIDRs. This effectively tells Gin to trust all proxies and extract the client IP from headers like X-Forwarded-For or X-Real-IP. You can find more information about the varous environmental values by checking the documentation here.

To use Pocket ID effectively, you will also need a passkey-capable device or software. This can be:

  • a smartphone with a built-in keychain (e.g. iPhone or Android),
  • a hardware security key such as a YubiKey,
  • or a password manager that supports passkeys.

As an optional but highly recommended enhancement, you can obtain an API key from MaxMind for free. This allows Pocket ID to enrich login events with geolocation data. For administrators, this is extremely useful, as it provides visibility into where login attempts originate from and can help detect suspicious activity.

Important: Persistent Storage

One aspect that must not be overlooked is persistent storage. The container mounts its data directory via:

volumes: - ./data:/app/data

This is critical. Pocket ID stores its database and identity information inside this directory. If the volume is not properly mounted, all user identities will be lost when the container is recreated or updated. In other words, without persistent storage, your identity provider will effectively reset on every redeploy.

Ensuring that this volume is correctly configured and backed up should therefore be considered mandatory for any production setup.

Pocket ID Initial Setup and Configuration

For the purpose of this guide, we assume that the necessary infrastructure has already been put in place. This includes a working subdomain (e.g. auth.domain.ltd) and a properly configured reverse proxy that forwards traffic to the Pocket ID container. As these steps are highly environment-specific, they are not covered in detail here.

Once your Pocket ID instance is reachable, the initial setup is triggered via:

https://auth.domain.ltd/setup

During this first step, you will create your initial administrative user. At this point, it is strongly recommended to choose a username that matches an existing user on your Tuwunel server. This greatly simplifies user migration later on.

For example, if a user on your Tuwunel server has the Matrix ID:

@user:domain.ltd

then the corresponding Pocket ID username should be:

user

Case sensitivity is not particularly critical, as it can be normalized later via Tuwunel’s user mapping configuration. However, maintaining a consistent naming scheme ensures that existing users can be matched seamlessly without additional migration steps.

If you prefer to separate administrative access from your regular Matrix account, this requirement becomes less strict. In that case, you can create additional users later through the Pocket ID admin interface and assign them appropriate roles.

After completing the initial setup, you will be prompted to register a passkey for your administrative account. It is highly recommended to register at least two passkeys, ideally on different devices, to ensure you have a fallback in case one becomes unavailable.

Application Configuration

Once logged in, navigate to the application configuration section within the Pocket ID interface. This is where you define the core behavior of your identity provider.

A few settings are particularly worth reviewing:

It is advisable to restrict user registration and enable token-based registration only. This prevents uncontrolled account creation and gives you full control over onboarding. At the same time, you should define a default user group. Later, when integrating applications such as Tuwunel, you can specify which groups are allowed to access a given service.

You should also configure SMTP settings for email delivery. These are used for various features, including the magic link login fallback. While passkeys should remain the primary authentication method, the magic link option can be useful in certain scenarios, especially during onboarding or when users are not yet familiar with passkey-based authentication. Within the email settings, you can explicitly enable or disable this fallback depending on your security requirements.

Most of the remaining options in the Pocket ID interface are self-explanatory and can be adjusted to your preferences. Rather than covering every available setting, the focus of this guide will shift to the more relevant aspects for integrating Pocket ID with Tuwunel.

Creating the OIDC Application in Pocket ID

With Pocket ID up and running, the next step is to create an OpenID Connect application that will later be used by Tuwunel. This application defines how Tuwunel communicates with Pocket ID and is the central link between your Matrix homeserver and your identity provider.

To begin, navigate to the Applications section in the Pocket ID admin interface and create a new application.

During this process, you will be asked to define several parameters that are essential for the OIDC flow.

Application Basics

First, choose a meaningful name for your application. In this guide, we will simply use something like:

Tuwunel

This name is only used for display purposes within Pocket ID and does not affect the technical configuration.

Redirect URI (Callback URL)

The most important parameter is the Redirect URI, sometimes also referred to as the callback URL. This is the endpoint on your Tuwunel server to which Pocket ID will redirect users after successful authentication.

For Tuwunel, this must follow a very specific format:

https://domain.ltd/_matrix/client/unstable/login/sso/callback/<client_id>

In our example, this becomes:

https://domain.ltd/_matrix/client/unstable/login/sso/callback/tuwunel

Note that we need to define the client-id under extended options. Otherwise a random string will be generated and must therefore be use in the above example. 

Other than that, there are a few important details to get right here:

  • The path must be exactly /_matrix/client/unstable/login/sso/callback/
  • The last segment must match the client_id you will configure
  • The Client-Start-URL must be publicly reachable and use HTTPS. Its should be your element web instance.
  • No trailing slashes or typos — even small deviations will break the flow

Client ID and Client Secret

As already explained, we need to define the Client ID. This is an arbitrary identifier for your application, but it must match exactly between Pocket ID and Tuwunel.

In this guide, we use:

tuwunel

We will not only need the client ID for the Callback URL but also for our tuwunel.toml later on. 

Once the application is created, Pocket ID will generate a Client Secret. This is a sensitive credential that Tuwunel will use to authenticate itself against the identity provider.

Like the client ID, the client secret will be required later in your tuwunel.toml.

Access Control

Pocket ID allows you to restrict which users or groups can access a given application. This is a powerful feature that can be used to control who is allowed to log in to your Matrix server.

At a minimum, you should assign the default user group you configured earlier. This ensures that newly registered users can authenticate without additional manual steps.

More advanced setups might define separate groups for different services, but for now a single default group is sufficient.

Tuwunel Configuration

With Pocket ID fully configured, the final step is to connect it to your Tuwunel homeserver. This is done through the tuwunel.toml configuration file.

At a high level, Tuwunel plays a dual role in this setup. On one hand, it acts as an OpenID Connect client towards Pocket ID, delegating authentication to the external identity provider. On the other hand, starting with version 1.6, Tuwunel also exposes its own OIDC-compatible interface to Matrix clients such as Element Web and Element X. This effectively places Tuwunel in the middle of the authentication flow, bridging modern client expectations with your chosen identity backend.

Configuring the Identity Provider

The connection to Pocket ID is defined via the [[global.identity_provider]] section. This is the most critical part of the configuration, as it tells Tuwunel how to communicate with your OIDC provider.

A minimal working example looks like this:

[[global.identity_provider]]
type = "oidc"
brand = "Pocket-ID"
name = "Pocket ID"
client_id = "tuwunel"
client_secret = "REPLACE_WITH_YOUR_CLIENT_SECRET"
issuer_url = "https://auth.domain.ltd"
callback_url = "https://domain.ltd/_matrix/client/unstable/login/sso/callback/tuwunel"
scope = ["openid", "profile", "email"]
default = true
discovery = true
trusted = true
registration = true

Each of these parameters serves a specific purpose.

The client_id and client_secret must exactly match the values generated in Pocket ID. The issuer_url points to your Pocket ID instance and must correspond to its public URL. The callback_url must be identical to the redirect URI configured earlier in Pocket ID. Any mismatch here will result in failed login attempts.

Scopes define which user information is requested during authentication. In most cases, openid, profile, and email are sufficient.

Setting default = true ensures that this provider is used as the primary login method. The discovery flag enables automatic retrieval of OIDC metadata from the provider, simplifying configuration. Marking the provider as trusted allows Tuwunel to accept the identity information without additional verification steps, and registration = true enables automatic account creation for users who do not yet exist on the homeserver.

User Mapping

To ensure that users from Pocket ID are mapped correctly to Matrix user IDs, Tuwunel provides a flexible user mapping mechanism.

In our setup, we use the following configuration:

[oidc.user_mapping]
localpart_template = "{{ user.preferred_username | lower }}"
displayname_template = "{{ user.preferred_username | capitalize }}"
confirm_localpart_identity = true

This configuration ensures that the preferred_username claim from Pocket ID is used as the Matrix localpart. In practice, this means that a Pocket ID user named user will map to the Matrix ID:

@user:domain.ltd

The confirm_localpart_identity setting adds an additional safeguard by ensuring that existing users are only matched if the identity is consistent. This is particularly important when migrating from a pre-existing Tuwunel user database.

Enabling the OIDC Server

With version 1.6, Tuwunel introduces a built-in OIDC server that is used by modern clients such as Element X. In principle, this functionality is expected to be activated automatically once an identity provider is configured.

However, during real-world testing, it turned out that the following configuration block was required to make the setup work reliably:

[oidc]
enabled = true

At the time of writing, this parameter does not appear in the official documentation or example configuration, which makes its role somewhat unclear. It may be an internal or transitional flag related to the new OIDC implementation introduced in version 1.6.

In this particular setup, enabling it ensured that the OIDC endpoints were properly exposed and that clients such as Element X could complete the authentication flow successfully.

That said, it is important to note that this behavior may change in future versions of Tuwunel. Depending on the version and configuration, the OIDC server might work without this parameter. If you prefer to stay closer to the documented configuration, you can try omitting it and verify whether authentication still works in your environment.

For now, if you encounter issues with OIDC discovery or login flows — especially with mobile clients — it is worth explicitly enabling this option.

Well-Known Configuration

In order for Matrix clients to discover your homeserver and its authentication capabilities, the .well-known configuration must be set correctly. In Tuwunel, this is done directly within the configuration file:

[global.well_known]
client = "https://domain.ltd"
server = "domain.ltd:443"

[[global.well_known.rtc_transports]]
type = "livekit"
livekit_service_url = "https://matrixRTC.domain.ltd"

At first glance, this may seem familiar. Traditionally, Matrix deployments relied on serving static .well-known files directly from a web server such as nginx. These files would contain JSON responses for endpoints like /.well-known/matrix/client and /.well-known/matrix/server, allowing clients to locate the homeserver and establish a connection.

However, with the introduction of OIDC and the new authentication model in Tuwunel 1.6, this approach is no longer sufficient.

Modern Matrix clients do not only rely on the classic Matrix well-known endpoints, but also expect additional discovery endpoints such as:

/.well-known/openid-configuration

These endpoints are part of the OIDC ecosystem and must be dynamically generated by the homeserver itself. As a result, simply placing static files on your web server is no longer enough. Instead, Tuwunel must actively serve these endpoints based on its internal configuration.

By defining the global.well_known section, you instruct Tuwunel to generate and expose the required discovery information for both Matrix and OIDC clients. This ensures that clients like Element Web and Element X can automatically discover the correct authentication flow without any manual configuration.

In addition to the basic client and server discovery, it is also important to configure Matrix RTC (Real-Time Communication), which is used by features such as Element Call. If this configuration is missing, Element Call will not function correctly, even if the rest of your setup is working as expected.

This can be configured bt adding below the [global.well_known] parameters the following:

[[global.well_known.rtc_transports]]
type = "livekit"
livekit_service_url = "https://matrixRTC.domain.ltd"

This ensures that clients are aware of the LiveKit service endpoint and can properly establish real-time communication sessions.

This change has an important implication for your overall setup. Since Tuwunel is now responsible for serving these endpoints, your web server must be configured to forward all relevant .well-known requests to Tuwunel rather than handling them locally.

This becomes particularly relevant when using a reverse proxy such as nginx. While it was previously common to serve .well-known files directly from the web server, you now need to ensure that requests to paths like:

/.well-known/matrix/*
/.well-known/openid-configuration

are routed to the Tuwunel backend.

In the following section, we will take a closer look at how to configure nginx to correctly handle this routing, as this is one of the most common sources of errors when setting up OIDC with Tuwunel.

Reverse Proxy Configuration

With the introduction of dynamic .well-known handling and OIDC in Tuwunel 1.6, the reverse proxy configuration becomes a critical part of the setup. While previous Matrix deployments often relied on serving static files directly from a web server, this approach is no longer sufficient.

As explained in the previous section, Tuwunel must now actively serve multiple discovery endpoints, including both Matrix and OIDC-related paths. This means that your reverse proxy must forward these requests to the Tuwunel backend instead of handling them locally.

Required Routing

At a minimum, the following paths must be routed to Tuwunel:

/_matrix/*
/_tuwunel/*
/.well-known/*

The /._matrix/ path handles the standard Matrix client-server API, while /_tuwunel/ is used internally for authentication and OIDC-related flows. The .well-known endpoints are required for both Matrix discovery and OIDC configuration.

Example nginx Configuration

The following configuration has been tested in a real-world setup and provides a clean, minimal, and robust baseline:

# Standard Matrix API
location /_matrix/ {
   proxy_pass http://tuwunel:6167;
   proxy_set_header Host $host;
   proxy_set_header X-Real-IP $remote_addr;
   proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
   proxy_set_header X-Forwarded-Proto $scheme;
}

# Internal Tuwunel endpoints (OIDC, auth flows)
location /_tuwunel/ {
   proxy_pass http://tuwunel:6167;
   proxy_set_header Host $host;
   proxy_set_header X-Real-IP $remote_addr;
   proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
   proxy_set_header X-Forwarded-Proto $scheme;
}

# Well-known (Matrix + OIDC discovery)
location ^~ /.well-known/ {
   proxy_pass http://tuwunel:6167;
   proxy_set_header Host $host;
   proxy_set_header X-Real-IP $remote_addr;
   proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
   proxy_set_header X-Forwarded-Proto $scheme;

   # Ensure exactly one valid CORS header
   proxy_hide_header Access-Control-Allow-Origin;
   add_header Access-Control-Allow-Origin * always;
}

This configuration ensures that all relevant requests are passed through to Tuwunel, allowing it to dynamically serve both Matrix and OIDC discovery endpoints.

Note: http://tuwunel:6167 is set in that example because the nginx webserver and the tuwunel server are sharing one docker network, hence its reachable under the container name. You need to adjust this according to your setup.

Why This Matters

If .well-known requests are not forwarded correctly, clients will fail to discover your homeserver’s authentication capabilities. Common symptoms include:

  • 404 Not Found on /.well-known/openid-configuration
  • CORS-related browser errors
  • Element Web failing during login
  • Element X being unable to complete authentication

These issues are often misinterpreted as problems with the identity provider or Tuwunel itself, when the root cause is actually incorrect reverse proxy routing.

Handling CORS Correctly

One of the more subtle issues you may encounter when setting up OIDC with Tuwunel is related to CORS (Cross-Origin Resource Sharing). While the concept itself is not new, it becomes particularly relevant once browsers start interacting with OIDC discovery endpoints such as:

/.well-known/openid-configuration

Modern Matrix clients like Element Web rely on these endpoints being accessible from the browser. If the required CORS headers are missing or malformed, the browser will simply block the request—even if the endpoint itself is working correctly.

A common pitfall in this context is that multiple components in your setup try to set the same header. For example, Tuwunel might include a CORS header in its response, nginx might add one as well, and an additional reverse proxy in front of nginx might do the same.

The result can look like this:

Access-Control-Allow-Origin: *, *

While this may seem harmless at first glance, it is actually invalid and will cause browsers to reject the response entirely.

The solution is simple in principle: make sure that only one layer is responsible for setting the CORS header.

In this guide, we handle CORS at the nginx level. To achieve this, we explicitly remove any header that might be set by upstream services:

proxy_hide_header Access-Control-Allow-Origin;

and then add a single, clean header:

add_header Access-Control-Allow-Origin * always;

This ensures that every response contains exactly one valid CORS header, which is sufficient for browser-based clients to function correctly.

If you prefer, you can also handle CORS at a different layer—for example in an outer reverse proxy—but the key point remains the same: there must only ever be one source of truth for this header.

Multi-Layer Reverse Proxy Setups

In many setups, nginx is not the only reverse proxy involved. Especially in home lab or NAS environments, it is quite common to have an additional layer in front of nginx, such as a Synology reverse proxy, Cloudflare, or another gateway.

In such cases, requests typically pass through multiple stages before reaching Tuwunel:

Client → Outer Reverse Proxy → nginx → Tuwunel

At first, this may seem transparent, but it introduces an important complication: each layer has the ability to modify headers, rewrite requests, or inject additional behavior.

This becomes particularly relevant for two aspects of your setup.

The first is CORS, as described above. If both nginx and the outer proxy attempt to set CORS headers, you will run into conflicts. This is why it is important to clearly decide where CORS should be handled and ensure that other layers do not interfere.

The second aspect is request forwarding. Tuwunel relies on certain headers to reconstruct the original request context, especially when it comes to OIDC. In particular, it needs to know whether the original request was made over HTTPS and which hostname was used.

This is handled via standard forwarding headers:

proxy_set_header Host $host;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

If these headers are missing or incorrect, you may encounter issues such as broken redirects, invalid callback URLs, or mismatches in OIDC discovery data.

In multi-layer setups, it is therefore important to ensure that each proxy correctly forwards these headers and does not overwrite them in unexpected ways.

In practice, this means:

  • ensuring that the outer proxy forwards requests transparently to nginx
  • letting nginx handle the application-specific routing
  • and avoiding overlapping responsibilities, especially for headers like CORS

Once this is properly configured, multi-layer setups work just as reliably as single-proxy environments.

User Migration and Account Provisioning

Once the technical setup is complete, one of the most important practical aspects is user management. This becomes especially relevant if you already have existing users in your Tuwunel database and want to migrate them seamlessly to your new OIDC-based authentication setup.

While Tuwunel supports automatic registration via OIDC, relying solely on this mechanism is not always the best approach—particularly when dealing with existing users. To ensure a smooth migration, it is recommended to take a more controlled path.

Why Manual User Creation Matters

When integrating an external identity provider, Tuwunel maps users based on the claims it receives during authentication. In our setup, this is typically the preferred_username, which is used to construct the Matrix ID:

@user:domain.ltd

For existing users, it is crucial that this mapping remains consistent. If the username provided by Pocket ID does not exactly match the existing Matrix localpart, Tuwunel will create a new account instead of linking to the existing one.

To avoid this, users should be created in Pocket ID with usernames that match the existing Matrix localparts. This ensures that when a user logs in via OIDC, Tuwunel can correctly associate the identity with the existing account.

Using Login Tokens for Initial Access

A practical challenge arises during migration: users that are created in Pocket ID do not yet have a passkey configured. Since Pocket ID is designed to operate without passwords, users cannot simply log in using traditional credentials. This is where login tokens become essential.

A login token allows a user to authenticate once without requiring a passkey. It acts as a temporary access mechanism that enables the user to complete their first login and set up their authentication method.

The recommended workflow looks like this:

  1. The administrator creates the user in Pocket ID with the correct username.
  2. The administrator generates a login token for that user.
  3. The user uses this token to log in to Pocket ID.
  4. During the first session, the user registers a passkey.
  5. From that point on, authentication is handled via passkey-based OIDC.

This mechanism is particularly valuable because it enables a smooth transition from a traditional user database to a passwordless authentication system.

Onboarding New Users

For new users, the process can be simplified significantly. Instead of manually creating accounts, you can issue registration tokens in Pocket ID. These tokens allow users to self-register in a controlled manner.

Once a new user registers and logs in via Pocket ID:

  • Tuwunel receives the identity via OIDC
  • a new Matrix account is created automatically
  • the username provided by Pocket ID becomes the Matrix localpart

Resulting in a user ID of the form:

@user:domain.ltd

This approach works particularly well for fresh deployments where no existing user base needs to be preserved.

Operational Recommendations

To keep your setup maintainable and secure over time, consider the following practices.

Always treat Pocket ID as the single source of truth for identities. Avoid manually creating or modifying users in Tuwunel unless absolutely necessary, as this can lead to inconsistencies. Encourage users to register at least two passkeys during onboarding. This provides a fallback in case one device is lost or unavailable. Use login tokens only for initial onboarding or recovery scenarios. They should be treated as sensitive, time-limited credentials and distributed securely. For new users, prefer registration tokens over open registration. This allows you to retain control over who can access your system while still providing a smooth onboarding experience.

Using Multiple Identity Providers

A common question when setting up OIDC with Tuwunel is whether it is possible to connect multiple identity providers, such as Google or GitHub, alongside Pocket ID.

To understand the implications, it helps to first look at the current architecture of this setup:

Element → Tuwunel (OIDC Server) → Pocket ID (OIDC IdP)

In this model, Pocket ID acts as the single source of truth for all user identities, while Tuwunel bridges authentication for Matrix clients. This results in a clean, predictable, and easy-to-maintain system.

A common expectation, however, is to extend this chain by adding additional upstream providers:

Element → Tuwunel → Pocket ID → Google / GitHub

While this is a valid architectural pattern in more complex identity setups, it is currently not supported by Pocket ID in the sense of full identity brokering. Pocket ID is intentionally minimalistic and does not aim to aggregate multiple external identity providers.

An alternative approach would be to configure multiple identity providers directly in Tuwunel. While this may work in some cases, it introduces a number of challenges. Different providers expose different user attributes (claims), which can lead to inconsistent user mapping. In practice, this often results in duplicate accounts or mismatched identities, especially when working with an existing user base.

In more advanced environments, this problem is typically solved by introducing a dedicated identity broker. In such setups, all external providers are unified behind a single authentication layer:

Tuwunel → Authentik → Google / GitHub / LDAP / etc.
Tuwunel → Keycloak → everything

These solutions are powerful and flexible, but they also come with increased complexity, higher resource consumption, and a steeper learning curve.

Pocket ID deliberately takes a different approach. It focuses on simplicity and a passkey-first authentication model, avoiding the overhead of full-fledged identity brokering.

For this reason, the recommended approach for most self-hosted setups is to stick with a single identity provider. This keeps the system easy to understand, reduces the risk of subtle bugs, and avoids unnecessary complexity.

In practice, this means that your current setup is not a limitation, but rather a well-balanced design choice. It provides strong security, a modern authentication flow, and a clean architecture — without introducing additional moving parts.

Conclusion

With the introduction of native OIDC support in Tuwunel 1.6, authentication in the Matrix ecosystem has taken a significant step forward. What was once a fragmented, client-driven process is now evolving into a standardized, server-centric model that is both more secure and easier to manage.

By combining Tuwunel with a lightweight identity provider like Pocket ID, it is possible to build a modern authentication stack that remains true to the principles of simplicity and efficiency. Pocket ID provides a minimal yet powerful foundation for identity management, while Tuwunel bridges the gap between Matrix clients and external authentication systems.

The shift to passkey-based authentication further strengthens this setup. By moving away from traditional passwords and leveraging hardware-backed credentials or secure keychains, both usability and security are improved at the same time. Users benefit from a seamless login experience, while administrators gain better control and visibility over authentication flows.

At the same time, this guide has shown that the transition is not entirely without challenges. Reverse proxy configuration, dynamic .well-known handling, and subtle issues such as CORS or multi-layer proxy interactions can introduce unexpected complexity. However, once these hurdles are understood and addressed, the resulting system is robust and reliable.

Perhaps most importantly, this setup establishes a clear separation of concerns. Authentication is handled by a dedicated identity provider, while the Matrix homeserver focuses on its core responsibilities. This not only aligns with the long-term direction of the Matrix ecosystem, but also makes future integrations and extensions significantly easier.

In the end, the combination of Tuwunel and Pocket ID offers a compelling solution for anyone looking to modernize their Matrix deployment. It provides strong security, a clean architecture, and a user-friendly experience—without the overhead of more complex enterprise identity systems.

As the ecosystem continues to evolve, this approach is likely to become the new standard.

Leave a Reply

Your email address will not be published. Required fields are marked *