Skip directly to page content
b13 GmbH

Hauptstätter Str. 59
70178 Stuttgart
Germany
+49 (0) 711 460 589 70 [email protected]
Icon featuring a key and overlapping squares, symbolizing security or access control.

Documentation

TYPO3 Module “Single-Sign-On”
Version 2.0.3 (04 Nov 2026)

Chapters of this Documentation

Federation means creating the TYPO3 user record from the SAML assertion the first time somebody signs in. It is off by default in both scopes, and off is the safe default: an identity the provider authenticates but TYPO3 has no user for is rejected, and the rejection is recorded—see Logging.

federation:
  enabled: true
  # userGroups: [2]

The key sits at the top level of the backend configuration file, and under settings.sso_authentication per site for the frontend. Turning it on means every identity your identity provider authenticates receives a TYPO3 account. That is the intended behavior for a tenant whose users should all have access, and the wrong one for a company-wide identity provider that also serves other applications—for that case, restrict it with a listener on BeforeUserCreationEvent.

userStoragePid

The page a new record is created on. Defaults to 0.

Leave it at 0 for the backend. TYPO3 only looks up be_users at the root of the page tree, so a backend user created anywhere else is invisible to every later login. Federation then does the only thing it can and creates another one: one extra be_users row per login, each carrying whatever group assignment the current claims produce, and none of them the one an administrator edited. The symptom is a be_users list that grows every time somebody signs in.

For the frontend, point it at your fe_users folder. A frontend SSO login only looks up users on that page, not in subfolders, so users of another site's folder are never matched.

Group Assignment

For a newly created user, in this order:

  1. A claimsMapping entry targeting usergroup wins if the assertion carried a value it accepts
  2. Otherwise federation.userGroups is used verbatim, regardless of what the identity provider sent

Backend users created through federation additionally get options = 3—database and file mounts taken from the group—and, when EXT:workspaces is installed, permission to edit the live workspace. Without those a federated backend user would sign in to a backend with no accessible page tree.

Matching an Existing User

Once a user exists, later logins have to find it again, or federation creates a duplicate. The lookup accepts any of the following as a match:

Match Compared Against
The name identifier from the assertion username—and email too when allowEmailAsUsername is on and the name identifier is an email address
A raw name claim, or login_name when name is absent The same two columns, under the same condition
A raw emailaddress claim, or email when emailaddress is absent username and email, unconditionally
The claimsMapping-resolved email value username and email, unconditionally
tx_sso_tenantid and tx_sso_objectidentifier together Both columns, as an alternative in its own right

Two of these deserve a note.

The raw-claim rows rarely fire in practice. They match an attribute literally named name, login_name, emailaddress or email—or one of the WS-Federation claim URIs that share those friendly names. Neither Entra ID nor a typical KeyCloak SAML client sends those names; they send namespaced URIs or OIDs. The row that carries the frontend and backend of a normal installation is the claimsMapping-resolved email, which is why declaring an email mapping matters even when you do not care about the column.

Tenant plus object identifier is a genuine alternative, not an extra condition, and it wins. It identifies a user even when the TYPO3 username has since been changed by hand and nothing else matches any more. Both columns have to be filled for it to apply, which happens on the first login that carries both claims. Among the other matches, the lowest uid wins.

The lookup excludes deleted records and honors the table’s enable clause, so a disabled or expired user is not found—correctly, because such a user must not be logged in.

The Disabled-User Guard

That last point creates a trap federation has to avoid. A disabled user with the right username is invisible to the lookup, so federation would otherwise create a second, active row with the same username—and the user an administrator disabled would be signed in again under it.

Before creating anything, federation therefore checks whether any row already holds that exact username, including disabled and expired ones. If one does, nothing is created and the login is refused, with a warning in the log naming the username. The user stays locked out, which is what disabling them meant.

allowEmailAsUsername

A top-level key in the configuration file, off by default. On, the name identifier—and a raw name or login_name claim—is additionally compared against the email column, not only username. The comparison only happens when the value parses as an email address.

It is a migration aid, for users originally created with an email address where their username is something else. TYPO3 does not enforce uniqueness on email, so as soon as two active users share an address, a login can bind to whichever row the database returns first—and that means signing somebody in with another person's permissions. Turn it off once the affected users are migrated.

What a Login Writes Back

On every successful login, found user or freshly created:

  • All mapped claims, as resolved by claimsMapping
  • tx_sso_objectidentifier, from the object identifier claim—falling back to the name identifier when the identity provider sends no such claim, so the column is filled either way
  • tx_sso_tenantid, only when a tenant claim is present
  • tx_sso_sessionindex, from the assertion. This is written after the update event and cannot be removed by a listener, because a single logout needs it
  • usergroup, reconciled rather than replaced: every group listed anywhere in a usergroup valueMapping is treated as identity-provider-managed and removed first, then the mapped groups are added. A group an administrator assigned by hand survives, as long as it does not appear in a valueMapping. One that does appear is removed on the next login if the claims no longer grant it

A listener can change all of this except the session index, see BeforeUserDataUpdatedEvent.

The tx_sso_* Columns

Four columns are added to both be_users and fe_users.

Column Written By Purpose
tx_sso_enabled An editor Editorial flag only, see below. Rendered as a toggle after the admin field
tx_sso_sessionindex Every login The identity provider's session index, needed to build a logout request
tx_sso_tenantid A login carrying a tenant claim Half of the tenant/object identity match. Read-only in the form
tx_sso_objectidentifier Every login The other half. Read-only in the form

tx_sso_tenantid and tx_sso_objectidentifier are shown after lastlogin, and read-only on purpose: editing them by hand would break the match they exist for.

The Access tab of a TYPO3 backend user record: the core fields "Login enabled" and "Last login", followed by the two read-only fields the extension adds - the tenant ID, empty because KeyCloak sends no tenant claim, and the object identifier, holding the name identifier "homer" the identity provider sent.

tx_sso_enabled Is Editorial Only

It does two things in the backend form: it hides the password field while checked, and it shows a warning badge when it is checked on a user whose tenant or object identifier is missing—that is, one who has not completed an SSO login.

It has no effect on whether an SSO login is accepted. Checking it does not enable SSO for a user, and unchecking it does not lock a user out of SSO. The authentication service ignores it entirely; the check that would consult it exists in the code but is deliberately commented out. If you need to keep a user out, disable the user, or stop the identity provider from authenticating them.