A Mattermost / Mostlymatter plugin that adds OpenID Connect (OIDC) authentication for Mattermost and Mostlymatter without an Enterprise license.
Mattermost restricts generic OIDC authentication to the Enterprise/Professional editions. The free Team Edition only supports GitLab as an SSO provider (and even this only in version 10). This plugin bypasses that limitation by implementing a full OIDC flow as a plugin that works with any standards-compliant OIDC provider.
- Keycloak
- Authentik
- Authelia
- Zitadel
- Dex
- LemonLDAP::NG
- Okta
- Auth0
- Azure AD / Entra ID
- Google Workspace
- Any other OIDC-compliant provider
┌──────────────┐ ┌──────────────────┐ ┌───────────────┐
│ Browser │───▶│ Mattermost │───▶│ OIDC Provider │
│ (Login Page) │◀───│ Mostlymatter │◀───│ (Keycloak…) │
│ │ │ OIDC Plugin │ │ │
└──────────────┘ └──────────────────┘ └───────────────┘
Flow:
- User clicks the OIDC login button
- Plugin redirects to the OIDC provider (Authorization Code Flow)
- User authenticates with the provider
- Provider redirects back to the plugin callback
- Plugin exchanges the code for tokens, verifies the ID token
- Plugin creates/updates the Mattermost user and creates a session
This covers web and desktop clients out of the box. For the native mobile
app, an optional reverse-proxy shim (mobile-bridge/)
is required — see Native mobile app below.
- Go 1.26+
- Node.js 22+ and npm
- Make
- Mattermost/Mostlymatter Server v9.0+
# Clone the repository
git clone <this-repo>
cd mattermost-oidc-plugin
# Build the plugin (server + webapp + bundle)
make all
# The bundle will be at:
# dist/mattermost-oidc-0.1.0.tar.gzmake server # Go binaries for all platforms
make webapp # Webpack bundle# Run Go tests with race detection
make test
# Go linting (requires golangci-lint)
make lint- Go to System Console → Plugin Management
- Click Upload Plugin
- Download the latest
.tar.gzfrom the Releases page - Enable the plugin
export MM_SERVICESETTINGS_SITEURL=https://chat.example.com
export MM_ADMIN_TOKEN=your-admin-token
make deployCreate a new OIDC application/client with your provider using the following settings:
| Setting | Value |
|---|---|
| Client Type | Confidential |
| Grant Type | Authorization Code |
| Redirect URI | https://chat.example.com/plugins/mattermost-oidc/oauth2/callback |
| Scopes | openid profile email |
Go to System Console → Plugins → OIDC Authentication:
| Field | Description | Example |
|---|---|---|
| Enable | Enable the plugin | true |
| Issuer URL | OIDC Issuer URL | https://idp.example.com/application/o/mostlymatter/ |
| Client ID | Client ID from your provider | mostlymatter |
| Client Secret | Client secret from your provider | secret123 |
| Scopes | Requested scopes | openid profile email |
| Button Text | Text on the login button | Log in with SSO |
| Button Color | Color of the login button | #0058CC |
| Username Claim | OIDC claim for username | preferred_username |
| Email Claim | OIDC claim for email | email |
| Auto-Create Accounts | Automatically create new accounts | true |
| Default Team | Team slug for new users | main |
After saving, the Mattermost / Mostlymatter server must be restarted.
- Create a new client
- Client ID: mostlymatter
- Client Protocol: openid-connect
- Access Type: confidential
- Valid Redirect URIs: https://chat.example.com/plugins/mattermost-oidc/oauth2/callback
- Ensure client scopes are assigned:
openid,profile,emailmust be assigned
- Discovery Endpoint: https://keycloak.example.com/realms/myrealm/.well-known/openid-configuration
- Create an OAuth2/OpenID Provider
- Name: Mostlymatter
- Authorization flow:
default-provider-authorization-explicit-consent - Redirect URIs: https://chat.example.com/plugins/mattermost-oidc/oauth2/callback
- Scopes:
openid,profile,email - Signing key: select a valid certificate (may be self-signed)
- Create an application and assign the provider
- Discovery Endpoint: https://sso.example.com/application/o/mostlymatter/.well-known/openid-configuration
Note that Authentik doesn't have a family_name claim. Mattermost optimistically skips parsing the family name if not present.
mattermost-oidc-plugin/
├── plugin.json # Plugin manifest and settings schema
├── Makefile # Build system
├── README.md # This file
├── assets/
│ └── oidc-icon.svg # Plugin icon
├── server/
│ ├── go.mod # Go module definition
│ ├── main.go # Entry point
│ ├── plugin.go # Plugin struct, lifecycle, router
│ ├── configuration.go # Configuration type and validation
│ └── oauth2.go # OIDC/OAuth2 flow handlers
├── webapp/
│ ├── package.json # Node dependencies
│ ├── webpack.config.js # Webpack configuration
│ └── src/
│ └── index.js # Login button React component
└── mobile-bridge/ # Optional reverse-proxy shim for native mobile app
├── main.go # Shim: client-config injection + mobile_login redirect
├── Dockerfile # Container image for the shim
└── README.md # Ingress wiring, env vars, test plan, troubleshooting
- HMAC-signed state parameters prevent CSRF attacks
- State tokens are stored in the KV store with an expiry time
- ID token verification via the provider's JWKS
- No client secret is sent to the browser
- Secure cookies are set when HTTPS is configured
- Username sanitization prevents injection attacks
This plugin creates sessions directly after a successful OIDC flow and does not evaluate Mattermost's built-in MFA. If users have Mattermost-level MFA configured, it will be bypassed when logging in via OIDC.
Recommendation: Enforce MFA at the OIDC provider level instead (Keycloak, Authentik, Authelia, etc. all support this). This is the more robust approach — MFA is enforced for every login regardless of which client or plugin is used.
The plugin's login button only renders in the web and desktop clients. The
native Mattermost mobile app hardcodes its SSO to the core OpenID flow: it
shows an OpenID button only when the client config advertises it, and always
posts the flow to the core endpoint /oauth/openid/mobile_login — neither of
which a plugin can influence.
To bridge this, the repository ships an optional reverse-proxy shim in
mobile-bridge/. Placed in front of Mattermost in your
ingress, it intercepts exactly two paths for mobile clients and forwards
everything else (REST, WebSocket, files, /plugins/...) untouched:
mobile app ──▶ ingress ──▶ /api/v4/config/client → bridge (inject EnableSignUpWithOpenId=true, mobile UA only)
/oauth/openid/mobile_login → bridge (302 → plugin connect with mobile_redirect)
everything else → Mattermost
The plugin side handles the mobile callback by handing the session token back to
the app via its custom URL scheme (mmauth://callback?MMAUTHTOKEN=…&MMCSRF=…),
mirroring Mattermost core's mobile_login flow. Web and desktop clients are
completely unaffected.
"OIDC provider not initialized" → Check the discovery endpoint, review server logs
"Authentication session expired" → Ensure that the clocks of Mattermost / Mostlymatter and the OIDC provider are synchronized
"email claim is empty"
→ Check that the OIDC provider returns the email scope and that the claim name is correct
Login button not visible → Enable the plugin in the System Console, clear browser cache
"failed to create user" → Check that auto-create is enabled and that the username doesn't already exist
The repository includes a docker-compose.dev.yml with Mattermost / Mostlymatter and Keycloak as an OIDC provider:
# Start the environment
docker-compose -f docker-compose.dev.yml up -d
# Mattermost: http://localhost:8065
# Keycloak: http://localhost:8080 (Admin: admin / admin)
# Stop the environment
docker-compose -f docker-compose.dev.yml downAfter starting Mattermost / Mostlymatter: create an admin account, enable plugin uploads under System Console → Plugin Management, then upload the built bundle.
export MM_SERVICESETTINGS_SITEURL=http://localhost:8065
export MM_ADMIN_TOKEN=your-token
# Build and deploy the plugin
make deploy
# Or use watch mode for the webapp:
cd webapp && npm run devmake release
# Current version: 0.1.0
# New version (without v): 1.0.0
# Tagged v1.0.0 — push with: git push && git push origin v1.0.0Updates plugin.json and webapp/package.json, commits the version bump, creates an annotated git tag, and prints the push command. The CI/CD pipeline then builds and publishes the release automatically.
The repository uses GitHub Actions (.github/workflows/ci.yml):
| Job | Trigger | Description |
|---|---|---|
test-server |
every push / PR | Go tests with race detection and coverage |
lint-server |
every push / PR | golangci-lint (non-blocking) |
build-server |
every push / PR | Cross-compile for linux/darwin × amd64/arm64 |
build-webapp |
every push / PR | npm ci && webpack production build |
build-bundle |
every push / PR | Package all artifacts into .tar.gz |
release |
v* tags only |
Publish GitHub Release with the bundle as release asset |
The pipeline runs automatically on every push and pull request. To cut a release, use make release and push the resulting tag.
Apache License 2.0 — compatible with Mattermost and Mostlymatter.
This project is an independent community plugin and is not affiliated with, endorsed by, or officially connected to Mattermost, Inc. or the Mostlymatter project. "Mattermost" is a trademark of Mattermost, Inc.