Single Sign-On
Lacroo Pro uses Microsoft Entra ID (formerly Azure Active Directory) for Single Sign-On.
An Entra tenant can be connected in two places:
- during org setup, by the founding admin in the setup wizard; and
- at any time afterwards, by an organisation admin at Settings → Single Sign-On on the
/orgconsole.
The two write the same three fields to the same organisation record. Everything below the app-registration walkthrough describes the ongoing, self-service surface.
Azure App Registration
The org admin needs to create an App Registration in their Azure portal:
- Go to Azure Portal > Microsoft Entra ID > App registrations > New registration
- Name:
Lacroo Pro(or any name) - Supported account types: Single tenant
- Redirect URI: Web, set to the web value shown on the Single Sign-On tab, then add the mobile value as a second Web redirect URI (see Redirect URIs)
- Note the Application (client) ID and Directory (tenant) ID
- Go to Certificates & secrets > New client secret
- Note the Secret value (it’s only shown once)
Required API Permissions
The app registration needs:
| Permission | Type | Purpose |
|---|---|---|
User.Read.All | Application | List directory users |
openid, profile, email | Delegated | Sign a person in |
User.Read | Delegated | Read the signed-in person’s own profile |
Grant admin consent for User.Read.All. Diagnostics check 4 produces a ready-made admin consent link for the tenant if this step was missed. The delegated permissions are the standard sign-in set; the diagnostics do not test them.
Sign-in names
Microsoft signs a person in with their sign-in name: for a member of your tenant that is their user principal name, and for an invited guest it is their own home address. That is not always the address in their mailbox — joint-venture and alliance mailboxes are the common case.
Lacroo keeps each person’s sign-in name, fills it in for them at Microsoft so they only type their password, and shows it as Signs in as … under their email wherever it differs. Nothing needs to change in your tenant.
Any address signs you in
A person can type any address on their Microsoft account on the sign-in card — their mailbox, their sign-in name, or another address such as an alliance mailbox — and reach the same account.
- Lacroo learns which email domains belong to your tenant by asking Microsoft, after each directory refresh. No list needs to be kept.
- A member’s other addresses on those domains are stored with their account, so Microsoft is filled in with their sign-in name whichever one they type.
- An address on one of your domains that Lacroo does not know yet goes straight to your Microsoft sign-in, never to sign-up. Microsoft shows its account picker.
- Someone Microsoft signs in who has no Lacroo account sees You’re not set up in Lacroo yet. Ask your office to add you. Accounts come from adding people (Users, or a project’s members), never from signing in.
- Adding someone from the directory never makes a second account for a person who already has one under another of their addresses.
- A member’s email in Lacroo follows their primary address in Microsoft. Their other addresses show under it as Also: …, and searching People or a project’s Members finds them by any of them (for example
tonkinalliance).
Who can see it
Anyone who can reach the /org console and holds the Edit org / SSO / config permission (orgsettings.manage). In the default role set that is master admin and organisational manager — the same people who can already reach every other page under Settings.
The page sits under /org, so it inherits that console’s existing access gate rather than defining one of its own; the API enforces orgsettings.manage independently on every read, write and diagnostics call. orgsettings.manage is also held by project admin by default, but project admin is not granted the /org console in the default role set, so it does not reach this page.
What is stored
| Field | Notes |
|---|---|
| Directory (tenant) ID | The GUID from the app registration’s Overview page |
| Application (client) ID | The GUID from the same page |
| Client secret | Encrypted at rest with AES-256-GCM and a fresh nonce per write |
The client secret is write-only. No API returns it, no log records it, and it never appears in a diagnostics response. The settings page shows only whether a secret is on file and when it was last written.
Leaving the secret box blank on save keeps the stored secret, so a mistyped tenant ID can be corrected without re-entering a credential.
Saving any change also clears Lacroo’s cached copy of your directory, so the next Users page reflects the configuration you just saved rather than the previous one.
Redirect URIs
The Single Sign-On tab shows two redirect URIs: Web (…/api/auth/sso/callback) and Mobile (…/api/mobile/auth/sso/callback). Register both, exactly as shown, in your app registration under Authentication → Redirect URIs (Web) — the mobile app signs in through the second one, so without it Microsoft sign-in fails on phones. The setup wizard’s SSO step shows the same two values.
If your people reach Lacroo on more than one address, register each address’s pair — Lacroo sends the address the browser actually used, and Entra rejects any redirect URI that is not registered.
Diagnostics
Run diagnostics performs six ordered checks. A check whose prerequisite failed reports Not checked rather than a second, confusing error.
| # | Check | What a failure means |
|---|---|---|
| 1 | Credentials stored | Nothing is saved, or the saved secret cannot be decrypted with the server’s current key |
| 2 | Tenant reachable | The tenant ID is wrong or mistyped |
| 3 | Token acquisition | The secret is expired or wrong (AADSTS7000215), or the app registration is not in this tenant (AADSTS700016) |
| 4 | Directory read | The User.Read.All application permission is missing, or admin consent was never granted (Authorization_RequestDenied) |
| 5 | Directory sync | Lacroo’s own background copy of your directory is not being produced — see below |
| 6 | Sign-in configuration | Informational — shows the redirect URI Lacroo sends. Lacroo cannot verify this from the server |
Check 4’s remediation names the exact permission and produces a ready-made admin consent link for the tenant, so the customer’s IT can act without a support ticket.
Checks 1 to 4 test Microsoft. Check 5 tests Lacroo: the background job that keeps a copy of your directory so the Users page never has to wait on Microsoft. The two can disagree, and when they do it matters — a tenant that answers every question correctly still shows an empty directory if that background job is not running. Check 5 reports when the copy was last made and how many people it holds, whether a sync is still in progress, and if one failed, when and why. It is the only check that can fail while every Microsoft check passes, and when it does, its remediation says plainly whether the fix is yours or ours.
Each failed check shows Microsoft’s own response under Show Microsoft’s response. Only the HTTP status and the error code are recorded in Lacroo’s logs — never the response body, never a credential.
The directory in Users and Roles
Users and Roles → SSO lists provisioned users first, then a separator, then people who exist in the Entra tenant but are not Lacroo users yet. Directory rows carry no role selector and no Manage roles action — they are not users.
Add to org opens a role picker and creates the user with that role in a single step. If the role cannot be applied, no user is created at all.
The directory is merged server-side from a cached copy that Lacroo refreshes in the background — every night, and whenever the page finds the copy out of date — so a large tenant never slows the page down. The page is served from the last good copy while a fresh one is fetched, and says nothing about the refresh itself.
Each refresh also brings every Microsoft sign-in user’s sign-in name up to date. People can be searched by that sign-in name as well as by name and email.
When the directory cannot be produced, the page still lists everyone already in Lacroo — a broken directory never blanks the Users page — and shows one of two different warnings, because the two faults need opposite instructions:
- Yours. Microsoft answered Lacroo and refused: a wrong tenant or client ID, an expired secret, a missing permission or consent. The warning links to the Single Sign-On tab so you can check the connection.
- Ours. The directory could not be produced for a reason inside Lacroo — the background job is not running, or a Lacroo service cannot read something it wrote. This warning says so plainly, links nowhere, changes nothing in your tenant, and gives you a short reference to quote to Lacroo support. It is the same reference the Directory sync check prints, so either surface gives support one identical string.
Anything Lacroo cannot confidently attribute to your directory is reported as ours. A Microsoft outage, a rate limit and a network fault are all answers Lacroo did not earn the right to blame on you, so none of them sends you back to the credentials form.
Before the first copy exists, the page lists everyone already in Lacroo and shows no note at all; the directory rows appear on their own once the copy lands. That is what you see on a brand-new connection, and for a short while after any change you save on the Single Sign-On tab, because saving clears the cached copy on purpose. Neither warning above appears in that state.
Organisations that do not use single sign-on see none of this: no directory section, no note, and no warning.
Staff who join with a password
Anyone whose email is at a domain your organisation signs in to Microsoft with signs in with Microsoft. Lacroo keeps them out of every route that would make a password account:
- Which addresses count. Any address at a domain Microsoft lists as verified for your tenant, at a domain your members’ sign-in names end in (taken from your Microsoft users and Lacroo’s saved copy of your directory, so it holds even while the verified list is empty or being refreshed), or an address your directory lists exactly. Guests do not count. Every listed domain blocks, including joint-venture and alliance domains.
- Where it applies. The phone’s email-first sign-in screen (it goes to Microsoft sign-in, never to “join your organisation”), Join Project, the supplier org-wide invite, a supplier worker invite, the supplier-admin invite and the client invite. Each says Use your work sign-in with a Sign in with Microsoft link. The answer is the same for every address at the domain, whether or not the person is in your directory.
- Admins can still create a password office user from the organisation dashboard (Create custom user); that address is always a Lacroo address, never a work one. Project admins can still add a record-only field user.
- Switch to Microsoft sign-in (Users and Roles, All Field Users) moves someone who joined with a password. The admin picks a role and confirms. The person keeps their history, records and project memberships; their password stops working and they are signed out. It needs both Manage users and Assign roles, works only on people who joined themselves with a password, and you can only give a role with no more access than your own.
Signing in to Microsoft never creates a Lacroo account. People get an account from the migration or when an admin adds them.
Project members
Add Member on a project is one searchable list covering both org users and the SSO directory, with the same separator. Picking someone from the directory adds them to the organisation and the project together. Everyone in the organisation is listed, whichever way they sign in. Large lists show the first 50 with a Show more people control; searching by name, email or Microsoft sign-in name is the quickest way to find someone from the Microsoft directory. A person whose sign-in name differs from their email shows it as Signs in as …, on web and on mobile.
How it works
- An admin enters Tenant ID, Client ID and Client Secret — in the setup wizard, or on Settings → Single Sign-On
- The Go API encrypts the client secret using AES-256-GCM
- Encrypted bytes + nonce are stored in the
organizationstable - To read the directory, the API:
- Decrypts the secret
- Acquires an OAuth2 token via the client credentials flow
- Calls Microsoft Graph
/v1.0/users, paging with a hard page cap and deadline
- For the Users page and the member pickers, that Graph walk runs in a Temporal workflow (
EntraDirectoryRefresh), never in a page request. The users list is always served from the cached snapshot and returns immediately. A daily sweep (EntraDirectorySyncSweep) starts one refresh per connected organisation; it never runs on per-PR preview environments - Each refresh also asks Microsoft’s public tenant discovery which of your members’ email domains belong to your tenant, and keeps only those. A member’s other addresses (their mailbox and
proxyAddresses) are kept only on those domains, and only when no one else in the directory holds them - After each refresh, the workflow updates every SSO user’s stored sign-in name and other addresses from the snapshot, and links SSO users who have no Microsoft object ID yet whose email matches a directory entry’s mailbox, user principal name or one of a member’s other addresses. It never links a password user, never moves an object ID already on a user, and never gives one object ID to two people
- Two request-thread paths still call Microsoft directly, both explicitly triggered by an admin who is waiting on the answer: the three bounded probes behind Run diagnostics, and the setup wizard’s Validate step, which walks the directory once (bounded) to offer a co-admin
- Every refresh records what happened to it — when it was asked for, whether it started, and whether it succeeded or failed and why. That record is what check 5 reads, and it is why “we have not fetched yet” and “the fetch failed” are told apart on screen instead of both being reported as a failure
Security
- Client secrets are never stored in plaintext
- Encryption uses AES-256-GCM with a random nonce per encryption
- The encryption key is a 32-byte (64-char hex) string configured via
AES_ENCRYPTION_KEY - Generate a key:
openssl rand -hex 32 - The secret is never returned by any endpoint, never written to a log attribute, never embedded in an error message and never placed in a workflow argument — the refresh workflow carries only the organisation id and re-reads the credential inside its own activity
- Microsoft’s raw responses are scrubbed of any literal occurrence of the secret before being surfaced in diagnostics
- Every SSO read, write and diagnostics call resolves the organisation from the verified session, so one organisation can never read another’s configuration
- The Entra client uses raw HTTP instead of the Microsoft Graph SDK to minimize dependencies
Operational requirements
| Variable | Where | Why |
|---|---|---|
WEB_APP_URL | API (lacroo-api) | Composes the redirect URI shown to customers. Required in production — the API refuses to boot without it |
AES_ENCRYPTION_KEY | API and worker (lacroo-worker) | Decrypts the stored secret. The directory refresh runs on the worker, so the worker needs it too |