
Documentation
TYPO3 Module “Single-Sign-On”
Version 2.0.3 (04 Nov 2026)
Chapters of this Documentation
There are three configuration sources, and which one a key belongs in is not a matter of taste:
| Source | Scope | Contains |
|---|---|---|
| Extension configuration | both | The two on/off switches and where the backend configuration file lives. See Extension Settings |
| Service provider configuration file | both | Everything the SAML toolkit itself needs, plus the backend’s own TYPO3 settings |
Site configuration, settings.sso_authentication | frontend only | The frontend’s TYPO3 settings, and the pointer to its configuration file |
The backend has no site context at login time, which is why it reads TYPO3-specific settings from its configuration file while the frontend reads them from the site configuration. The same key therefore lives in two different places depending on scope, and this chapter states which for every one of them.
The Configuration File
BackendServiceProviderConfigurationFile for the backend, the site setting metadata for the frontend. Three forms are accepted:
| Form | Read As |
|---|---|
EXT:my_ext/Configuration/sso.yaml | YAML. %env(...)% placeholders are resolved. Everything below is available |
EXT:my_ext/Configuration/idp.xml | Plain identity provider metadata. Only idp, strict: true, debug: false and security.requestedAuthnContext: false come out of it—there is no place for a TYPO3 setting, so enforceSSO and allowEmailAsUsername cannot be used |
idp.example.com/saml/metadata | Fetched and parsed as identity provider metadata, same limitation as XML |
A path that is neither EXT:-prefixed nor a URL is treated as a URL and fails.
YAML files may nest everything under a single sso_typo3: root key, which is then unwrapped. Without that key the file is read as-is.
idp is required in a YAML file and has to be a string. It is either the metadata URL of the identity provider—fetched and parsed on use—or any other string. An inline idp: block in the toolkit’s own nested notation is not supported here and fails; see Known Limits.
Keys Read by This Extension
Everything in this table is read by the extension itself. Where a key exists in both scopes, the two locations are given.
| Key | Default | Where | Effect |
|---|---|---|---|
enabled | (none) | Site config only | Required, and required to be true, or frontend SSO is off for this site. There is no equivalent for the backend—enableBE and the presence of a configuration file decide there |
metadata | (none) | Site config only | Required for the frontend: the configuration file described above |
enforceSSO | false | Config file (backend) · site config (frontend) | On, SSO becomes the only accepted login for that scope: the password fields disappear and a posted password is rejected during authentication. See Backend Login and Frontend Login |
entityId | The backend entry point URL · the site base | Config file (backend) · site config (frontend) | The service provider identity registered at the identity provider. A value without a scheme is replaced by BackendBaseUrl |
acsUrl | <entry point>auth/sso/acs · /_auth/sso/acs | Config file (backend) · site config (frontend) | Where the identity provider posts its assertion. A path is resolved against the base URL. An overridden value is used as-is and still recognized even when it does not follow the /auth/sso convention |
metadata.url | (empty) | Config file, backend only | Path of the service provider metadata endpoint. Matched as a substring of the request path, which is why the shipped example configures /auth/sso/metadata for an endpoint served at /typo3/auth/sso/metadata. Keep it specific enough not to match unrelated backend routes. Empty disables the endpoint |
metadataUrl | (empty) | Site config, frontend only | The same endpoint for the frontend, matched against the path exactly. Empty disables it. Setting it in a backend file has no effect |
x509cert | (none) | Config file (backend) · site config (frontend) | Public certificate. An absolute path, a path relative to the project root, or the PEM content itself |
privateKey | (none) | Config file (backend) · site config (frontend) | Private key, resolved the same way |
NameIDFormat | urn:oasis:names:tc:SAML:2.0:nameid-format:unspecified | Config file only, top level | The name identifier format requested in the authentication request. A value nested under sp: is ignored |
userStoragePid | 0 | Config file (backend) · site config (frontend) | Page a federated user is created on. Leave it at 0 for the backend, see userStoragePid |
loginFormPage | 0 | Config file (backend) · site config (frontend) | Frontend only in effect: the page excluded from the enforced redirect, and the page TYPO3 resolves while an assertion is being processed. See Frontend Login |
loginStatusPage | the site’s root page | Site config, frontend only | Page a logout lands on. Read directly from the site configuration, not through the configuration file |
noGroupsPage | (none, no redirect) | Site config, frontend only | Page a logged-in visitor without any usable group is sent to. Unset, the visitor stays where they are. See Frontend Login |
federation.enabled | false | Config file (backend) · site config (frontend) | On, an identity with no TYPO3 user gets one created. Off, it is rejected. See User Federation |
federation.userGroups | (empty) | Config file (backend) · site config (frontend) | Group IDs given to a federated user when no claim maps to usergroup |
claimsMapping | (none) | Config file (backend) · site config (frontend) | Maps SAML attribute names to user record columns. See Claims Mapping |
allowEmailAsUsername | false | Config file only, top level | Also matches the name identifier against the email column, not only username. See allowEmailAsUsername |
contactPerson | (none) | Config file (backend) · site config (frontend) | Contact block in the generated metadata, omitted when unset. If a type (technical, support, administrative, billing, other) is given, both givenName and emailAddress are required, or the toolkit rejects the whole configuration |
security.authnRequestsSigned | false | Config file (backend) · site config (frontend) | Sign the authentication request. Only honored when true |
security.logoutRequestSigned | false | Config file (backend) · site config (frontend) | Sign the logout request |
security.logoutResponseSigned | false | Config file (backend) · site config (frontend) | Sign the logout response |
security.allowRepeatAttributeName | false | Config file (backend) · site config (frontend) | Accept an assertion carrying the same attribute name twice instead of rejecting it. See Common Issues |
Keys Only the Configuration File Can Carry
For the frontend, the site configuration contributes exactly the keys marked "site config" above. Everything else has to be in the configuration file—idp, allowEmailAsUsername, NameIDFormat, strict, organization, and any security key beyond the four listed. A top-level x509cert, federation or claimsMapping in a frontend configuration file is not read; those come from the site configuration only.
Keys Passed Straight to the SAML Toolkit
Anything else in the configuration file reaches onelogin/php-saml untouched. The two worth knowing:
strictrejects unsigned or unencrypted messages where a signature or encryption is expected, instead of accepting them with reduced security. This extension never reads it; leave it alone unless you know why you are turning it off.organizationshows up in the generated metadata and is otherwise inert.
Keys That Look Configurable but Are Not
A nested sp: block has no effect. entityId, assertionConsumerService and singleLogoutService are assembled from the base URL plus the top-level entityId and acsUrl, and the computed values overwrite whatever the block says. sp.NameIDFormat is overwritten the same way by the top-level key.
The single logout URL is generated in both scopes and cannot be configured. For the backend it is the entry point plus ?logintype=slo, for the frontend the site base plus ?logintype=slo.
BackendBaseUrl
Despite the name in the extension configuration form, this only takes effect when entityId resolves to a value containing no ://—the assertion consumer service and single logout URLs are already absolute by the time it is applied. So it matters in exactly one case: a configuration that sets a relative entityId such as '/' and needs it prefixed with a specific host, for instance because the backend is reachable under several domains. Leave both empty and the default entity ID—the backend entry point of the current request—is used, which is the right answer for a single-domain installation.
It applies to the frontend as well, even though the name says backend: a relative entityId in a site configuration is prefixed with the same value.
Claims Mapping
claimsMapping maps an incoming SAML attribute name to a column of be_users or fe_users. Two forms:
claimsMapping:
# attribute name → column
"urn:oid:2.5.4.42": "realName"
"urn:oid:1.2.840.113549.1.9.1": "email"
# attribute name → column, with the accepted values enumerated
"member":
field: "usergroup"
valueMapping:
"/saml-users": ['1']
"/editors": ['1', '2', '3']
Attribute names may be written short. The namespace prefixes schemas.microsoft.com/claims/, schemas.microsoft.com/identity/claims/ and schemas.xmlsoap.org/ws/2005/05/identity/claims/ are stripped before matching, so displayname and the full URI are equivalent keys.
A multi-valued attribute is joined with | before mapping. A single value is used as-is.
valueMapping is a filter, not just a translation. Only incoming values listed there are used; everything else is dropped. The results are joined with a comma, which is what makes it suitable for usergroup. Without a valueMapping, the raw value is written to the column verbatim.
A mapping with an empty field name is skipped, which is how you switch a default off.
The Default Mapping
One entry is applied before yours and can be overridden: schemas.microsoft.com/identity/claims/displayname maps to realName in the backend and to name in the frontend.
The Settings That Actually Cost You Something
Three of the keys above are the ones worth a second look before going live.
userStoragePid in the backend. TYPO3 only looks up be_users at the root of the page tree. A backend user federated onto any other page is invisible to every later login, so federation creates a fresh duplicate each time somebody signs in—growing be_users by one row per login, each with the group assignment the claims currently produce. Keep it at 0. In the frontend the same setting is required to be your fe_users folder instead, because a frontend login is restricted to it.
enforceSSO without a tested way back in. It removes the password login for the whole scope, with no per-user exemption. If the identity provider becomes unreachable, nobody signs into the backend until it returns—except through the Install Tool, which has its own authentication. Confirm that access before you switch enforcement on, see Losing the Identity Provider.
allowEmailAsUsername left on after a migration. It exists so users created with an email address as their username can be matched. TYPO3 does not enforce uniqueness on email, so as soon as two active users share an address a login can bind to the wrong record—which means the wrong person’s permissions. Turn it off once the migration is done.
Known Limits
These are properties of the current implementation, not settings:
- A YAML configuration file needs a top-level
idpstring. An inlineidpblock in the toolkit's nested notation, or a missingidpkey, aborts resolution of the file. loginStatusPageandnoGroupsPageare read directly from the site configuration at the point they are needed, not through the configuration file. LeaveloginStatusPageunset and a logout lands on the site's root page; leavenoGroupsPageunset and a visitor without groups is not redirected at all. Set both on any site that uses frontend logout or group-gated access — the fallbacks keep the site working, they do not do the job the pages are there for.- Backend single logout is not implemented. The URL is generated into the metadata, but nothing handles it.