Mantis plugin that adds OAuth2 / OIDC login alongside the standard username+password form. Tested with Keycloak.
- MantisBT 2.x
- PHP 7.4+
allow_url_fopen = Oninphp.ini(used to fetch the OIDC discovery document and exchange tokens)
- Copy the
ImaticOAuth2/directory tomantis/plugins/. - Go to Manage → Plugins in Mantis and install ImaticOAuth2.
- Add provider configuration to
mantis/config/config_inc.php(see below).
Add to config/config_inc.php — never commit secrets to version control:
$g_imatic_oauth2_providers = [
'keycloak' => [
'discovery_url' => 'https://keycloak.example.com/realms/myrealm/.well-known/openid-configuration',
'client_id' => 'mantis',
'client_secret' => 'REPLACE_WITH_SECRET',
'label' => 'Sign in with Keycloak',
'enabled' => true,
// 'public_url' => 'http://localhost:8080', // only needed when discovery_url uses an internal Docker hostname
],
];Multiple providers are supported — add more keys to the array.
When running Mantis and Keycloak in Docker, PHP reaches Keycloak via an internal hostname (e.g. keycloak:8080) but the browser cannot. Set public_url to the browser-accessible base URL of the provider:
'discovery_url' => 'http://keycloak:8080/realms/myrealm/.well-known/openid-configuration',
'public_url' => 'http://localhost:8080',The plugin substitutes the internal hostname with public_url only for browser redirects (authorization and end_session). Token exchange stays on the internal network.
In Keycloak admin:
- Valid redirect URIs:
https://your-mantis.example.com/plugin.php?page=ImaticOAuth2/callback - Valid post-logout redirect URIs:
https://your-mantis.example.com/* - Standard flow enabled, PKCE supported
1. User clicks "Sign in with Keycloak" on the login page
2. Plugin generates PKCE code_verifier + state, stores them in PHP session
3. Browser is redirected to Keycloak's authorization_endpoint
4. Keycloak authenticates the user and redirects back with ?code=...&state=...
5. Plugin verifies state, exchanges code for tokens (PKCE)
6. id_token payload is decoded to get email + sub (no extra userinfo request)
7. Mantis user is resolved by sub, then by email (auto-link), then error
8. auth_login_user() creates a standard Mantis session
9. Browser is redirected to the Mantis home page
1. User clicks logout
2. JS intercept rewrites the logout link to the plugin logout page
(only active when the OAuth session cookie is present)
3. Plugin reads the provider from the session cookie, calls auth_logout()
4. Browser is redirected to the provider's end_session_endpoint
5. Provider clears its SSO session and redirects back to the Mantis login page
Standard Mantis username/password sessions are not affected — the JS intercept
only fires when the imatic_oauth2_session cookie is set.
The plugin never creates Mantis users automatically (configurable). On first OAuth login it matches the incoming identity to an existing Mantis account:
- By stored identity —
imatic_oauth2_identities.subject(stable across email changes) - By email — if
auto_link_by_email = true(default) and the email from the token matches a Mantis user email
Once linked, the identity record is reused on subsequent logins. Admins can unlink identities via Manage → OAuth2 Identities.
CREATE TABLE {imatic_oauth2_identities} (
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
user_id INT UNSIGNED NOT NULL,
provider VARCHAR(50) NOT NULL,
subject VARCHAR(255) NOT NULL, -- OAuth2 "sub" claim
email VARCHAR(255),
linked_at DATETIME NOT NULL,
last_used DATETIME,
UNIQUE KEY uq_provider_subject (provider, subject),
INDEX idx_user_id (user_id)
);The table is created automatically on plugin install via the Mantis schema system.
- PKCE (S256) is required on every flow — protects against authorization code interception.
- State parameter is verified on every callback — CSRF protection.
client_secretis read fromconfig_inc.phpand never stored in the plugin files.sub(not email) is used as the stable identity key — email changes in the provider do not break the link.- Logging in via OAuth never creates a new Mantis user unless explicitly implemented.
- Logout redirects to the provider's
end_session_endpointso the SSO session is also cleared.
ImaticOAuth2/
├── ImaticOAuth2.php — plugin registration, hooks, JS injection
├── ImaticOAuth2Core.php — OIDC discovery, token exchange, user resolution
├── pages/
│ ├── callback.php — handles ?action=start and the provider callback
│ ├── logout.php — Mantis logout + provider end_session redirect
│ └── admin.php — admin UI: list and unlink identities
├── config/
│ └── config.php — configuration reference (no runtime logic)
├── js/
│ └── index.js — webpack source
├── files/
│ └── index.js — built bundle served by plugin_file()
└── webpack.config.js
Any provider that exposes an OIDC discovery document works. Only Keycloak has been tested.
'microsoft' => [
'discovery_url' => 'https://login.microsoftonline.com/{tenant-id}/v2.0/.well-known/openid-configuration',
'client_id' => 'YOUR_APP_ID',
'client_secret' => 'REPLACE_WITH_SECRET',
'label' => 'Sign in with Microsoft',
'enabled' => true,
],Register a redirect URI in Azure: https://your-mantis.example.com/plugin.php?page=ImaticOAuth2/callback