Skip to content

Organizations & SSO

Organizations

Organizations group related tenants together and provide shared SSO configuration. For example, a company with separate Milestone instances for different departments can share a single Microsoft Entra ID setup.

Admin portal — Organizations tab

Creating an Organization

  1. Go to the Organizations tab in the admin portal
  2. Click Create Organization
  3. Enter the organization name and admin email
  4. Click Create

Assigning Tenants

After creating an organization, assign tenants to it:

  1. Edit the organization
  2. Select tenants from the dropdown
  3. Save changes

Tenants inherit the organization's SSO configuration automatically.

Microsoft Entra ID (SSO)

Milestone supports enterprise SSO through Microsoft Entra ID (formerly Azure AD).

Prerequisites

  • An Azure AD tenant
  • An App Registration in Azure AD
  • A client secret for the App Registration
  • The redirect URI configured in Azure AD
  • Outbound HTTPS access from the Milestone server to login.microsoftonline.com and graph.microsoft.com (see Network & Firewall Requirements)

Redirect URI

Milestone uses a single, shared callback URL for all SSO sign-ins:

https://your-domain.com/api/auth/sso/callback

One redirect URI for the whole organization

Do not add the tenant path (/t/{slug}/...) to the redirect URI. Milestone carries the tenant through the sign-in flow internally, so this one URL works for every tenant in the organization. Register it once per App Registration and reuse it — you do not need a separate redirect URI per tenant.

Use the same value in three places: the Azure App Registration, the Redirect URI field in Milestone's SSO configuration, and (single-tenant only) SSO_REDIRECT_URI. They must match exactly.

Azure AD App Registration

  1. Go to Azure Portal > Azure Active Directory > App Registrations
  2. Click New Registration
  3. Set the redirect URI (platform Web) to: https://your-domain.com/api/auth/sso/callback
  4. Under Certificates & secrets, create a new client secret
  5. Note the Application (client) ID, Directory (tenant) ID, and the client secret value

Configuring SSO in Milestone

SSO can be configured at two scopes:

Per-Organization (Multi-Tenant):

  1. In the admin portal, go to the Organizations tab
  2. Click the SSO configure button on the organization
  3. Enter:
  4. Client ID — Application (client) ID from Azure
  5. Tenant ID — Directory (tenant) ID from Azure
  6. Client Secret — The secret value
  7. Redirect URIhttps://your-domain.com/api/auth/sso/callback (the shared URL above)
  8. Save configuration

Organization details with the SSO configuration form in the admin portal

All tenants in the organization share this SSO setup.

Per-Tenant (Multi-Tenant):

A tenant that does not belong to an organization can configure its own SSO from the SSO Configuration screen inside the application (admin only), using the same fields and the same shared redirect URI.

Organization SSO takes precedence

If a tenant belongs to an organization that has SSO enabled, the organization's configuration always applies and the tenant-level SSO form is shown read-only — a per-tenant configuration would be ignored. To configure SSO per tenant, remove the tenant from the organization (or disable the organization's SSO).

Tenant SSO Configuration form shown read-only because organization SSO is active

Per-Instance (Single-Tenant):

Configure SSO in the Settings modal within the application, or set environment variables:

SSO_ENABLED=true
SSO_CLIENT_ID=your-azure-app-client-id
SSO_CLIENT_SECRET=your-azure-app-client-secret
SSO_TENANT_ID=your-azure-tenant-id
SSO_REDIRECT_URI=https://your-domain.com/api/auth/sso/callback

SSO Login Flow

  1. User clicks Sign in with Microsoft on the login screen
  2. Redirected to Microsoft's login page
  3. After authentication, redirected back to Milestone with an authorization code
  4. Milestone exchanges the code for tokens and creates/updates the user session
  5. If the user doesn't exist in Milestone, their account is automatically created

Testing SSO

After saving the configuration, verify it end to end: open a tenant's login page and click Sign in with Microsoft. You should be redirected to Microsoft, and after signing in, returned to that tenant's workspace. If sign-in fails on the return trip, you are sent back to that tenant's own sign-in page with the reason shown above the form — start there. The most common cause is a redirect-URI mismatch — confirm the App Registration, Milestone's Redirect URI field, and (single-tenant) SSO_REDIRECT_URI all read exactly https://your-domain.com/api/auth/sso/callback. The server log carries the underlying Microsoft error for every failure.

Test configuration

The organization's SSO Configuration tab has a Test configuration button. It tests the saved configuration, so save your changes first.

The test signs in as the application itself, which checks:

  • the Directory (tenant) ID, Application (client) ID and client secret look right and, where a secret was pasted from the wrong column, that it is not the Secret ID;
  • the redirect URI is an absolute https:// URL ending in /api/auth/sso/callback with no /t/{slug}/ path;
  • Microsoft recognises the directory, and accepts the client ID and secret;
  • how the server reaches the internet — whether a proxy is configured, and whether any proxy setting is one that sign-in does not use (see below).

What it cannot check is the redirect URI's registration, because signing in as the application never uses one. A green result therefore reads "Microsoft accepted the credentials", not "SSO works", and the last row always asks you to confirm the platform by hand — see the warning below. If the test cannot reach Microsoft at all it says so, rather than blaming the credentials.

Troubleshooting sign-in failures

When a sign-in fails, the message above the sign-in form names Microsoft's error code. Look it up here.

Check the platform first: Web, not Single-page application

This is the one failure that looks like a working configuration. Microsoft issues the authorization code normally — the redirect URI is registered — and then refuses to exchange it, because a URI registered under the Single-page application platform can only be redeemed from a browser, never from a server holding a client secret.

One App Registration can hold both platforms, so a workspace-level sign-in using a /t/{slug}/… URI registered under Web keeps working while the shared organization URI added under SPA fails. In the Azure portal: App registrations → your app → Authentication, check which platform card lists https://your-domain.com/api/auth/sso/callback. Under Manifest, its replyUrlsWithType entry must read "type": "Web". If it reads "Spa", delete it from the Single-page application platform and add the same URI under Web.

The secret Value, not the Secret ID

Azure shows a client secret's Value and its Secret ID side by side, and only the Value works. A secret that looks like a GUID (0a1b2c3d-…) is the Secret ID.

Code What it means What to do
AADSTS9002327, AADSTS9002325 The redirect URI is registered as a single-page application Re-register it under the Web platform (see above)
AADSTS9002326 An Origin header reached a Web-registered URI A forward proxy is injecting Origin on the server's outbound requests
AADSTS7000215 The client secret is not valid Re-enter the secret's Value; check for truncation and trailing whitespace
AADSTS7000222 The client secret has expired Create a new secret under Certificates & secrets and save its Value
AADSTS7000218 No client secret reached Microsoft The stored secret is empty or could not be decrypted — re-enter it
AADSTS50011 The redirect URI does not match the registration Since Microsoft would not have issued a code for an unregistered URI, this usually means the redemption used a different SSO configuration than the sign-in — check the server log, which records the configuration used on both steps
AADSTS54005, AADSTS70008 The sign-in link was already used or expired Sign in again. If it happens every time, look for a link prefetcher or a retrying proxy in front of the callback URL
AADSTS700016 The client ID is not in that directory Check the Application (client) ID and Directory (tenant) ID belong to the same App Registration
AADSTS65001 Consent has not been granted Grant admin consent for User.Read (and GroupMember.Read.All if group access is in use) under API permissions
AADSTS90002, AADSTS900023 The directory could not be found The Directory (tenant) ID is wrong or malformed — copy it from the App Registration's Overview

Some messages come from Milestone rather than Microsoft:

  • "SSO client secret is not configured" — the configuration in effect has no client secret. Save one; a configuration without a secret can never complete a sign-in.
  • "Single sign-on is temporarily unavailable" — the organization's configuration could not be read, usually a master-database problem or a changed TENANT_ENCRYPTION_KEY. Milestone deliberately stops here rather than continuing with the workspace's own settings, which would fail against Microsoft with an unrelated-looking error.
  • "The identity provider did not respond as expected (HTTP …)" — something answered, but without the AADSTS code Microsoft always includes. Usually a proxy or gateway intercepting requests to login.microsoftonline.com from the server and returning a block page. A fault at Microsoft looks the same, so check the Outbound network row of the SSO test, and whether the server is allowed to reach login.microsoftonline.com directly.
  • "Could not reach the identity provider" — the request never got a reply at all: outbound access to login.microsoftonline.com is blocked, or a configured proxy is unreachable.

Compare a working environment against the failing one

When the same version works in one environment and not another, the message tells you which half to look at. The same message in both points at the App Registration, which they share. A message in only one points at that environment — its network, or its own SSO configuration.

What sign-in uses to reach Microsoft

Sign-in requests use HTTPS_PROXY / HTTP_PROXY from the environment, or connect directly when neither is set. They do not use PROXY_PAC_URL, PROXY_CA_CERT, PROXY_USERNAME or PROXY_PASSWORD — those apply only to the public-holiday import. A deployment that describes its proxy solely through those settings has not described it to sign-in, and Microsoft will be unreachable even though holiday import works. The Outbound network row of the SSO test reports exactly this.

Group-Based Access Control

Restrict tenant access to users who belong to specific Microsoft Entra ID (Azure AD) security groups.

How It Works

  1. In the admin portal, edit a tenant
  2. Under Group Restrictions, add one or more Azure AD Group IDs (GUIDs from your Entra directory)
  3. Choose the membership mode:
  4. Any (default) — User must belong to at least one of the listed groups
  5. All — User must belong to every listed group
  6. Save the tenant configuration

When a user logs in via SSO, Milestone fetches their group memberships from the Microsoft Graph API and validates them against the tenant's requirements. If the user doesn't meet the group criteria, access is denied.

Grant GroupMember.Read.All first

Reading group memberships requires the GroupMember.Read.All (or Directory.Read.All) Microsoft Graph permission on the App Registration, with admin consent granted. Without it the group lookup fails for every user, and sign-in reports "Could not verify group membership. Contact administrator." Add the permission under API permissions > Add a permission > Microsoft Graph > Delegated permissions, then click Grant admin consent. Leave the group list empty if you don't need group-based restrictions — the lookup is then skipped entirely.

Finding Azure AD Group IDs

  1. Go to Azure Portal > Azure Active Directory > Groups
  2. Click on the group you want to use
  3. Copy the Object ID (a GUID like a1b2c3d4-e5f6-7890-abcd-ef1234567890)

Use Cases

  • Department isolation — Only R&D department members can access the R&D tenant
  • Project-based access — Create an Azure AD group per project team and restrict the tenant accordingly
  • Compliance — Ensure only authorized personnel can access sensitive project data

Note

Group-based access control requires SSO to be configured. It has no effect on local (email/password) authentication.