This is a pretty geeky description of my matrix homeserver setup using Matrix-authentication-service and Nextcloud as the authentication source. It might be useful to others or future me. If not interested in technical details, please skip this post. A minimal version highlighting just the necessary configuration can be found in this TL;DR post. This is the slightly extended version explaining my setup.
Setting up matrix with experimental OIDC auth is still pretty … experimental … and seems difficult as there are no easy guides yet. Here, I describe my setup, using Nextcloud as an upstream authentication provider. This way, I can e.g. enforce 2FA authentication.
I don’t claim it is the most elegant of all setups but it seems to work fine for now.
My server is basically a single-user setup and I infrequently change users. Adding another user is as easy as creating a new nextcloud user and putting them into the “matrix” group in my cloud setup. Yay.
I started using MAS (Matrix-authentication-service) using a local password setup and that worked fine, there is no Nextcloud needed. So if you have a small server, consider using just that and save yourself a bit of complexity. I did want to use 2FA and I did want to play with stuff, so that is why I went fuly down the rabbit hole.
I do use containers, but I like to run things on bare metal too when it is easy. So I set up everything on a Debian server. Using docker or somesuch to install the components is certainly possible, but you will need to make the networking work yourself.
If you have feedback or improvement suggestions, let me know by responding via ActivityPub to my toot or ping me via matrix.
Setup
Domains
I deploy on my domain sspaeth.de with user names @user:sspaeth.de. The matrix server runs on the subdomain matrix.sspaeth.de and the MAS server runs on the domain auth.sspaeth.de. My nextcloud runs on cloud.sspaeth.de. Everything is presented to the world through a nginx proxy which also provides the necessary TLS certificates.
Components
Ignoring the sliding sync proxy (which is being integrated into synapse), my setup consists out of four components:
- some (static) .well-known files
- Synapse homeserver
- Matrix authentication service (MAS)
- Nextcloud as upstream auth provider
- A nginx proxy to really tie the room together
well-known files
To make my server setup discoverable, I use some simple well known files on MAIN_DOMAIN (it is the @user:MAIN_DOMAIN part). These are actually just static files on my cheap shared webhoster.
https://sspaeth.de/.well-known/matrix/server contains:
{ "m.server": "matrix.sspaeth.de:443" }
https://sspaeth.de/.well-known/matrix/client contains:
{
"m.homeserver": {
"base_url": "https://matrix.sspaeth.de"
},
"org.matrix.msc2965.authentication": {
"issuer": "https://auth.sspaeth.de/",
"account": "https://auth.sspaeth.de/account/"
}
}
(I left out the sliding sync proxy in the example above, to make things simpler to understand).
When I try to login via Element Web, I select ‘sspaeth.de’ as my home server and login looks kind of like this:
Synapse
I use the prebuild binary package provided through the matrix.org repository to install synapse (instructions here). You might use a different method, it should not really matter. I will also not explain here how to configure Synapse in general.
Suffice to say, that my synapse runs locally via http on port 8008, so homeserver.yaml
contains this:
- type: http
port: 8008
tls: false
type: http
x_forwarded: true
bind_addresses: ['::1', '127.0.0.1']
resources:
- names: [client, federation, metrics]
compress: false
Also, I set the following to disable plain old synapse-internal passwords (probably not all of them needed, but this is how it evolved over time. Let me know if I can ditch some):
enable_registration: false
password_config:
enabled: false
Now, the crucial part, and the only real change that I needed to do in Synapse is to set up MAS as the OIDC server. I put the below in a separate OIDC.yaml file in the conf.d subdirectory of my synapse config:
experimental_features:
msc3861:
enabled: true
issuer: https://auth.sspaeth.de/
# Synapse will call `{issuer}/.well-known/openid-configuration` to get the OIDC configuration
# Matches the `client_id` in the auth service config
client_id: 00000000000000000SYNAPSE00
# Matches the `client_auth_method` in the auth service config
client_auth_method: client_secret_basic
# Matches the `client_secret` in the auth service config
client_secret: 1234CLIENTSECRETHERE56789
# Matches the `matrix.secret` in the auth service config
admin_token: 0x97531ADMINTOKENHERE13579
That was really all there is to do in synapse. (I will post my nginx config file later)
OK, so far, so good, now when I try to login to synapse, synapse will call {issuer}/.well-known/openid-configuration
to get the OIDC configuration and use the endpoints specified there. (that openid-configuration URI is autogenerated by MAS, by the way)
MAS
I first describe the installation of the Matrix-authentication-service (MAS) (because perhaps not everyone wants to install it as weirdly as I did), followed by my configuration. MAS runs locally on port 8080.
Installation
I downloaded the latest release from https://github.com/matrix-org/matrix-authentication-service (0.10) and uncompressed it. The main mas-cli
binary went into /usr/local/bin, the “shared” folder went into /usr/local/share/mas-cli/*, my created config.yaml
file went into /etc/mas/ and a crude systemd service file autostarts it. The service file looks like this:
[Unit]
Description=MAS (Matrix OIDC Auth)
After=network.target
After=postgresql.service
[Service]
ExecReload=/bin/kill $MAINPID
# Environment="RUST_LOG=warn" # use one of debug,info,warn,error
User=www-data
Group=nogroup
WorkingDirectory=/etc/mas
ExecStart=/usr/local/bin/mas-cli server
Restart=on-failure
# Optional hardening to improve security
PrivateDevices=yes
PrivateTmp=yes
[Install]
WantedBy=multi-user.target
Yes, the hardening part could be made a lot better, I am sure… but that is for another day.
Now, let us have a look at the MAS configuration. This visualization illustrates how MAS sits between Synapse and the upstream IdP (Nextcloud):
Configuration
A few snippets that I either added to or where I modified the defaults in config.yaml
. The following sets up the client talking to synapse and MAS in general.
# Config client to talk to synapse
clients:
- client_id: 00000000000000000SYNAPSE00
client_auth_method: client_secret_basic
client_secret: 1234CLIENTSECRETHERE56789
redirect_uris:
- https://openidconnect.net/callback
This is the main MAS component listening on port 8080:
http:
listeners:
- name: web
resources:
- name: discovery
- name: human
- name: oauth
- name: compat
- name: assets
# NOTE: This is the custom path in my local installation!!!
path: /usr/local/share/mas-cli/assets/
binds:
- address: '[::1]:8080'
proxy_protocol: false
trusted_proxies:
- 192.128.0.0/16
- 172.16.0.0/12
- 10.0.0.0/10
- 127.0.0.1/8
- fd00::/8
- ::1/128
public_base: https://auth.sspaeth.de/
issuer: https://auth.sspaeth.de/
I’ll skip the database config here, but note, I also changed the template directory to suit my local installation:
templates:
path: /usr/local/share/mas-cli/templates/
assets_manifest: /usr/local/share/mas-cli/manifest.json
translations_path: /usr/local/share/mas-cli/translations/
I also tell my MAS about the matrix server and how to talk locally to it:
matrix:
homeserver: sspaeth.de
secret: 0x97531ADMINTOKENHERE13579
endpoint: http://localhost:8008/
This should be OK for local password auth through MAS, but the next section is what I added to configure Nextcloud as an upstream OIDC provider:
upstream_oauth2:
providers:
- id: 01B2BSNY1QVVS9ZG3JTVDHNYYE
# Note, above value is used in the Nextcloud config in the Redirection URI
human_name: Nextcloud
issuer: "https://cloud.sspaeth.de"
client_id: "THISISMYLONGANDSECRETNEXTCLOUDCLIENTID" # needs to be configured in the Nextcloud OIDC settings
client_secret: "THISISMYLONGANDSECRETNEXTCLOUDCLIENSECRET" # needs to be configured in the Nextcloud OIDC settings
token_endpoint_auth_method: "client_secret_post"
scope: "openid profile email"
claims_imports:
localpart:
action: require
template: "{{ user.preferred_username }}"
displayname:
action: suggest
template: "{{ user.name }}"
email:
action: suggest
template: "{{ user.email }}"
set_email_verification: import
Ahh, last but not least you can allow MAS to use local passwords and upstream Nextcloud in parallel. If you want to only allow Nextcloud, you also need to disable local passwords. In this case, you will directly be forwarded to the Nextcloud login page instead of the auth.sspaeth.de/login one first.
passwords:
enabled: false
schemes:
- version: 1
algorithm: argon2id
Nextcloud
The OpenID Connect app/plugin adds the central config option OpenID Connect clients
. In this I have set:
Name auth.sspaeth.de
# Note the Redirect URI below uses the
# upstream_oauth2->providers->id value from my MAS configuration. (see above)
Redirection URI: https://auth.sspaeth.de/upstream/callback/01B2BSNY1QVVS9ZG3JTVDHNYYE
Client Identifier: THISISMYLONGANDSECRETNEXTCLOUDCLIENTID
Secret: THISISMYLONGANDSECRETNEXTCLOUDCLIENTSECRET
Signing Algorithm: RS256
Type: confidential
Flows: Code & Implicit Authorization Flow
Limited to Groups: matrix # my choice to only allow specific users to log in.
Note, that it uses three values that I had configured in my MAS upstream_oauth2 part.
NGINX
My nginx server serves 1) matrix.sspaeth.de, these are noteworthy locations:
# Only if you need Sliding Sync which runs on port 8009 on my box.
# Not covered in this setup description.
location ~ ^/(client/|_matrix/client/unstable/org.matrix.msc3575/sync) {
proxy_pass http://127.0.0.1:8009$request_uri;
proxy_set_header Host $http_host;
proxy_buffering off;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header X-Forwarded-Proto $scheme;
}
These legacy matrix auth endpoints at matrix.sspaeth.de, need to be redirected to my MAS instance running on port 8080:
# Forward legacy auth to the matrix-auth service
location ~ ^/_matrix/client/(.*)/(login|logout|refresh) {
proxy_pass http://localhost:8080;
proxy_http_version 1.1;
}
Plus all the “normal” matrix communication that should go to synapse:
# Synapse
location ~ ^(/_matrix|/_synapse/client) {
# note: do not add a path (even a single /) after the port in `proxy_pass`,
# otherwise nginx will canonicalise the URI and cause signature verification
# errors.
proxy_pass http://localhost:8008;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Host $host;
# Nginx by default only allows file uploads up to 1M in size
# Increase client_max_body_size to match max_upload_size defined in homeserver.yaml
client_max_body_size 50M;
# Synapse responses may be chunked, which is an HTTP/1.1 feature.
proxy_http_version 1.1;
}
Then we have 2) the domain auth.sspaeth.de which is where MAS does its magic. I pretty much just forward all traffic to the MAS server, and serve the static assets directly. The two relevant locations in nginx are:
location / {
proxy_pass http://[::1]:8080;
proxy_http_version 1.1;
}
# Optional: serve the assets directly via nginx
location /assets/ {
root /usr/local/share/mas-cli/;
# Serve pre-compressed assets
gzip_static on;
# With the ngx_brotli module installed:
#brotli_static on;
# Cache assets for a year
expires 365d;
}
That was simple wasn’t it?
I don’t explain the nginx setup of Nextcloud, you should have one running beforehand if you are interested in this.
Now, if I want to login, I select “sspaeth.de” as a server in e.g. Element Web, click login and get presented with the regular Nextcloud login page.
That is all, folks.