- Go 60.4%
- TypeScript 21.7%
- CSS 16.1%
- templ 1.2%
- Dockerfile 0.6%
| cmd | ||
| docs | ||
| internal | ||
| web | ||
| .dockerignore | ||
| .gitignore | ||
| CLAUDE.md | ||
| Dockerfile | ||
| Dockerfile.demo | ||
| go.mod | ||
| go.sum | ||
| matoidc | ||
| mise.toml | ||
| README.md | ||
matOIDC is an oAuth2 broker to prove Matrix identity without granting access. To an application it looks like an ordinary OpenID Connect provider. Behind that, it owns no users and just authenticates people against their own Matrix homeserver, whichever that is. A service adds one OIDC integration and gets federated Matrix login without being Matrix-aware.
Want to see how it works? Click the login button at the top of this readme.
The security property
The user's Matrix credentials and Matrix access token never touch any backend, not the relying party's, and not matOIDC's.
The login UI is a browser-side application. Credentials go only from your browser to your homeserver. The only thing that reaches the matOIDC backend is a Matrix OpenID token, a single use key proving the user's MXID.
Display name and avatar are self-asserted profile data, fetched in the browser and sanitised server-side before entering an ID token.
How a login flows
- The RP redirects to
/authorize(standard OIDC code flow). - The user names their homeserver
- The browser authenticates directly with the homeserver, with the Matrix oAuth 2.0 API on modern servers, or legacy
/loginwith password/SSO. - The browser mints an OpenID token and posts it, including optional profile data to matOIDC.
- matOIDC resolves the homeserver's federation endpoint and exchanges the token.
- After giving consent, the user follows the redirect back to the RP with an authorization code.
Running
go build ./cmd/matoidc
./matoidc -listen :8080 -issuer https://id.example.com -db matoidc.db
cd web/app && bun install && bun run build
Container
A pre-built image is published to forgejo.perny.dev/perny/matoidc.
docker run -p 8080:8080 -v matoidc-data:/data \
-e MATOIDC_ISSUER=https://id.example.com \
forgejo.perny.dev/perny/matoidc:latest
The image listens on 8080, stores the persistent data at /data.
Or with Docker Compose:
services:
matoidc:
image: forgejo.perny.dev/perny/matoidc:latest
ports:
- "8080:8080"
volumes:
- matoidc-data:/data
environment:
MATOIDC_ISSUER: https://id.example.com
# MATOIDC_REGISTRATION_TOKEN: change-me
volumes:
matoidc-data:
Flags / environment:
| Flag | Env | Default | |
|---|---|---|---|
-listen |
:8080 |
listen address | |
-issuer |
MATOIDC_ISSUER |
http://localhost:8080 |
public issuer URL, no trailing slash |
-db |
MATOIDC_DB |
matoidc.db |
SQLite path (WAL) |
-static |
MATOIDC_STATIC |
web/static |
built browser app |
-registration-token |
MATOIDC_REGISTRATION_TOKEN |
(open) | if set, required as a Bearer token on /register |
Registering a relying party
Dynamic registration per RFC 7591:
curl -X POST https://id.example.com/register \
-H 'Content-Type: application/json' \
-d '{
"redirect_uris": ["https://app.example.org/callback"],
"client_name": "My App",
"homeserver_allowlist": ["example.com"]
}'
homeserver_allowlist is optional: empty means any homeserver is accepted. Non-empty restricts logins to exactly those server names. Redirect URIs must be https:// (or http:// loopback for native apps).
Then it's plain OIDC: discovery at /.well-known/openid-configuration, response_type=code with PKCE S256, RS256 ID tokens. Claims:
subis the full MXID (@alice:example.com)matrix_user_id,matrix_server_name,preferred_usernamename,picture(data URI) with theprofilescope, when the user has them
Developing
go test ./...
templ generate
cd web/app && bunx tsc --noEmit
cd web/app && bun run build
The matOIDC logo and the "Log in with Matrix" badge are based on the Matrix logo, a trademark of The Matrix.org Foundation. Their use does not imply endorsement or affiliation.