About this documentation
This documentation is intended to give an overview of how the matrix-authentication-service
works, both from an admin perspective and from a developer perspective.
The documentation itself is built using mdBook. A hosted version is available at https://matrix-org.github.io/matrix-authentication-service/.
Links
Planning the installation
This part of the documentation goes through installing the service, the important parts of the configuration file, and how to run the service.
Before going through the installation, it is important to understand the different components of an OIDC-native Matrix homeserver, and how they interact with each other. It is meant to complement the homeserver, replacing the internal authentication mechanism with the authentication service.
Making a homeserver deployment OIDC-native radically shifts the authentication model: the homeserver is no longer responsible for managing user accounts and sessions. The authentication service becomes the source of truth for user accounts and access tokens, and the homeserver only verifies the validity of the tokens it receives through the service.
At time of writing, the authentication service is meant to be run on a standalone domain name (e.g. auth.example.com
), and the homeserver on another (e.g. matrix.example.com
).
This domain will be user-facing as part of the authentication flow.
When a client initiates an authentication flow, it will discover the authentication service through the deployment .well-known/matrix/client
endpoint.
This file will refer to an issuer
, which is the canonical name of the authentication service instance.
Out of that issuer, it will discover the rest of the endpoints by calling the [issuer]/.well-known/openid-configuration
endpoint.
By default, the issuer
will match the root domain where the service is deployed (e.g. https://auth.example.com/
), but it can be configured to be different.
An example setup could look like this:
-
The deployment domain is
example.com
, so Matrix IDs look like@user:example.com
-
The issuer chosen is
https://example.com/
-
The homeserver is deployed on
matrix.example.com
-
The authentication service is deployed on
auth.example.com
-
Calling
https://example.com/.well-known/matrix/client
returns the following JSON:{ "m.homeserver": { "base_url": "https://matrix.example.com" }, "org.matrix.msc2965.authentication": { "issuer": "https://example.com/", "account": "https://auth.example.com/account" } }
-
Calling
https://example.com/.well-known/openid-configuration
returns a JSON document similar to the following:{ "issuer": "https://example.com/", "authorization_endpoint": "https://auth.example.com/authorize", "token_endpoint": "https://auth.example.com/oauth2/token", "jwks_uri": "https://auth.example.com/oauth2/keys.json", "registration_endpoint": "https://auth.example.com/oauth2/registration", "//": "..." }
With the installation planned, it is time to go through the installation and configuration process. The first section focuses on installing the service.
Installation
Pre-built binaries
Pre-built binaries can be found attached on each release, for Linux and macOS, on both x86_64
and aarch64
architectures.
mas-cli-aarch64-linux.tar.gz
mas-cli-x86_64-linux.tar.gz
mas-cli-aarch64-macos.tar.gz
mas-cli-x86_64-macos.tar.gz
Each archive contains:
- the
mas-cli
binary - assets needed for running the service, including:
share/assets/
: the built frontend assetsshare/manifest.json
: the manifest for the frontend assetsshare/policy.wasm
: the built OPA policiesshare/templates/
: the default templatesshare/translations/
: the default translations
The location of all these assets can be overridden in the configuration file.
Example shell commands to download and extract the mas-cli
binary:
ARCH=x86_64 # or aarch64
OS=linux # or macos
VERSION=latest # or a specific version, like "v0.1.0"
# URL to the right archive
URL="https://github.com/matrix-org/matrix-authentication-service/releases/${VERSION}/download/mas-cli-${ARCH}-${OS}.tar.gz"
# Create a directory and extract the archive in it
mkdir -p /path/to/mas
curl -sL "$URL" | tar xzC /path/to/mas
# This should display the help message
/path/to/mas/mas-cli --help
Note for macOS users: the binaries are not signed, so if the archive is downloaded from a browser, it may be necessary to run xattr -d com.apple.quarantine mas-cli
to remove the quarantine attribute on the binary.
Using the Docker image
A pre-built Docker image is available here: ghcr.io/matrix-org/matrix-authentication-service:main
The main
tag is built from the main
branch, and each commit on the main
branch is also tagged with a stable sha-<commit sha>
tag.
The image can also be built from the source:
- Get the source
git clone https://github.com/matrix-org/matrix-authentication-service.git cd matrix-authentication-service
- Build the image
docker build -t mas .
Building from the source
Building from the source requires:
- The latest stable Rust toolchain
- Node.js (18 and later) and npm
- the Open Policy Agent binary (or alternatively, Docker)
- Get the source
git clone https://github.com/matrix-org/matrix-authentication-service.git cd matrix-authentication-service
- Build the frontend
This will produce acd frontend npm ci npm run build cd ..
frontend/dist
directory containing the built frontend assets. This folder, along with thefrontend/dist/manifest.json
file, can be relocated, as long as the configuration file is updated accordingly. - Build the Open Policy Agent policies
OR, if you don't havecd policies make cd ..
opa
installed and want to build through the OPA docker image
This will produce acd policies make DOCKER=1 cd ..
policies/policy.wasm
file containing the built OPA policies. This file can be relocated, as long as the configuration file is updated accordingly. - Compile the CLI
cargo build --release
- Grab the built binary
cp ./target/release/mas-cli ~/.local/bin # Copy the binary somewhere in $PATH mas-cli --help # Should display the help message
Next steps
The service needs some configuration to work. This includes random, private keys and secrets. Follow the configuration guide to configure the service.
General configuration
Initial configuration generation
The service needs a few unique secrets and keys to work. It mainly includes:
- the various signing keys referenced in the
secrets.keys
section - the encryption key used to encrypt fields in the database and cookies, set in the
secrets.encryption
section - a shared secret between the service and the homeserver, set in the
matrix.secret
section
Although it is possible to generate these secrets manually, it is strongly recommended to use the config generate
command to generate a configuration file with unique secrets and keys.
mas-cli config generate > config.yaml
If you're using the docker container, the command mas-cli
can be invoked with docker run
:
docker run ghcr.io/matrix-org/matrix-authentication-service:main config generate > config.yaml
This applies to all of the mas-cli
commands in this document.
Note: The generated configuration file is very extensive, and contains the default values for all the configuration options. This will be made easier to read in the future, but in the meantime, it is recommended to strip untouched options from the configuration file.
Using and inspecting the configuration file
When using the mas-cli
, multiple configuration files can be loaded, with the following rule:
- If the
--config
option is specified, possibly multiple times, load the file at the specified path, relative to the current working directory - If not, load the files specified in the
MAS_CONFIG
environment variable if set, separated by:
, relative to the current working directory - If not, load the file at
config.yaml
in the current working directory
The validity of the configuration file can be checked using the config check
command:
# This will read both the `first.yaml` and `second.yaml` files
mas-cli config check --config=first.yaml --config=second.yaml
# This will also read both the `first.yaml` and `second.yaml` files
MAS_CONFIG=first.yaml:second.yaml mas-cli config check
# This will only read the `config.yaml` file
mas-cli config check
To help understand what the resulting configuration looks like after merging all the configuration files, the config dump
command can be used:
mas-cli config dump
Configuration schema
The configuration file is validated against a JSON schema, which can be found here. Many tools in text editors can use this schema to provide autocompletion and validation.
Syncing the configuration file with the database
Some sections of the configuration file need to be synced every time the configuration file is updated.
This includes the clients
and upstream_oauth
sections.
The configuration is synced by default on startup, and can be manually synced using the config sync
command.
By default, this will only add new clients and upstream OAuth providers and update existing ones, but will not remove entries that were removed from the configuration file.
To do so, use the --prune
option:
mas-cli config sync --prune
Next step
After generating the configuration file, the next step is to set up a database.
Database configuration
The service uses a PostgreSQL database to store all of its state.
Although it may be possible to run with earlier versions, it is recommended to use PostgreSQL 13 or later.
Connection to the database is configured in the database
section of the configuration file.
Set up a database
You will need to create a dedicated PostgreSQL database for the service. The database can run on the same server as the service, or on a dedicated host. The recommended setup for this database is to create a dedicated role and database for the service.
Assuming your PostgreSQL database user is called postgres
, first authenticate as the database user with:
su - postgres
# Or, if your system uses sudo to get administrative rights
sudo -u postgres bash
Then, create a postgres user and a database with:
# this will prompt for a password for the new user
createuser --pwprompt mas_user
createdb --owner=mas_user mas
The above will create a user called mas_user
with a password of your choice, and a database called mas
owned by the mas_user
user.
Service configuration
Once the database is created, the service needs to be configured to connect to it.
Edit the database
section of the configuration file to match the database just created:
database:
# Full connection string as per
# https://www.postgresql.org/docs/13/libpq-connect.html#id-1.7.3.8.3.6
uri: postgres://<user>:<password>@<host>/<database>
# -- OR --
# Separate parameters
host: <host>
port: 5432
username: <user>
password: <password>
database: <database>
Database migrations
The service manages the database schema with embedded migrations.
Those migrations are run automatically when the service starts, but it is also possible to run them manually.
This is done using the database migrate
command:
mas-cli database migrate
Next steps
Once the database is up, the remaining steps are to:
- Set up the connection to the homeserver (recommended)
- Setup email sending (optional)
- Configure a reverse proxy (optional)
- Run the service
Homeserver configuration
The matrix-authentication-service
is designed to be run alongside a Matrix homeserver.
It currently only supports Synapse through the experimental OAuth delegation feature.
The authentication service needs to be able to call the Synapse admin API to provision users through a shared secret, and Synapse needs to be able to call the service to verify access tokens using the OAuth 2.0 token introspection endpoint.
Provision a client for the Homeserver to use
In the clients
section of the configuration file, add a new client with the following properties:
client_id
: a unique identifier for the client. It must be a valid ULID, and it happens that0000000000000000000SYNAPSE
is a valid ULID.client_auth_method
: set toclient_secret_basic
. Other methods are possible, but this is the easiest to set up.client_secret
: a shared secret used for the homeserver to authenticate
clients:
- client_id: 0000000000000000000SYNAPSE
client_auth_method: client_secret_basic
client_secret: "SomeRandomSecret"
Don't forget to sync the configuration file with the database after adding the client, using the config sync
command.
Configure the connection to the homeserver
In the matrix
section of the configuration file, add the following properties:
homeserver
: corresponds to theserver_name
in the Synapse configuration filesecret
: a shared secret the service will use to call the homeserver admin APIendpoint
: the URL to which the homeserver is accessible from the service
matrix:
homeserver: localhost:8008
secret: "AnotherRandomSecret"
endpoint: "http://localhost:8008"
Configure the homeserver to delegate authentication to the service
Set up the delegated authentication feature in the Synapse configuration in the experimental_features
section:
experimental_features:
msc3861:
enabled: true
# Synapse will call `{issuer}/.well-known/openid-configuration` to get the OIDC configuration
issuer: http://localhost:8080/
# Matches the `client_id` in the auth service config
client_id: 0000000000000000000SYNAPSE
# 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: "SomeRandomSecret"
# Matches the `matrix.secret` in the auth service config
admin_token: "AnotherRandomSecret"
# URL to advertise to clients where users can self-manage their account
account_management_url: "http://localhost:8080/account"
Set up the compatibility layer
The service exposes a compatibility layer to allow legacy clients to authenticate using the service. This works by exposing a few Matrix endpoints that should be proxied to the service.
The following Matrix Client-Server API endpoints need to be handled by the authentication service:
See the reverse proxy configuration guide for more information.
Configuring a reverse proxy
Although the service can be exposed directly to the internet, including handling the TLS termination, many deployments will want to run a reverse proxy in front of the service.
In those configuration, the service should be configured to listen on localhost
or Unix domain socket.
Example configuration
http:
public_base: https://auth.example.com/
listeners:
- name: web
resources:
- name: discovery
- name: human
- name: oauth
- name: compat
- name: graphql
# Uncomment to serve the assets by the service
#- name: assets
# path: ./share/assets/
binds:
# Bind on a local port
- host: localhost
port: 8080
# OR bind on a Unix domain socket
#- socket: /var/run/mas.sock
# OR bind on a systemd socket
#- fd: 0
# kind: tcp # or unix
# Optional: use the PROXY protocol
#proxy_protocol: true
Example nginx configuration
Note that the assets can be served directly by nginx, and the assets
resource can be removed from the service configuration.
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name auth.example.com;
ssl_certificate path/to/fullchain.pem;
ssl_certificate_key path/to/privkey.pem;
location / {
proxy_pass http://localhost:8080;
# OR via the Unix domain socket
#proxy_pass http://unix:/var/run/mas.sock;
proxy_http_version 1.1;
# Optional: use the PROXY protocol
#proxy_protocol on;
}
# Optional: serve the assets directly
location /assets/ {
root /path/to/share/assets/;
# Serve pre-compressed assets
gzip_static on;
# With the ngx_brotli module installed
# https://github.com/google/ngx_brotli
#brotli_static on;
# Cache assets for a year
expires 365d;
}
}
For the compatibility layer, the following endpoints need to be proxied to the service:
/_matrix/client/*/login
/_matrix/client/*/logout
/_matrix/client/*/refresh
For example, a nginx configuration could look like:
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name matrix.example.com;
# Forward to the auth service
location ~ ^/_matrix/client/(.*)/(login|logout|refresh) {
proxy_pass http://localhost:8080;
# OR via the Unix domain socket
#proxy_pass http://unix:/var/run/mas.sock;
proxy_http_version 1.1;
# Optional: use the PROXY protocol
#proxy_protocol on;
}
# Forward to Synapse
# as per https://matrix-org.github.io/synapse/latest/reverse_proxy.html#nginx
location ~ ^(/_matrix|/_synapse/client) {
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;
client_max_body_size 50M;
proxy_http_version 1.1;
}
}
.well-known configuration
A .well-known/matrix/client
file is required to be served to allow clients to discover the authentication service.
If no .well-known/matrix/client
file is served currently then this will need to be enabled.
If the homeserver is Synapse and serving this file already then the correct values will already be included when the homeserver is configured to use MAS.
If the .well-known is hosted elsewhere then org.matrix.msc2965.authentication
entries need to be included similar to the following:
{
"m.homeserver": {
"base_url": "https://matrix.example.com"
},
"org.matrix.msc2965.authentication": {
"issuer": "https://example.com/",
"account": "https://auth.example.com/account"
}
}
For more context on what the correct values are, see here.
Configure an upstream SSO provider
The authentication service supports using an upstream OpenID Connect provider to authenticate its users. Multiple providers can be configured, and can be used in conjunction with the local password database authentication.
Any OIDC compliant provider should work with the service as long as it supports the authorization code flow.
Note that the service does not support other SSO protocols such as SAML, and there is no plan to support them in the future. A deployment which requires SAML or LDAP-based authentication should use a service like Dex to bridge between the SAML provider and the authentication service.
General configuration
Configuration of upstream providers is done in the upstream_oauth2
section of the configuration file, which has a providers
list.
Additions and changes to this sections are synced with the database on startup.
Removals need to be applied using the mas-cli config sync --prune
command.
An exhaustive list of all the parameters is available in the configuration file reference.
The general configuration usually goes as follows:
- determine a unique
id
for the provider, which will be used as stable identifier between the configuration file and the database. Thisid
must be a ULID, and can be generated using online tools like https://www.ulidtools.com - create an OAuth 2.0/OIDC client on the provider's side, using the following parameters:
redirect_uri
:https://<auth-service-domain>/upstream/callback/<id>
response_type
:code
response_mode
:query
grant_type
:authorization_code
- fill the
upstream_oauth2
section of the configuration file with the following parameters:providers
:id
: the previously generated ULIDclient_id
: the client ID of the OAuth 2.0/OIDC client given by the providerclient_secret
: the client secret of the OAuth 2.0/OIDC client given by the providerissuer
: the issuer URL of the providerscope
: the scope to request from the provider.openid
is usually required, andprofile
andemail
are recommended to import a few user attributes.
- setup user attributes mapping to automatically fill the user profile with data from the provider. See the user attributes mapping section for more details.
User attributes mapping
The authentication service supports importing the following user attributes from the provider:
- The localpart/username (e.g.
@localpart:example.com
) - The display name
- An email address
For each of those attributes, administrators can configure a mapping using the claims provided by the upstream provider. They can also configure what should be done for each of those attributes. It can either:
ignore
: ignore the attribute, and let the user fill it manuallysuggest
: suggest the attribute to the user, but let them opt-out of importing itforce
: automatically import the attribute, but don't fail if it is not provided by the providerrequire
: automatically import the attribute, and fail if it is not provided by the provider
A Jinja2 template is used as mapping for each attribute. The template currently has one user
variable, which is an object with the claims got through the id_token
given by the provider.
The following default templates are used:
localpart
:{{ user.preferred_username }}
displayname
:{{ user.name }}
email
:{{ user.email }}
Multiple providers behaviour
Multiple authentication methods can be configured at the same time, in which case the authentication service will let the user choose which one to use.
This is true if both the local password database and an upstream provider are configured, or if multiple upstream providers are configured.
In such cases, the human_name
parameter of the provider configuration is used to display a human-readable name for the provider, and the brand_name
parameter is used to show a logo for well-known providers.
If there is only one upstream provider configured and the local password database is disabled (passwords.enabled
is set to false
), the authentication service will automatically trigger an authorization flow with this provider.
Sample configurations
This section contains sample configurations for popular OIDC providers.
Authentik
Authentik is an open-source IdP solution.
- Create a provider in Authentik, with type OAuth2/OpenID.
- The parameters are:
- Client Type: Confidential
- Redirect URIs:
https://<auth-service-domain>/upstream/callback/<id>
- Create an application for the authentication service in Authentik and link it to the provider.
- Note the slug of your application, Client ID and Client Secret.
Authentication service configuration:
upstream_oauth2:
providers:
- id: 01HFRQFT5QFMJFGF01P7JAV2ME
human_name: Authentik
issuer: "https://<authentik-domain>/application/o/<app-slug>/" # TO BE FILLED
client_id: "<client-id>" # TO BE FILLED
client_secret: "<client-secret>" # TO BE FILLED
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: always
- You will need a Facebook developer account. You can register for one here.
- On the apps page of the developer console, "Create App", and choose "Allow people to log in with their Facebook account".
- Once the app is created, add "Facebook Login" and choose "Web". You don't need to go through the whole form here.
- In the left-hand menu, open "Use cases" > "Authentication and account creation" > "Customize" > "Settings"
- Add
https://<auth-service-domain>/upstream/callback/<id>
as an OAuth Redirect URL.
- Add
- In the left-hand menu, open "App settings/Basic". Here you can copy the "App ID" and "App Secret" for use below.
Authentication service configuration:
upstream_oauth2:
providers:
- id: "01HFS3WM7KSWCEQVJTN0V9X1W6"
issuer: "https://www.facebook.com"
human_name: "Facebook"
brand_name: "facebook"
discovery_mode: disabled
pkce_method: always
authorization_endpoint: "https://facebook.com/v11.0/dialog/oauth/"
token_endpoint: "https://graph.facebook.com/v11.0/oauth/access_token"
jwks_uri: "https://www.facebook.com/.well-known/oauth/openid/jwks/"
token_endpoint_auth_method: "client_secret_post"
client_id: "<app-id>" # TO BE FILLED
client_secret: "<app-secret>" # TO BE FILLED
scope: "openid"
claims_imports:
localpart:
action: ignore
displayname:
action: suggest
template: "{{ user.name }}"
email:
action: suggest
template: "{{ user.email }}"
set_email_verification: always
GitLab
- Create a new application.
- Add the
openid
scope. Optionally add theprofile
andemail
scope if you want to import the user's name and email. - Add this Callback URL:
https://<auth-service-domain>/upstream/callback/<id>
Authentication service configuration:
upstream_oauth2:
providers:
- id: "01HFS67GJ145HCM9ZASYS9DC3J"
issuer: "https://gitlab.com"
human_name: "GitLab"
brand_name: "gitlab"
token_endpoint_auth_method: "client_secret_post"
client_id: "<client-id>" # TO BE FILLED
client_secret: "<client-secret>" # TO BE FILLED
scope: "openid profile email"
claims_imports:
displayname:
action: suggest
template: "{{ user.name }}"
localpart:
action: ignore
email:
action: suggest
template: "{{ user.email }}"
- Set up a project in the Google API Console (see documentation)
- Add an "OAuth Client ID" for a Web Application under "Credentials"
- Add the following "Authorized redirect URI":
https://<auth-service-domain>/upstream/callback/<id>
Authentication service configuration:
upstream_oauth2:
providers:
- id: 01HFS6S2SVAR7Y7QYMZJ53ZAGZ
human_name: Google
brand_name: "google"
issuer: "https://accounts.google.com"
client_id: "<client-id>" # TO BE FILLED
client_secret: "<client-secret>" # TO BE FILLED
scope: "openid profile email"
claims_imports:
localpart:
action: ignore
displayname:
action: suggest
template: "{{ user.name }}"
email:
action: suggest
template: "{{ user.email }}"
Keycloak
Follow the Getting Started Guide to install Keycloak and set up a realm.
-
Click
Clients
in the sidebar and clickCreate
-
Fill in the fields as below:
Field Value Client ID matrix-authentication-service
Client Protocol openid-connect
-
Click
Save
-
Fill in the fields as below:
Field Value Client ID matrix-authentication-service
Enabled On
Client Protocol openid-connect
Access Type confidential
Valid Redirect URIs https://<auth-service-domain>/upstream/callback/<id>
-
Click
Save
-
On the Credentials tab, update the fields:
Field Value Client Authenticator Client ID and Secret
-
Click
Regenerate Secret
-
Copy Secret
upstream_oauth2:
providers:
- id: "01H8PKNWKKRPCBW4YGH1RWV279"
issuer: "https://<keycloak>/realms/<realm>" # TO BE FILLED
token_endpoint_auth_method: client_secret_basic
client_id: "matrix-authentication-service"
client_secret: "<client-secret>" # TO BE FILLED
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: always
Microsoft Azure Active Directory
Azure AD can act as an OpenID Connect Provider.
Register a new application under App registrations in the Azure AD management console.
The RedirectURI
for your application should point to your authentication service instance:
https://<auth-service-domain>/upstream/callback/<id>
where <id>
is the same as in the config file.
Go to Certificates & secrets and register a new client secret. Make note of your Directory (tenant) ID as it will be used in the Azure links.
Authentication service configuration:
upstream_oauth2:
providers:
- id: "01HFRPWGR6BG9SAGAKDTQHG2R2"
human_name: Microsoft Azure AD
issuer: "https://login.microsoftonline.com/<tenant-id>/v2.0" # TO BE FILLED
client_id: "<client-id>" # TO BE FILLED
client_secret: "<client-secret>" # TO BE FILLED
scope: "openid profile email"
claims_imports:
localpart:
action: require
template: "{{ (user.preferred_username | split('@'))[0] }}"
displayname:
action: suggest
template: "{{ user.name }}"
email:
action: suggest
template: "{{ user.email }}"
set_email_verification: always
Running the service
To fully function, the service needs to run two main components:
- An HTTP server
- A background worker
By default, the mas-cli server
command will start both components.
It is possible to only run the HTTP server by setting the --no-worker
option, and run a background worker with the mas-cli worker
command.
Both components are stateless, and can be scaled horizontally by running multiple instances of each.
Runtime requirements
Other than the binary, the service needs a few files to run:
- The templates, referenced by the
templates.path
configuration option - The compiled policy, referenced by the
policy.path
configuration option - The frontend assets, referenced by the
path
option of theassets
resource in thehttp.listeners
configuration section - The frontend manifest file, referenced by tge
templates.assets_manifest
configuration option
Be sure to check the installation instructions for more information on how to get these files, and make sure the configuration file is updated accordingly.
Configure the HTTP server
The service can be configured to have multiple HTTP listeners, serving different resources.
See the http.listeners
configuration section for more information.
The service needs to be aware of the public URL it is served on, regardless of the HTTP listeners configuration.
This is done using the http.public_base
configuration option.
By default, the OIDC issuer advertised by the /.well-known/openid-configuration
endpoint will be the same as the public_base
URL, but can be configured to be different.
Tweak the remaining configuration
A few configuration sections might still require some tweaking, including:
telemetry
: to setup metrics, tracing and Sentry crash reportingemail
: to setup email sendingpassword
: to enable/disable password authenticationupstream_oauth
: to configure upstream OAuth providers
Run the service
Once the configuration is done, the service can be started with the mas-cli server
command:
mas-cli server
It is advised to run the service as a non-root user, using a tool like systemd
to manage the service lifecycle.
Troubleshoot common issues
Once the service is running, it is possible to check its configuration using the mas-cli doctor
command.
This should help diagnose common issues with the service configuration and deployment.
Migrating an existing homeserver
One of the design goals of MAS has been to allow it to be used to migrate an existing homeserver to an OIDC-based architecture.
Specifically without requiring users to re-authenticate and that non-OIDC clients continue to work.
Features that are provided to support this include:
- Ability to import existing password hashes from Synapse
- Ability to import existing sessions and devices
- Ability to import existing access tokens linked to devices (ie not including short-lived admin puppeted access tokens)
- Ability to import existing upstream IdP subject ID mappings
- Provides a compatibility layer for legacy Matrix authentication
There will be tools to help with the migration process itself. But these aren't quite ready yet.
Preparing for the migration
The deployment is non-trivial so it is important to read through and understand the steps involved and make a plan before starting.
Run the migration advisor
You can use the advisor mode of the syn2mas
tool to identify extra configuration steps or issues with the configuration of the homeserver.
syn2mas --command=advisor --synapseConfigFile=homeserver.yaml
This will output WARN
entries for any identified actions and ERROR
entries in the case of any issues that will prevent the migration from working.
Install and configure MAS alongside your existing homeserver
Follow the instructions in the installation guide to install MAS alongside your existing homeserver.
Map any upstream SSO providers
If you are using an upstream SSO provider then you will need to provision the upstream provide in MAS manually.
Each upstream provider will need to be given as an --upstreamProviderMapping
command line option to the import tool.
Do a dry-run of the import to test
syn2mas --command migrate --synapseConfigFile homeserver.yaml --masConfigFile config.yaml --dryRun
If no errors are reported then you can proceed to the next step.
Doing the migration
Having done the preparation, you can now proceed with the actual migration. Note that this will require downtime for the homeserver and is not easily reversible.
Backup your data
As with any migration, it is important to backup your data before proceeding.
Shutdown the homeserver
This is to ensure that no new sessions are created whilst the migration is in progress.
Configure the homeserver
Follow the instructions in the homeserver configuration guide to configure the homeserver to use MAS.
Do the import
Run syn2mas
in non-dry-run mode.
syn2mas --command migrate --synapseConfigFile homeserver.yaml --masConfigFile config.yaml --dryRun false
Start up the homeserver
Start up the homeserver again with the new configuration.
Update or serve the .well-known
The .well-known/matrix/client
needs to be served as described here.
Configuration file reference
http
Controls the web server.
http:
# Public URL base used when building absolute public URLs
public_base: https://auth.example.com/
# OIDC issuer advertised by the service. Defaults to `public_base`
issuer: https://example.com/
# List of HTTP listeners, see below
listeners:
# ...
http.listeners
Each listener can serve multiple resources, and listen on multiple TCP ports or UNIX sockets.
http:
listeners:
# The name of the listener, used in logs and metrics
- name: web
# List of resources to serve
resources:
# Serves the .well-known/openid-configuration document
- name: discovery
# Serves the human-facing pages, such as the login page
- name: human
# Serves the OAuth 2.0/OIDC endpoints
- name: oauth
# Serves the Matrix C-S API compatibility endpoints
- name: compat
# Serve the GraphQL API used by the frontend,
# and optionally the GraphQL playground
- name: graphql
playground: true
# Serve the given folder on the /assets/ path
- name: assets
path: ./share/assets/
# List of addresses and ports to listen to
binds:
# First option: listen to the given address
- address: "[::]:8080"
# Second option: listen on the given host and port combination
- host: localhost
port: 8081
# Third option: listen on the given UNIX socket
- socket: /tmp/mas.sock
# Fourth option: grab an already open file descriptor given by the parent process
# This is useful when using systemd socket activation
- fd: 1
# Kind of socket that was passed, defaults to tcp
kind: tcp # or unix
# Whether to enable the PROXY protocol on the listener
proxy_protocol: false
# If set, makes the listener use TLS with the provided certificate and key
tls:
#certificate: <inline PEM>
certificate_file: /path/to/cert.pem
#key: <inline PEM>
key_file: /path/to/key.pem
#password: <password to decrypt the key>
#password_file: /path/to/password.txt
The following additional resources are available, although it is recommended to serve them on a separate listener, not exposed to the public internet:
name: prometheus
: serves a Prometheus-compatible metrics endpoint on/metrics
, if the Prometheus exporter is enabled intelemetry.metrics.exporter
.name: health
: serves the health check endpoint on/health
.
database
Configure how to connect to the PostgreSQL database.
database:
# Full connection string as per
# https://www.postgresql.org/docs/13/libpq-connect.html#id-1.7.3.8.3.6
uri: postgresql://user:password@hostname:5432/database?sslmode=require
# -- OR --
# Separate parameters
host: hostname
port: 5432
#socket:
username: user
password: password
database: database
# Additional parameters for the connection pool
min_connections: 0
max_connections: 10
connect_timeout: 30
idle_timeout: 600
max_lifetime: 1800
matrix
Settings related to the connection to the Matrix homeserver
matrix:
# The homeserver name, as per the `server_name` in the Synapse configuration file
homeserver: example.com
# Shared secret used to authenticate the service to the homeserver
# This must be of high entropy, because leaking this secret would allow anyone to perform admin actions on the homeserver
secret: "SomeRandomSecret"
# URL to which the homeserver is accessible from the service
endpoint: "http://localhost:8008"
templates
Allows loading custom templates
templates:
# From where to load the templates
# This is relative to the current working directory, *not* the config file
path: /to/templates
# Path to the frontend assets manifest file
assets_manifest: /to/manifest.json
clients
List of OAuth 2.0/OIDC clients and their keys/secrets. Each client_id
must be a ULID.
clients:
# Confidential client
- client_id: 000000000000000000000FIRST
client_auth_method: client_secret_post
client_secret: secret
# List of authorized redirect URIs
redirect_uris:
- http://localhost:1234/callback
# Public client
- client_id: 00000000000000000000SEC0ND
client_auth_method: none
Note: any additions or modification in this list are synced with the database on server startup. Removed entries are only removed with the config sync --prune
command.
secrets
Signing and encryption secrets
secrets:
# Encryption secret (used for encrypting cookies and database fields)
# This must be a 32-byte long hex-encoded key
encryption: c7e42fb8baba8f228b2e169fdf4c8216dffd5d33ad18bafd8b928c09ca46c718
# Signing keys
keys:
# It needs at least an RSA key to work properly
- kid: "ahM2bien"
key: |
-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEAuf28zPUp574jDRdX6uN0d7niZCIUpACFo+Po/13FuIGsrpze
yMX6CYWVPalgXW9FCrhxL+4toJRy5npjkgsLFsknL5/zXbWKFgt69cMwsWJ9Ra57
bonSlI7SoCuHhtw7j+sAlHAlqTOCAVz6P039Y/AGvO6xbC7f+9XftWlbbDcjKFcb
pQilkN9qtkdEH7TLayMAFOsgNvBlwF9+oj9w5PIk3veRTdBXI4GlHjhhzqGZKiRp
oP9HnycHHveyT+C33vuhQso5a3wcUNuvDVOixSqR4kvSt4UVWNK/KmEQmlWU1/m9
ClIwrs8Q79q0xkGaSa0iuG60nvm7tZez9TFkxwIDAQABAoIBAHA5YkppQ7fJSm0D
wNDCHeyABNJWng23IuwZAOXVNxB1bjSOAv8yNgS4zaw/Hx5BnW8yi1lYZb+W0x2u
i5X7g91j0nkyEi5g88kJdFAGTsM5ok0BUwkHsEBjTUPIACanjGjya48lfBP0OGWK
LJU2Acbjda1aeUPFpPDXw/w6bieEthQwroq3DHCMnk6i9bsxgIOXeN04ij9XBmsH
KPCP2hAUnZSlx5febYfHK7/W95aJp22qa//eHS8cKQZCJ0+dQuZwLhlGosTFqLUm
qhPlt/b1EvPPY0cq5rtUc2W31L0YayVEHVOQx1fQIkH2VIUNbAS+bfVy+o6WCRk6
s1XDhsECgYEA30tykVTN5LncY4eQIww2mW8v1j1EG6ngVShN3GuBTuXXaEOB8Duc
yT7yJt1ZhmaJwMk4agmZ1/f/ZXBtfLREGVzVvuwqRZ+LHbqIyhi0wQJA0aezPote
uTQnFn+IveHGtpQNDYGL/UgkexuCxbc2HOZG51JpunCK0TdtVfO/9OUCgYEA1TuS
2WAXzNudRG3xd/4OgtkLD9AvfSvyjw2LkwqCMb3A5UEqw7vubk/xgnRvqrAgJRWo
jndgRrRnikHCavDHBO0GAO/kzrFRfw+e+r4jcLl0Yadke8ndCc7VTnx4wQCrMi5H
7HEeRwaZONoj5PAPyA5X+N/gT0NNDA7KoQT45DsCgYBt+QWa6A5jaNpPNpPZfwlg
9e60cAYcLcUri6cVOOk9h1tYoW7cdy+XueWfGIMf+1460Z90MfhP8ncZaY6yzUGA
0EUBO+Tx10q3wIfgKNzU9hwgZZyU4CUtx668mOEqy4iHoVDwZu4gNyiobPsyDzKa
dxtSkDc8OHNV6RtzKpJOtQKBgFoRGcwbnLH5KYqX7eDDPRnj15pMU2LJx2DJVeU8
ERY1kl7Dke6vWNzbg6WYzPoJ/unrJhFXNyFmXj213QsSvN3FyD1pFvp/R28mB/7d
hVa93vzImdb3wxe7d7n5NYBAag9+IP8sIJ/bl6i9619uTxwvgtUqqzKPuOGY9dnh
oce1AoGBAKZyZc/NVgqV2KgAnnYlcwNn7sRSkM8dcq0/gBMNuSZkfZSuEd4wwUzR
iFlYp23O2nHWggTkzimuBPtD7Kq4jBey3ZkyGye+sAdmnKkOjNILNbpIZlT6gK3z
fBaFmJGRJinKA+BJeH79WFpYN6SBZ/c3s5BusAbEU7kE5eInyazP
-----END RSA PRIVATE KEY-----
- kid: "iv1aShae"
key: |
-----BEGIN EC PRIVATE KEY-----
MHQCAQEEIE8yeUh111Npqu2e5wXxjC/GA5lbGe0j0KVXqZP12vqioAcGBSuBBAAK
oUQDQgAESKfUtKaLqCfhK+p3z870W59yOYvd+kjGWe+tK16SmWzZJbRCgdHakHE5
MC6tJRnvedsYoKTrYoDv/XZIBI9zlA==
-----END EC PRIVATE KEY-----
secrets.keys
The service can use a number of key types for signing. The following key types are supported:
- RSA
- ECDSA with the P-256 (
prime256v1
) curve - ECDSA with the P-384 (
secp384r1
) curve - ECDSA with the K-256 (
secp256k1
) curve
Each entry must have a unique (and arbitrary) kid
, plus the key itself.
The key can either be specified inline (with the key
property), or loaded from a file (with the key_file
property).
The following key formats are supported:
- PKCS#1 PEM or DER-encoded RSA private key
- PKCS#8 PEM or DER-encoded RSA or ECDSA private key, encrypted or not
- SEC1 PEM or DER-encoded ECDSA private key
For PKCS#8 encoded keys, the password
or password_file
properties can be used to decrypt the key.
passwords
Settings related to the local password database
passwords:
# Whether to enable the password database.
# If disabled, users will only be able to log in using upstream OIDC providers
enabled: true
# List of password hashing schemes being used
# /!\ Only change this if you know what you're doing
# TODO: document this section better
schemes:
- version: 1
algorithm: argon2id
policy
Policy settings
policy:
data:
admin_users:
- person1
- person2
# Dynamic Client Registration
client_registration:
# don't require URIs to be on the same host. default: false
allow_host_mismatch: true
# allow non-SSL and localhost URIs. default: false
allow_insecure_uris: true
# Registration using passwords
passwords:
# minimum length of a password. default: ?
min_length: 8
# require at least one lowercase character in a password. default: false
require_lowercase: true
# require at least one uppercase character in a password. default: false
require_uppercase: true
# require at least one number in a password. default: false
require_number: true
telemetry
Settings related to metrics and traces
telemetry:
tracing:
# List of propagators to use for extracting and injecting trace contexts
propagators:
# Propagate according to the W3C Trace Context specification
- tracecontext
# Propagate according to the W3C Baggage specification
- baggage
# Propagate trace context with Jaeger compatible headers
- jaeger
# The default: don't export traces
exporter: none
# Export traces to an OTLP-compatible endpoint
#exporter: otlp
#endpoint: https://localhost:4318
metrics:
# The default: don't export metrics
exporter: none
# Export metrics to an OTLP-compatible endpoint
#exporter: otlp
#endpoint: https://localhost:4317
# Export metrics by exposing a Prometheus endpoint
# This requires mounting the `prometheus` resource to an HTTP listener
#exporter: prometheus
sentry:
# DSN to use for sending errors and crashes to Sentry
dsn: https://public@host:port/1
email
Settings related to sending emails
email:
from: '"The almighty auth service" <auth@example.com>'
reply_to: '"No reply" <no-reply@example.com>'
# Default transport: don't send any emails
transport: blackhole
# Send emails using SMTP
#transport: smtp
#mode: plain | tls | starttls
#hostname: localhost
#port: 587
#username: username
#password: password
# Send emails by calling a local sendmail binary
#transport: sendmail
#command: /usr/sbin/sendmail
# Send emails through the AWS SESv2 API
# This uses the AWS SDK, so the usual AWS environment variables are supported
#transport: aws_ses
upstream_oauth2
Settings related to upstream OAuth 2.0/OIDC providers.
Additions and modifications within this section are synced with the database on server startup.
Removed entries are only removed with the config sync --prune
command.
upstream_oauth2.providers
A list of upstream OAuth 2.0/OIDC providers to use to authenticate users.
Sample configurations for popular providers can be found in the upstream provider setup guide.
upstream_oauth2:
providers:
- # A unique identifier for the provider
# Must be a valid ULID
id: 01HFVBY12TMNTYTBV8W921M5FA
# The issuer URL, which will be used to discover the provider's configuration.
# If discovery is enabled, this *must* exactly match the `issuer` field
# advertised in `<issuer>/.well-known/openid-configuration`.
issuer: https://example.com/
# A human-readable name for the provider,
# which will be displayed on the login page
#human_name: Example
# A brand identifier for the provider, which will be used to display a logo
# on the login page. Values supported by the default template are:
# - `apple`
# - `google`
# - `facebook`
# - `github`
# - `gitlab`
# - `twitter`
#brand_name: google
# The client ID to use to authenticate to the provider
client_id: mas-fb3f0c09c4c23de4
# The client secret to use to authenticate to the provider
# This is only used by the `client_secret_post`, `client_secret_basic`
# and `client_secret_jwk` authentication methods
#client_secret: f4f6bb68a0269264877e9cb23b1856ab
# Which authentication method to use to authenticate to the provider
# Supported methods are:
# - `none`
# - `client_secret_basic`
# - `client_secret_post`
# - `client_secret_jwt`
# - `private_key_jwt` (using the keys defined in the `secrets.keys` section)
token_endpoint_auth_method: client_secret_post
# What signing algorithm to use to sign the authentication request when using
# the `private_key_jwt` or the `client_secret_jwt` authentication methods
#token_endpoint_auth_signing_alg: RS256
# The scopes to request from the provider
# In most cases, it should always include `openid` scope
scope: "openid email profile"
# How the provider configuration and endpoints should be discovered
# Possible values are:
# - `oidc`: discover the provider through OIDC discovery,
# with strict metadata validation (default)
# - `insecure`: discover through OIDC discovery, but skip metadata validation
# - `disabled`: don't discover the provider and use the endpoints below
#discovery_mode: oidc
# Whether PKCE should be used during the authorization code flow.
# Possible values are:
# - `auto`: use PKCE if the provider supports it (default)
# Determined through discovery, and disabled if discovery is disabled
# - `always`: always use PKCE (with the S256 method)
# - `never`: never use PKCE
#pkce_method: auto
# The provider authorization endpoint
# This takes precedence over the discovery mechanism
#authorization_endpoint: https://example.com/oauth2/authorize
# The provider token endpoint
# This takes precedence over the discovery mechanism
#token_endpoint: https://example.com/oauth2/token
# The provider JWKS URI
# This takes precedence over the discovery mechanism
#jwks_uri: https://example.com/oauth2/keys
# How user attributes should be mapped
#
# Most of those attributes have two main properties:
# - `action`: what to do with the attribute. Possible values are:
# - `ignore`: ignore the attribute
# - `suggest`: suggest the attribute to the user, but let them opt out
# - `force`: always import the attribute, and don't fail if it's missing
# - `require`: always import the attribute, and fail if it's missing
# - `template`: a Jinja2 template used to generate the value. In this template,
# the `user` variable is available, which contains the user's attributes
# retrieved from the `id_token` given by the upstream provider.
#
# Each attribute has a default template which follows the well-known OIDC claims.
#
claims_imports:
# The subject is an internal identifier used to link the
# user's provider identity to local accounts.
# By default it uses the `sub` claim as per the OIDC spec,
# which should fit most use cases.
subject:
#template: "{{ user.sub }}"
# The localpart is the local part of the user's Matrix ID.
# For example, on the `example.com` server, if the localpart is `alice`,
# the user's Matrix ID will be `@alice:example.com`.
localpart:
#action: force
#template: "{{ user.preferred_username }}"
# The display name is the user's display name.
displayname:
#action: suggest
#template: "{{ user.name }}"
# An email address to import.
email:
#action: suggest
#template: "{{ user.email }}"
# Whether the email address must be marked as verified.
# Possible values are:
# - `import`: mark the email address as verified if the upstream provider
# has marked it as verified, using the `email_verified` claim.
# This is the default.
# - `always`: mark the email address as verified
# - `never`: mark the email address as not verified
#set_email_verification: import
Using the service
Running
Once the configuration is done, one should run the database migrations (database schema upgrade, also for setup) by running
mas-cli database migrate
The server can then be started by running
mas-cli server
Sep 24 14:42:42.743 INFO mas_cli::server: Starting task scheduler
Sep 24 14:42:42.743 INFO mas_core::templates: Loading builtin templates
Sep 24 14:42:42.752 INFO mas_cli::server: Listening on http://0.0.0.0:8080
The server should now be accessible through http://localhost:8080/.
Note: when running with Docker, the port used by the server should be exposed with the -p
flag:
docker run --rm \
-v `pwd`/config.yaml:/config.yaml \
-p 8080:8080 \
ghcr.io/matrix-org/matrix-authentication-service:main \
server
Registering, logging in and out
Through the interface, users are able to create an account by clicking the Register
button on the top right (or going to /register
).
They can then end their session by clicking the Sign out
button and sign back in.
Playing around with the playground
The OpenID Foundation hosts a OpenID Connect Playground where one can test logging in through an OIDC provider: https://openidconnect.net/
Step 1: Add the client to the server config
Add the following section to the server configuration file config.yaml
:
clients:
# The client ID must be a valid ULID
- client_id: 000000000000OIDCPLAYGROUND
client_secret: verysecret
redirect_uris:
- "https://openidconnect.net/callback"
Sync the configuration with the database:
mas-cli config sync
Step 2: Change the playground configuration
- Navigate to the playground
- Click on "Configuration"
- Server template: Custom
- Paste the discovery document URL found on the service homepage (e.g.
http://localhost:8080/.well-known/openid-configuration
) - Click "Use discovery document" ; it should fill out the authorization, token and token keys endpoints
- Set the OIDC Client ID to
000000000000OIDCPLAYGROUND
and the Client Secret toverysecret
(must match the ones in the configuration) - Click "Save"
Step 3: Run the OpenID Connect flow
Start the flow by clicking the "Start" button. It should redirect the browser to the authentication service. If a user is already logged in, it should redirect back to the playground immediately.
Follow the flow to see the code exchange happen. Note that the last step only works if the service is accessible through the internet.
Command line tool
The command line interface provides subcommands that helps running the service.
Logging
The overall log level of the CLI can be changed via the RUST_LOG
environment variable.
Default log level is info
.
Valid levels from least to most verbose are error
, warn
, info
, debug
and trace
.
Global flags
--config
Sets the configuration file to load. It can be repeated multiple times to merge multiple files together.
Usage: mas-cli [OPTIONS] [COMMAND]
Commands:
config Configuration-related commands
database Manage the database
server Runs the web server
worker Run the worker
manage Manage the instance
templates Templates-related commands
doctor Run diagnostics on the deployment
help Print this message or the help of the given subcommand(s)
Options:
-c, --config <CONFIG> Path to the configuration file
-h, --help Print help
config
Helps to deal with the configuration
config check
Check the validity of configuration files.
$ mas-cli config check --config=config.yaml
INFO mas_cli::config: Configuration file looks good path=["config.yaml"]
config dump
Dump the merged configuration tree.
$ mas-cli config dump --config=first.yaml --config=second.yaml
---
clients:
# ...
config generate
Generate a sample configuration file.
It generates random signing keys (.secrets.keys
) and the cookie encryption secret (.secrets.encryption
).
$ mas-cli config generate > config.yaml
INFO generate: mas_config::oauth2: Generating keys...
INFO generate:rsa: mas_config::oauth2: Done generating RSA key
INFO generate:ecdsa: mas_config::oauth2: Done generating ECDSA key
config sync [--prune] [--dry-run]
Synchronize the configuration with the database.
This will synchronize the clients
and upstream_oauth
sections of the configuration with the database.
By default, it does not delete clients and upstreams that are not in the configuration anymore. Use the --prune
option to do so.
The --dry-run
option will log the changes that would be made, without actually making them.
$ mas-cli config sync --prune --config=config.yaml
INFO cli.config.sync: Syncing providers and clients defined in config to database prune=true dry_run=false
INFO cli.config.sync: Updating provider provider.id=01H3FDH2XZJS8ADKRGWM84PZTY
INFO cli.config.sync: Adding provider provider.id=01H3FDH2XZJS8ADKRGWM84PZTF
INFO cli.config.sync: Deleting client client.id=01GFWRB9MYE0QYK60NZP2YF905
INFO cli.config.sync: Updating client client.id=01GFWRB9MYE0QYK60NZP2YF904
database
Run database-related operations
database migrate
Run the pending database migrations
$ mas-cli database migrate
manage
Includes admin-related subcommands.
manage verify-email <username> <email>
Mark a user email address as verified
server
Runs the authentication service.
$ mas-cli server
INFO mas_cli::server: Starting task scheduler
INFO mas_core::templates: Loading builtin templates
INFO mas_cli::server: Listening on http://0.0.0.0:8080
templates
Helps customizing templates.
templates save <path>
Save the builtin template in the specified folder.
$ mas-cli templates save ./templates
INFO mas_core::templates: Wrote template path="./templates/login.html"
INFO mas_core::templates: Wrote template path="./templates/register.html"
INFO mas_core::templates: Wrote template path="./templates/index.html"
INFO mas_core::templates: Wrote template path="./templates/reauth.html"
INFO mas_core::templates: Wrote template path="./templates/form_post.html"
INFO mas_core::templates: Wrote template path="./templates/error.html"
INFO mas_core::templates: Wrote template path="./templates/base.html"
By default this command won't overwrite existing files, but this behavior can be changed by adding the --overwrite
flag.
templates check <path>
Check the validity of the templates in the specified folder. It compiles the templates and then renders them with different contexts.
$ mas-cli templates check ./templates
INFO mas_core::templates: Loading builtin templates
INFO mas_core::templates: Loading templates from filesystem path=./templates/**/*.{html,txt}
INFO mas_core::templates::check: Rendering template name="login.html" context={"csrf_token":"fake_csrf_token","form":{"fields_errors":{},"form_errors":[],"has_errors":false}}
INFO mas_core::templates::check: Rendering template name="register.html" context={"__UNUSED":null,"csrf_token":"fake_csrf_token"}
INFO mas_core::templates::check: Rendering template name="index.html" context={"csrf_token":"fake_csrf_token","current_session":{"active":true,"created_at":"2021-09-24T13:26:52.962135085Z","id":1,"last_authd_at":"2021-09-24T13:26:52.962135316Z","user_id":2,"username":"john"},"discovery_url":"https://example.com/.well-known/openid-configuration"}
...
Builtin templates are still loaded by default when running this command, but this can be skipped by adding the --skip-builtin
flag.
doctor
Run diagnostics on the live deployment. This tool should help diagnose common issues with the service configuration and deployment.
When running this tool, make sure it runs from the same point-of-view as the service, with the same configuration file and environment variables.
$ mas-cli doctor
Contributing
This document aims to get you started with contributing to the Matrix Authentication Service!
1. Who can contribute to MAS?
Everyone is welcome to contribute code to matrix.org projects, provided that they are willing to license their contributions under the same license as the project itself. We follow a simple 'inbound=outbound' model for contributions: the act of submitting an 'inbound' contribution means that the contributor agrees to license the code under the same terms as the project's overall 'outbound' license - in our case, this is almost always Apache Software License v2 (see LICENSE).
2. What do I need?
To get MAS running locally from source you will need:
3. Get the source
- Clone this repository
4. Build and run MAS
- Build the frontend
cd frontend npm ci npm run build cd ..
- Build the Open Policy Agent policies
cd policies make # OR, if you don't have `opa` installed and want to build through the OPA docker image make DOCKER=1 cd ..
- Generate the sample config via
cargo run -- config generate > config.yaml
- Run a PostgreSQL database locally
docker run -p 5432:5432 -e 'POSTGRES_USER=postgres' -e 'POSTGRES_PASSWORD=postgres' -e 'POSTGRES_DATABASE=postgres' postgres
- Update the database URI in
config.yaml
topostgresql://postgres:postgres@localhost/postgres
- Run the database migrations via
cargo run -- database migrate
- Run the server via
cargo run -- server -c config.yaml
- Go to http://localhost:8080/
5. Learn about MAS
You can learn about the architecture and database of MAS here.
Architecture
The service is meant to be easily embeddable, with only a dependency to a database. It is also meant to stay lightweight in terms of resource usage and easily scalable horizontally.
Scope and goals
The Matrix Authentication Service has been created to support the migration of Matrix to an OpenID Connect (OIDC) based architecture as per MSC3861.
It is not intended to be a general purpose Identity Provider (IdP) and instead focuses on the specific needs of Matrix.
Furthermore, it is only intended that it would speak OIDC for authentication and not other protocols. Instead, if you want to connect to an upstream SAML, CAS or LDAP backend then you need to pair MAS with a separate service (such as Dex or Keycloak) which does that translation for you.
Whilst it only supports use with Synapse today, we hope that other homeservers will become supported in future.
If you need some other feature that MAS doesn't support (such as TOTP or WebAuthn), then you should consider pairing MAS with another IdP that does support the features you need.
Workspace and crate split
The whole repository is a Cargo Workspace that includes multiple crates under the /crates
directory.
This includes:
mas-cli
: Command line utility, main entry pointmas-config
: Configuration parsing and loadingmas-data-model
: Models of objects that live in the database, regardless of the storage backendmas-email
: High-level email sending abstractionmas-handlers
: Main HTTP application logicmas-iana
: Auto-generated enums from IANA registriesmas-iana-codegen
: Code generator for themas-iana
cratemas-jose
: JWT/JWS/JWE/JWK abstractionmas-static-files
: Frontend static files (CSS/JS). Includes some frontend toolingmas-storage
: Abstraction of the storage backendsmas-storage-pg
: Storage backend implementation for a PostgreSQL databasemas-tasks
: Asynchronous task runner and scheduleroauth2-types
: Useful structures and types to deal with OAuth 2.0/OpenID Connect endpoints. This might end up published as a standalone library as it can be useful in other contexts.
Important crates
The project makes use of a few important crates.
Async runtime: tokio
Tokio is the async runtime used by the project. The choice of runtime does not have much impact on most of the code.
It has an impact when:
- spawning asynchronous work (as in "not awaiting on it immediately")
- running CPU-intensive tasks. They should be ran in a blocking context using
tokio::task::spawn_blocking
. This includes password hashing and other crypto operations. - when dealing with shared memory, e.g. mutexes, rwlocks, etc.
Logging: tracing
Logging is handled through the tracing
crate.
It provides a way to emit structured log messages at various levels.
#![allow(unused)] fn main() { use tracing::{info, debug}; info!("Logging some things"); debug!(user = "john", "Structured stuff"); }
tracing
also provides ways to create spans to better understand where a logging message comes from.
In the future, it will help building OpenTelemetry-compatible distributed traces to help with debugging.
tracing
is becoming the standard to log things in Rust.
By itself it will do nothing unless a subscriber is installed to -for example- log the events to the console.
The CLI installs tracing-subcriber
on startup to log in the console.
It looks for a RUST_LOG
environment variable to determine what event should be logged.
Error management: thiserror
/ anyhow
thiserror
helps defining custom error types.
This is especially useful for errors that should be handled in a specific way, while being able to augment underlying errors with additional context.
anyhow
helps dealing with chains of errors.
It allows for quickly adding additional context around an error while it is being propagated.
Both crates work well together and complement each other.
Database interactions: sqlx
Interactions with the database are done through sqlx
, an async, pure-Rust SQL library with compile-time check of queries.
It also handles schema migrations.
Templates: tera
Tera was chosen as template engine for its simplicity as well as its ability to load templates at runtime. The builtin templates are embedded in the final binary through some macro magic.
The downside of Tera compared to compile-time template engines is the possibility of runtime crashes. This can however be somewhat mitigated with unit tests.
Crates from RustCrypto
The RustCrypto team offer high quality, independent crates for dealing with cryptography. The whole project is highly modular and APIs are coherent between crates.
Database
Interactions with the database goes through sqlx
.
It provides async database operations with connection pooling, migrations support and compile-time check of queries through macros.
Writing database interactions
All database interactions are done through repositoriy traits. Each repository trait usually manages one type of data, defined in the mas-data-model
crate.
Defining a new data type and associated repository looks like this:
- Define new structs in
mas-data-model
crate - Define the repository trait in
mas-storage
crate - Make that repository trait available via the
RepositoryAccess
trait inmas-storage
crate - Setup the database schema by writing a migration file in
mas-storage-pg
crate - Implement the new repository trait in
mas-storage-pg
crate - Write tests for the PostgreSQL implementation in
mas-storage-pg
crate
Some of those steps are documented in more details in the mas-storage
and mas-storage-pg
crates.
Compile-time check of queries
To be able to check queries, sqlx
has to introspect the live database.
Usually it does so by having the database available at compile time, but to avoid that we're using the offline
feature of sqlx
, which saves the introspection informatons as a flat file in the repository.
Preparing this flat file is done through sqlx-cli
, and should be done everytime the database schema or the queries changed.
# Install the CLI
cargo install sqlx-cli --no-default-features --features postgres
cd crates/storage-pg/ # Must be in the mas-storage-pg crate folder
export DATABASE_URL=postgresql:///matrix_auth
cargo sqlx prepare
Migrations
Migration files live in the migrations
folder in the mas-core
crate.
cd crates/storage-pg/ # Again, in the mas-storage-pg crate folder
export DATABASE_URL=postgresql:///matrix_auth
cargo sqlx migrate run # Run pending migrations
cargo sqlx migrate add [description] # Add new migration files
Note that migrations are embedded in the final binary and can be run from the service CLI tool.
About Application Services login
Encrypted Application Services/Bridges currently leverage the m.login.application_service
login type to create devices for users.
This API is not available in the Matrix Authentication Service.
We're working on a solution to support this use case, but in the meantime, this means encrypted bridges will not work with the Matrix Authentication Service. A workaround is to disable E2EE support in your bridge setup.