Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@

# Builds
build/*
site/

# Docker persistent data
volumes/*
Expand Down
2 changes: 1 addition & 1 deletion .prettierignore
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ pnpm-lock.yaml

# Ignore artifacts
phpmyfaq/assets/public/
site/

# Ignore PNPM Cache
.pnpm-store

1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ This is a log of major user-visible changes in each phpMyFAQ release.
- added optional Redis support for configuration caching (Thorsten)
- added LDAP configuration frontend (Thorsten)
- added phpMyFAQ recent news widget to the admin dashboard (Thorsten)
- added experimental support for Keycloak (Thorsten)
- added experimental support for API key authentication via OAuth2 (Thorsten)
- added experimental per-tenant quota enforcement and API request rate limits (Thorsten)
- added SBOM (Software Bill of Materials) generation (Thorsten)
Expand Down
1 change: 1 addition & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,7 @@
"test:coverage": "./phpmyfaq/src/libs/bin/phpunit --coverage-text",
"test:coverage-html": "./phpmyfaq/src/libs/bin/phpunit --coverage-html coverage",
"bench": "./phpmyfaq/src/libs/bin/phpbench run tests/Benchmarks --report=aggregate",
"docs:build": "mkdocs build --strict",
"version:get": "php scripts/get-version.php",
"version:sync": "pnpm version $(php scripts/get-version.php) --no-git-tag-version --allow-same-version",
"version:check": "php scripts/check-version.php"
Expand Down
81 changes: 77 additions & 4 deletions docs/administration.md
Original file line number Diff line number Diff line change
Expand Up @@ -499,7 +499,7 @@ Navigate to the **Configuration → Translation** tab to configure your translat
- **Amazon**: AWS Access Key ID, Secret Access Key, and region
- **LibreTranslate**: Server URL and optional API key

For detailed setup instructions for each provider, see the [AI Translation Guide](9-ai-translation.md).
For detailed setup instructions for each provider, see the [AI Translation Guide](ai-translation.md).

#### 5.2.13.3 Translating Content

Expand Down Expand Up @@ -596,7 +596,7 @@ The AI will translate:
- Re-translate if formatting is broken

For comprehensive documentation, see:
- [Complete AI Translation Guide](9-ai-translation.md) - Full documentation
- [Complete AI Translation Guide](ai-translation.md) - Full documentation

## 5.3 Statistics

Expand Down Expand Up @@ -791,7 +791,7 @@ To back up the whole data located on your web server, you can run our simple bac
Here you can edit the general, FAQ specific, search, spam protection, spam control center, SEO related, layout
settings, Mail setup for SMTP, API settings, online update settings, and if enabled, LDAP configuration of phpMyFAQ.

You can find a detailed description of all settings in the [Configuration key reference](8-configuration.md).
You can find a detailed description of all settings in the [Configuration key reference](configuration.md).

### 5.6.2 FAQ Multi-sites

Expand Down Expand Up @@ -871,7 +871,80 @@ On this page, phpMyFAQ displays some relevant system information like PHP versio
Please use this information when reporting bugs. Additionally, you can check the status of all translation files and see
if there are any missing translations.

## 5.7 Using Microsoft Entra ID
## 5.7 Using external identity providers

phpMyFAQ can integrate with external identity providers for administrator and frontend logins.

### 5.7.1 Using Keycloak

Keycloak support uses OpenID Connect Authorization Code flow with PKCE.
You can enable it in the administration under `Configuration` -> `Security` -> `Keycloak`.
For a worked configuration example, see the dedicated [Keycloak Integration guide](keycloak.md).

Recommended Keycloak client settings:

- Client type: confidential
- Standard flow enabled
- Direct access grants disabled unless you need them for other tools
- PKCE code challenge method: `S256`
- Root URL: `https://faq.example.com/`
- Home URL: `https://faq.example.com/`
- Valid redirect URI: `https://faq.example.com/auth/keycloak/callback`
- Valid post logout redirect URI: `https://faq.example.com/`
- Web origin: `https://faq.example.com`

Minimum phpMyFAQ configuration:

1. Enable `Keycloak sign-in`
2. Set the `Keycloak base URL`, for example `https://sso.example.com`
3. Set the `Realm`
4. Set the `Client ID`
5. Set the `Client secret`
6. Set the `Redirect URI` to your phpMyFAQ callback URL
7. Keep `Scopes` at least on `openid profile email`
8. Optionally set `Logout redirect URL` to the page users should see after provider logout

Example phpMyFAQ values for a production setup:

- `keycloak.baseUrl`: `https://sso.example.com`
- `keycloak.realm`: `faq`
- `keycloak.clientId`: `phpmyfaq`
- `keycloak.redirectUri`: `https://faq.example.com/auth/keycloak/callback`
- `keycloak.scopes`: `openid profile email`
- `keycloak.logoutRedirectUrl`: `https://faq.example.com/`

Optional settings:

- Enable automatic provisioning if phpMyFAQ should create local users on first successful Keycloak login
- Enable automatic group assignment if phpMyFAQ should assign local groups from Keycloak roles
- Enable group synchronization on login if phpMyFAQ should remove stale memberships for mapped Keycloak groups
- Add a JSON role-to-group mapping if Keycloak role names should map to different phpMyFAQ group names
- Set a logout redirect URL if users should return to a specific page after provider logout
- Use a JSON mapping such as `{"admin":"Administrators","faq-editors":"Editors"}` if Keycloak role names and phpMyFAQ group names differ

phpMyFAQ resolves users in this order:

1. existing user linked by stored Keycloak subject (`sub`)
2. preferred username from Keycloak
3. existing user by email address
4. automatic provisioning if enabled

If automatic provisioning is disabled, users must already exist in phpMyFAQ before they can sign in with Keycloak.

Group assignment behavior:

- only roles listed in the JSON mapping are managed by phpMyFAQ
- matched groups are added to the user on login
- if group synchronization on login is enabled, stale memberships for mapped groups are removed during login
- phpMyFAQ groups outside the configured Keycloak mapping are left untouched

Troubleshooting:

- If login works but logout does not return to phpMyFAQ, verify `Valid post logout redirect URI` in Keycloak and `keycloak.logoutRedirectUrl` in phpMyFAQ
- If users are created but not added to groups, make sure permission level `medium` is enabled and the Keycloak roles actually match your JSON mapping keys
- If an existing user cannot log in, check whether the stored Keycloak subject (`sub`) is already linked to a different account

### 5.7.2 Using Microsoft Entra ID

You can use our experimental Microsoft Entra ID support for user authentication as well.
App Registrations in Azure are used to integrate applications with Microsoft Azure services,
Expand Down
12 changes: 12 additions & 0 deletions docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,18 @@ each setting controls. These values are set during installation and can be chang
| `security.enableRegistration` | Enable registration for visitors | `true` | Allows new users to register an account on the FAQ. |
| `security.domainWhiteListForRegistrations` | Allowed hosts for registrations | *(empty)* | A list of allowed email domains for new registrations. Leave empty to allow all domains. |
| `security.enableSignInWithMicrosoft` | Enable Sign in with Microsoft Entra ID | `false` | Enables authentication via Microsoft Entra ID (formerly Azure AD). |
| `keycloak.enable` | Enable Keycloak sign-in | `false` | Enables OpenID Connect authentication via Keycloak for the frontend and admin login forms. |
| `keycloak.baseUrl` | Keycloak base URL | *(empty)* | Base URL of the Keycloak server, for example `https://sso.example.com`. |
| `keycloak.realm` | Realm | *(empty)* | Keycloak realm used for phpMyFAQ authentication. |
| `keycloak.clientId` | Client ID | *(empty)* | OIDC client identifier configured in Keycloak. |
| `keycloak.clientSecret` | Client secret | *(empty)* | Client secret configured for the Keycloak OIDC client. |
| `keycloak.redirectUri` | Redirect URI | *(empty)* | Callback URL registered in the Keycloak client, usually `https://faq.example.com/auth/keycloak/callback`. |
| `keycloak.scopes` | Scopes | `openid profile email` | Space-separated scopes requested during login. |
| `keycloak.autoProvision` | Automatically create phpMyFAQ users on first Keycloak login | `false` | When enabled, phpMyFAQ creates a local user automatically if no matching account exists yet. |
| `keycloak.groupAutoAssign` | Automatically assign phpMyFAQ groups from Keycloak roles | `false` | When enabled and permission level `medium` is active, phpMyFAQ assigns users to groups derived from Keycloak roles on login. |
| `keycloak.groupSyncOnLogin` | Synchronize mapped phpMyFAQ groups on login | `false` | When enabled, phpMyFAQ also removes stale memberships for groups managed by the Keycloak role mapping during login. |
| `keycloak.groupMapping` | Role to group mapping | *(empty)* | JSON object mapping Keycloak role names to phpMyFAQ group names, for example `{"admin":"Administrators","faq-editors":"Editors"}`. Only mapped roles are managed for assignment and synchronization. |
| `keycloak.logoutRedirectUrl` | Logout redirect URL | *(empty)* | URL users should be redirected to after logging out from Keycloak, for example `https://faq.example.com/`. |
| `security.enableGoogleReCaptchaV2` | Enable Invisible Google ReCAPTCHA v2 | `false` | Enables Google reCAPTCHA v2 to protect forms from spam and abuse. |
| `security.googleReCaptchaV2SiteKey` | Google ReCAPTCHA v2 site key | *(empty)* | The site key from your Google reCAPTCHA v2 registration. |
| `security.googleReCaptchaV2SecretKey` | Google ReCAPTCHA v2 secret key | *(empty)* | The secret key from your Google reCAPTCHA v2 registration. |
Expand Down
2 changes: 1 addition & 1 deletion docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ phpMyFAQ also supports two-factor authentication (2FA) for enhanced security.
An AI-assisted translation feature helps translate content into multiple languages using Google Cloud Translation, DeepL,
Azure Translator, Amazon Translate, or LibreTranslate.
A REST API is available for integration with other systems.
It also supports OpenLDAP, Microsoft Active Directory, Microsoft Entra ID, and an MCP Server for AI agents.
It also supports OpenLDAP, Microsoft Active Directory, Microsoft Entra ID, Keycloak, and an MCP Server for AI agents.
The system is easy to install, thanks to its user-friendly installation script.

phpMyFAQ is versatile
Expand Down
127 changes: 127 additions & 0 deletions docs/keycloak.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
# Keycloak Integration

phpMyFAQ supports Keycloak as an OpenID Connect provider for frontend and administration logins.
The integration uses Authorization Code flow with PKCE.

## 1. Prerequisites

Before you configure phpMyFAQ, make sure you have:

- a reachable Keycloak server, for example `https://sso.example.com`
- a realm for phpMyFAQ users, for example `faq`
- a confidential client for phpMyFAQ
- the public URL of your phpMyFAQ installation, for example `https://faq.example.com/`

## 2. Recommended Keycloak client settings

Recommended client settings for a phpMyFAQ installation at `https://faq.example.com/`:

- Client type: confidential
- Standard flow enabled
- Direct access grants are disabled unless required for another integration
- Service accounts disabled
- PKCE code challenge method: `S256`
- Root URL: `https://faq.example.com/`
- Home URL: `https://faq.example.com/`
- Valid redirect URIs:
- `https://faq.example.com/auth/keycloak/callback`
- Valid post logout redirect URIs:
- `https://faq.example.com/`
- Web origins:
- `https://faq.example.com`

## 3. phpMyFAQ configuration

In phpMyFAQ, open:

- `Configuration`
- `Security`
- `Keycloak`

Typical values:

- `keycloak.enable`: `true`
- `keycloak.baseUrl`: `https://sso.example.com`
- `keycloak.realm`: `faq`
- `keycloak.clientId`: `phpmyfaq`
- `keycloak.clientSecret`: `<client secret from Keycloak>`
- `keycloak.redirectUri`: `https://faq.example.com/auth/keycloak/callback`
- `keycloak.scopes`: `openid profile email`
- `keycloak.logoutRedirectUrl`: `https://faq.example.com/`

Optional user and group settings:

- `keycloak.autoProvision`: `true`
- `keycloak.groupAutoAssign`: `true`
- `keycloak.groupSyncOnLogin`: `true`
- `keycloak.groupMapping`: `{"admin":"Administrators","faq-editors":"Editors"}`

## 4. User resolution and account linking

phpMyFAQ resolves Keycloak users in this order:

1. existing user linked by stored Keycloak subject (`sub`)
2. preferred username from Keycloak
3. existing user by email address
4. automatic provisioning, if enabled

The stored Keycloak subject is the durable link between a local phpMyFAQ account and the external identity.

If automatic provisioning is disabled, users must already exist in phpMyFAQ before they can sign in.

## 5. Group mapping behavior

Group handling is intentionally conservative:

- only roles listed in `keycloak.groupMapping` are managed by phpMyFAQ
- mapped groups are added on login when `keycloak.groupAutoAssign` is enabled
- stale memberships are removed only for mapped groups when `keycloak.groupSyncOnLogin` is enabled
- phpMyFAQ groups outside the configured mapping are left untouched

Example mapping:

```json
{
"admin": "Administrators",
"faq-editors": "Editors"
}
```

This means:

- Keycloak role `admin` maps to phpMyFAQ group `Administrators`
- Keycloak role `faq-editors` maps to phpMyFAQ group `Editors`

## 6. Logout behavior

phpMyFAQ logs the user out locally and then redirects to Keycloak logout when:

- Keycloak sign-in is enabled
- the current user is authenticated through Keycloak

For a reliable provider logout:

- set `keycloak.logoutRedirectUrl` in phpMyFAQ
- make sure the same URL is listed as a valid post-logout redirect URI in Keycloak

## 7. Troubleshooting

If login works but logout does not return to phpMyFAQ:

- verify `keycloak.logoutRedirectUrl`
- verify the matching valid post-logout redirect URI in Keycloak

If users are created but not assigned to groups:

- verify permission level `medium`
- verify `keycloak.groupAutoAssign` is enabled
- verify the Keycloak role names exactly match the JSON mapping keys

If group synchronization removes the wrong memberships:

- check `keycloak.groupMapping`
- remember that only mapped groups are managed

If an existing user cannot log in:

- check whether the stored Keycloak subject (`sub`) is already linked to another local account
1 change: 1 addition & 0 deletions eslint.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ const ignoresConfig = globalIgnores([
'phpmyfaq/assets/public/*',
'phpmyfaq/content/upgrades/*',
'phpmyfaq/src/libs/*',
'site/*',
'volumes/*',
]);

Expand Down
16 changes: 10 additions & 6 deletions mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ site_name: phpMyFAQ v4.2
theme:
name: readthedocs
highlightjs: true
not_in_nav: |
/s3-storage-checklist.md
/keys/**
extra_css:
- css/custom.css
plugins:
Expand All @@ -15,9 +18,10 @@ nav:
- 6. Use phpMyFAQ: 'usage.md'
- 7. Manage phpMyFAQ: 'administration.md'
- 8. Configuration reference: 'configuration.md'
- 9. AI-Assisted Translation feature: 'ai-translation.md'
- 10. Developer documentation: 'development.md'
- 11. Plugins: 'plugins.md'
- 12. MCP Server: 'mcp-server.md'
- 13. Release: 'release.md'
- 14. Thank you!: 'thank-you.md'
- 9. Keycloak Integration: 'keycloak.md'
- 10. AI-Assisted Translation feature: 'ai-translation.md'
- 11. Developer documentation: 'development.md'
- 12. Plugins: 'plugins.md'
- 13. MCP Server: 'mcp-server.md'
- 14. Release: 'release.md'
- 15. Thank you!: 'thank-you.md'
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
"release:artifacts": "./scripts/prepare-release-artifacts.sh",
"release:sign": "./scripts/sign-release-artifacts.sh",
"sbom": "./scripts/generate-sbom.sh",
"docs:build": "mkdocs build --strict",
"eslint": "eslint .",
"lint": "prettier --check .",
"lint:fix": "prettier --write .",
Expand Down
Loading
Loading