identity-service
By Marek Nowak · created 9 Mar 2020, 08:30 UTC · last updated 22 Oct 2026, 15:10 UTC · version 31
identity-serviceapprovalssamlrolesarchitecture
Repository: Emargen/acme-identity-service · Owner: Marek Nowak (approvals, SAML), with Elif Yildiz and Jonas Berg · Stack: PHP 8.4, Laravel 13, PostgreSQL 16 (RDS, one per region), Redis (cache and rate limits); sign-in tokens are rows in PostgreSQL (Sanctum)
identity-service signs people in, keeps who they are, where they work and what roles they hold, and decides who approves each access request. Everything else reads from it.
Code map
| Folder | What lives there |
|---|---|
src/Saml | DepotSamlSettings (each depot's identity provider: entity ID, sign-in URL, certificate), DepotSamlConfig (each depot's own sign-in address on our side), EnableDepotSaml (the per-depot switch), SamlSignIn, AssignRolesFromClaims, GroupRoleMap, MetadataImport (metadata upload) |
src/Policies | SignInPolicy: second factor, session length and administrators' addresses, per client |
src/Roles | RoleAssignment: who holds which role, where it came from (portal or saml), and since ACM-247 the depot it covers |
src/Requests | access requests and their states, SubmitAccessRequest (the required reason), RequestMove |
src/Access | access items, grants, request bundles, the Access report |
src/Approvals | ApprovalRouting, ApprovalPolicy, deciding, moving waiting requests when a role changes |
src/Clients | Client and its settings, Region, CurrentClient |
src/Onboarding | StartOnboarding, FinishOnboarding, ChangeRegion |
src/Jobs | scheduled jobs (below) |
tools/ | the local SAML test IdP, see Local development |
People, depots and roles
Each person has one home depot (people.home_depot_id, never empty) and may work at others (depot_assignments, one row per depot with the role there: driver, dispatcher or depot manager). An agency driver without a depot of their own is given the depot of their first shift. Assignments have no end date. Jonas's Claude Code wrote this up in ACM-309.
Client-wide roles are Administrator, Approver and Member. Since 1 October a role assignment can carry a depot (role_assignments.depot_id); empty means every depot, as before, so nothing changed for clients who don't set one (ACM-247, #412 and #431).
How a request finds its approver
// src/Approvals/ApprovalRouting.php:47
return $request->person->homeDepot->approvers();
- A request is routed once, when it comes in, to whoever holds the approver's role at the person's home depot, not the depot they ask for. Kelderby's drivers move between depots, so this matters there; changing it changes ACM-247's R1 and is Lena's call.
- A depot manager's own request goes to administrators.
ApprovalPolicyrefuses a decision by the person who asked; the test came in #418. - If nobody holds the role at the depot, the request waits unassigned. Administrators see every waiting request under All requests and can decide any of them.
- Reminders go to whoever the request waits on, 24 hours after it arrives and then daily.
ExpireRequestsruns nightly at 02:00 and closes any request older than 14 days. Nobody is told when it does. This is known; see Runbook: access requests stuck or closed.
Since 22 October (ACM-320): when a role assignment changes, the requests waiting at that depot move to whoever approves there now, or to the client's administrators with the line "No approver at this depot". Each moved request keeps who it waited on and when it moved (access_requests.moved_from_person_id, moved_at), and notifier sends the new approver one email listing what moved. A change made in the portal applies at once; one made in Entra ID applies at the person's next sign-in.
Single sign-on
- Each depot has its own identity provider settings (
DepotSamlSettings) and its own sign-in address on our side (DepotSamlConfig). In Entra ID that means one enterprise application per depot: 25 for Kelderby, until ACM-293 gives a client one setup for every depot. EnableDepotSamlswitches one depot. A depot's people keep their passwords until its switch is on. The switch only decides how the next sign-in happens; nobody is signed out by it. Switching back: runbook.AssignRolesFromClaimsre-applies roles from the person's Entra ID or Okta groups at every sign-in. It removes only the assignments it made last time and makes them again; assignments made in the portal are left alone, with their depot (ADR-010).- No account is made at sign-in. A SAML sign-in is matched to an existing person by email; if nobody matches, it is refused with "No account for this sign-in". Accounts are made by an administrator, by the people import or by an onboarding import. When someone's imported email differs from their Entra ID or Okta username, an administrator tends to make them a second account, and their roles and requests stay on the first: that is where duplicate people come from (merge them in the portal; matching on employee number is ENG-1838).
- Okta depots read the depot code from an Okta attribute (Vossberg, since 22 June).
Passwords, phone codes and sessions
- A session lasts eight hours by default; each client's administrators can set 1 to 12 hours (
src/Policies/SignInPolicy.php, Settings › Sign-in). No idle time-out. - Phone codes: six digits, valid ten minutes, five tries, then a 15-minute wait. Sent through notifier and Textbridge.
- Since 10 September: ten failed passwords in 15 minutes lock the account for 15 minutes (ENG-1705), and the edge limits each IP address (OPS-42). Administrators can unlock someone in the portal. ADR-014.
Scheduled jobs
| Job | When | What |
|---|---|---|
DeliverOutboxEvents | every minute | posts waiting outbox_events to the notifier, signed |
SendApproverReminders | hourly | reminders at 24 hours, then daily |
ExpireRequests | 02:00 | closes requests older than 14 days |
PruneSignInLog | 03:00 | drops sign-in log rows older than 90 days |
PruneApprovalAudit | 03:00 | drops approval decisions older than 400 days |
BackUpClients | 01:30 | backs up each client to its own region's storage |
CheckSamlCertificates | 06:00 | warns about certificates that expire soon, see SAML certificates |
Change history
| Version | Date | Who | What |
|---|---|---|---|
| 26 | 11 Jun 2026 | Marek | Phone codes, MFA for administrators |
| 27 | 14 Sep 2026 | Marek | Lockout and rate limits |
| 28 | 2 Oct 2026 | Marek | Depot on role assignments (ACM-247) |
| 29 | 8 Oct 2026 | Jonas | Home depot routing and the 14-day job, from Claude Code's notes |
| 30 | 15 Oct 2026 | Marek | Note on per-depot certificates and ACM-293 |
| 31 | 22 Oct 2026 | Marek | Waiting requests follow their depot's approver (ACM-320) |