Skip to content

Accounts and identity federation

Two populations

Population Authentication Scope
Provider staff identity federation (SSO) or local account every customer
Customers local account only one customer

SSO is configured at platform level, not per customer: a single identity provider, the provider's own. There is no per-customer federation — a customer gets local accounts restricted to its own perimeter.

Roles

Role Scope What it can do
platform_admin all customers everything, including creating customers and accounts
platform_operator all customers full operation, agents included
tenant_admin its customer inventory, credentials, retention, audit
tenant_operator its customer trigger backups, download
tenant_readonly its customer read
platform_pending none nothing — a holding role, see below

What each role authorises, precisely

Permission readonly operator tenant_admin platform_operator platform_admin
See the inventory, the configurations, the reports
Download a configuration
Trigger a backup or a test
Modify the inventory
Manage credentials, approve a fingerprint
Change retention and trust postures
Read the audit log
Manage agents
Create and withdraw customers, change the settings
Manage accounts
Destroy a key, force a purge

The platform_* roles are attached to no customer; the tenant_* roles require one.

The provider staff's global access is deliberate: restricting it would break the managed-service model. Its counterpart is the logging of every view, with the customer concerned.

Local accounts

backup user ops@mssp.example.net '<password>' --role platform_admin
backup user customer@acme.example '<password>' --role tenant_admin --tenant acme

The password must be at least twelve characters, and is stored hashed (Argon2). A tenant_* account must name its customer; a platform_* account names none.

Also via the API.

# Create a local account (requires “user:manage”)
curl -s -X POST "$BASE/api/users" \
  -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
  -d '{"email":"customer@acme.example","display_name":"ACME — operations",
       "role":"tenant_admin","tenant_id":"<customer-uuid>","password":"…"}'

# List the accounts
curl -s "$BASE/api/users" -H "Authorization: Bearer $TOKEN"

platform_pending cannot be assigned by hand: it is a holding role that only identity federation produces.

The holding role

A member of staff who authenticates through SSO without belonging to any known group gets platform_pending rather than an authentication refusal.

The sign-in succeeds, no data is accessible, and the screen displayed names what is missing: “account recognised by the directory but attached to no platform group”. That produces fewer support calls than an opaque error, and the person knows who to ask.

platform_pending is neither a platform role nor a customer role: it does not lift isolation, holds no permission, and requires no attachment to a customer.

Signing in through the API

TOKEN=$(curl -s -X POST "$BASE/api/auth/login" \
  -H 'Content-Type: application/json' \
  -d '{"email":"ops@mssp.example.net","password":"…"}' | jq -r .token)

curl -s "$BASE/api/auth/me" -H "Authorization: Bearer $TOKEN"

The token is valid for BKP_SESSION_TTL_MINUTES (480 min by default). Failed attempts are logged, and the refusal message is identical whatever the reason: it does not reveal whether the address exists.

See Using the API.


Identity federation (SSO)

Two protocols are supported. OIDC is recommended for Entra ID: it needs no system library, is far easier to diagnose, and covers exactly the same need as SAML here.

Trying the flow out without Entra ID

A laboratory OpenID Connect issuer is provided. It speaks the same protocol as Entra ID: what works here will work there, up to the tenant's own settings.

docker compose --profile sso up -d keycloak

BKP_SSO_PROVIDER=oidc \
BKP_OIDC_ISSUER=http://keycloak:8081/realms/backupmssp \
BKP_OIDC_CLIENT_ID=backupmssp \
BKP_OIDC_CLIENT_SECRET=secret-de-laboratoire \
BKP_PUBLIC_URL=http://localhost:8080 \
  docker compose up -d platform

The realm is provisioned in advance: two groups (backup-admins, backup-operators) and two accounts.

Account Password Group Expected role
ops ops backup-admins platform_admin
lecteur lecteur none platform_pending

Open http://localhost:8080 and click Sign in with SSO. The Keycloak administration console is on http://localhost:8081 (admin / admin).

The second account is there to check the behaviour of the holding role described above.

Entra ID, in real conditions

1. Register the application

Azure portal → Microsoft Entra IDApp registrationsNew registration.

  • Name: MSSP backup platform
  • Account types: Accounts in this organizational directory only
  • Redirect URI: type Web, value https://<your-domain>/api/auth/sso/callback

Take note of the Application (client) ID and the Directory (tenant) ID.

The redirect URI must match exactly, scheme and case included. Entra ID requires HTTPS, except for http://localhost.

2. Create a client secret

Certificates & secretsNew client secret. Copy the value immediately: it is not displayed again afterwards. Note the expiry date — a secret that has run out shows up as sign-ins stopping overnight.

3. Emit the groups claim

This is the step most often forgotten, and without it everyone arrives with the holding role.

Token configurationAdd groups claim → tick Groups assigned to the application (rather than All groups), and for each token type choose Group ID or Display name as the source attribute.

Then Enterprise applications → your application → Users and groups → assign the groups concerned.

Entra ID returns object identifiers by default, not names. If you keep that setting, BKP_SSO_ADMIN_GROUP must contain the group's identifier, not its label.

Beyond roughly 150 groups, Entra ID stops including the claim and returns an indirect reference: the role then falls back silently to holding. Assigning the groups explicitly to the application avoids that case.

4. Configure the platform

BKP_SSO_PROVIDER=oidc
BKP_OIDC_ISSUER=https://login.microsoftonline.com/<directory-tenant-ID>/v2.0
BKP_OIDC_CLIENT_ID=<application-ID>
BKP_OIDC_CLIENT_SECRET=<secret-value>
BKP_PUBLIC_URL=https://<your-domain>
BKP_SSO_GROUPS_CLAIM=groups
BKP_SSO_ADMIN_GROUP=<name or identifier of the administrator group>
BKP_SSO_OPERATOR_GROUP=<name or identifier of the operator group>

BKP_PUBLIC_URL is what builds the redirect URI: if it does not match what is declared on the Entra ID side, sign-in fails with AADSTS50011.

The role is read again at every sign-in: removing somebody from a group takes effect at their next sign-in, with no action on the platform.

5. Verify

curl -s https://<your-domain>/api/auth/sso/status

Must answer {"configured": true, "provider": "oidc", "available": true}. Then sign in from the home page: the SSO link only appears if a provider is genuinely reachable.

The audit log records every federated sign-in with the groups received and the role deduced. That is where you read why somebody arrived with the wrong role, instead of guessing.

Diagnosis

Symptom Probable cause
AADSTS50011 redirect URI does not match — check BKP_PUBLIC_URL
AADSTS7000215 client secret wrong or expired
Everyone in platform_pending groups claim not emitted, or object identifier not matching BKP_SSO_ADMIN_GROUP — the Audit screen records the groups received; compare them
“no email address in the assertion” profile and email scopes not granted
“state parameter does not match” cookie lost — cookies blocked, or several tabs

SAML 2.0 (ADFS)

Same principle, with two differences: the return point is /api/auth/sso/callback by POST, and the python3-saml library requires libxmlsec1 — present in the platform image, absent from an ordinary development machine.

GET /api/auth/sso/status says explicitly whether the provider is operational, rather than failing at the first exchange.

What to declare on each side

SAML compares what the two sides say. Three values must match exactly, and a mismatch is not a warning — the assertion is rejected.

In your identity provider Value Where it comes from
Identifier / Entity ID <BKP_PUBLIC_URL>/saml/metadata BKP_SAML_ENTITY_ID, or this default
Reply URL / ACS <BKP_PUBLIC_URL>/api/auth/sso/callback fixed — it is the return route
Sign the assertion the default in Entra ID and ADFS

BKP_SAML_METADATA_PATH points the other way: at the provider's own metadata, as a file or a federation metadata URL. Its signing certificate and sign-in address are read from there, so an expired or rotated certificate is picked up by replacing that file, not by editing settings.

An Entity ID is an identifier, not an address: the convention gives it the shape of a URL without requiring it to lead anywhere. The Reply URL is the opposite — it is a real address, and it is the one route that consumes an assertion.

Behind a URL prefix, both values carry it: an instance published at https://example.net/demo/ declares https://example.net/demo/api/auth/sso/callback. Setting the prefix in BKP_PUBLIC_URL, in BKP_ROOT_PATH, or in both gives the same result — it is never added twice. Declaring the address without the prefix fails in a way worth knowing: the assertion is rejected for a Destination mismatch, which reads as a provider problem rather than as a missing prefix.

Unsigned assertions are refused, whatever the library's own default. A provider configured to sign only the response message, and not the assertion, will therefore be turned away; have it sign the assertion.

Single logout is not offered: no route consumes a LogoutRequest, so none is advertised. Signing out of the platform ends the platform session only.