Migration from LDAP to OIDC: Matrix
enIn my hackerspace we operate various services for our members. Up until this month, most of these services used to do user authentication against a LDAP server. For a multitude of reasons, we replaced the LDAP server with an OpenID Connect SSO using a Keycloak server as the OIDC Identity Provider.
In this series of articles I summarize the efforts required to migrate each of the services from LDAP to OIDC authentication. This article covers the setup and migration in the Matrix homeserver Synapse.
OIDC Setup in Synapse
Adding an OIDC provider in Synapse is accomplished by
adding its definition to the homeserver.yaml
configuration file:
oidc_providers:
- idp_id: "oidc"
idp_name: "Human Readable Name"
issuer: "https://sso.example.org/realms/example/"
client_id: "oidc-client-id-matrix"
client_secret: "oidc-client-secret-matrix"
scopes: ["openid", "profile"]
allow_existing_users: true
user_mapping_provider:
config:
localpart_template: "{{ user.preferred_username }}"
confirm_localpart: true
The crucial setting here is allow_existing_users: true
; this is what
enables migration from LDAP with OIDC. Synapse stores all users in
the same database, and providers such as LDAP or OIDC are primarily
used for authentication and initial account creation. This makes the
migration in Synapse pretty straight-forward: as long as the LDAP
provider and OIDC provider maps the user to the same Matrix ID
localpart (the s3lph
in @s3lph:kabelsalat.ch
), they map to the
same user and can be used interchangeably.
How the localpart is derived from the OIDC id_token
is configured in
the localpart_template
setting. This setting takes a Jinja2
template where the token claims are available in the user
object.
So the example above uses the preferred_username
token claim.
Even though I have not tested this, I'm fairly confident that a
migration should work even when the localpart_template
does not
match the existing users' localparts. However, then you'd have to
fill the user_external_ids
database table with the mappings between
OIDC subject identifiers (the sub
token claim) and the full Matrix
ID (@localpart:example.org
). This table is already populated each
time a user signs in via OIDC:
synapse=# select * from user_external_ids;
auth_provider | external_id | user_id
---------------+--------------------------------------+----------------------
oidc | ee5ff004-07c7-4c8f-b609-298aa1b5cd88 | @s3lph:kabelsalat.ch
You can also allow new users on your homeserver to choose a different
username when they sign in via OIDC for the first time. This is
controlled by the confirm_localpart
setting in the example above.
This too is controlled by entries in the user_external_ids
table.
This architecture of only using LDAP and OIDC as an authentication
providers and decoupling them from the actual users in this way,
alongside with the allow_existing_users
OIDC provider setting made
Synapse the easiest service to migrate. If you can even call it
"migration", that is - you simply add the OIDC provider and later
remove the LDAP provider and you're done.
Limitations
Unfortunately SSO handling in Matrix heavily depends on the specific clients your users are using, with greatly varying behaviors:
- Some clients don't support SSO at all. Check the client feature matrix to see which clients support SSO login.
- Some clients only support SSO if the default password
authentication flow,
m.login.password
, is disabled. However, if you need password login for some local users, disabling it may not be an option for you. - However, most clients I tested properly implement the
m.login.sso
authentication flow and give the user the option to sign in via SSO.
You can disable the m.login.password
flow if you don't need it by
adding the following to your homeserver.yaml
:
password_config:
enabled: false