Microsoft Entra ID (formerly Azure AD)
Curiosity signs users in with Microsoft Entra ID over OAuth 2.0 / OIDC, and reads Microsoft 365 content (SharePoint, OneDrive, Teams, Outlook) through Microsoft Graph. Both use an app registration in a Microsoft tenant — and in a workspace running in your own cloud, that registration is yours to create.
The same registration can serve both purposes: the connectors fall back to the SSO credentials when they have none of their own.
Which registration applies to your deployment
| Deployment | "Log in with Microsoft" | Microsoft 365 connectors |
|---|---|---|
| Workspace in your own cloud / on-premises | Your app registration (this page) | Your app registration — either shared with SSO or one per connector |
| Curiosity-hosted workspace or the desktop app | Your app registration, if you want Microsoft SSO | Curiosity's own multi-tenant apps; no registration needed, but your tenant may have to consent to them |
For the Curiosity-hosted connector apps (Curiosity for SharePoint, Curiosity for OneDrive,
Curiosity for Teams, Curiosity for Outlook, Curiosity for Outlook Calendar) and the tenant
consent settings they need, see
Configuring Curiosity for an Office 365 tenant.
1. Register the application
Sign in to the Azure Portal and open App registrations.
Select + New registration, name it (e.g.
Curiosity Workspace), and choose Accounts in this organizational directory only unless users from other tenants have to sign in.Under Redirect URI, choose Web and add the callbacks the workspace uses:
Purpose Redirect URI SSO login {public-workspace-url}/api/microsoftsso/completed-login-attemptMicrosoft 365 connector authorization {public-workspace-url}/api/oauth/microsoft/authorizeAdd the second URI only if this registration is also used by the connectors.
Select Register, then note the Application (client) ID and Directory (tenant) ID.
The public URL has to be right
The workspace builds both callbacks from MSK_PUBLIC_ADDRESS, falling back to the hostname of the
incoming request when it is unset. Behind a reverse proxy or ingress, set MSK_PUBLIC_ADDRESS to
the user-facing URL, and register the URI byte-for-byte (scheme, host, port, path).
2. Create a client secret
- In the registration, open Certificates & secrets → + New client secret.
- Set a description and expiry, select Add, and copy the Value immediately — it is not shown again.
Note the expiry date. When the secret expires, both SSO logins and connector token refreshes fail at the same time; see Rotating the client secret.
3. Add the API permissions
Under API permissions, add these delegated Microsoft Graph permissions and then select Grant admin consent for <tenant>:
| Permission | Needed for | What breaks without it |
|---|---|---|
openid |
Sign-in, ID token | Login fails: "Got empty id token" |
profile, email |
Display name and email address | Login fails to resolve the user's identity |
offline_access |
Refresh token | Login fails: "Got empty refresh token" — no unattended sync either |
User.Read |
/me profile and profile photo |
Login fails: "Failed to get user summary, missing permissions" |
User.ReadBasic.All |
Resolving the other users who appear on documents | Authors and contributors stay unresolved |
Sites.Read.All |
SharePoint sites, site pages, document libraries, files | The SharePoint connector finds no sites |
Contacts.Read, Contacts.Read.Shared |
Reserved; requested but not consumed by any feature today | Nothing |
Curiosity reads the signed-in user's email from the ID token's email claim, and falls back to
Microsoft Graph /me (mail, then userPrincipalName) when that claim is absent. Adding email
as an optional ID-token claim under Token configuration saves that extra call.
4. Enter the details in the workspace
Go to Manage → Access → Single Sign-On (#/manage/access/sso), select Microsoft, and fill in:
| Field | Meaning |
|---|---|
| Tenant Name | Label used in the names of connectors created by the login flow. Optional; the email domain is used when empty. |
| Tenant ID | Directory (tenant) ID from the registration. |
| Client ID | Application (client) ID. |
| Client Secret | The secret value. Write-only — the UI only shows whether the server holds one. |
| Connect Sharepoint on Login | See Connecting SharePoint at login. |
| Required / Optional Scopes | The scopes requested at login when the SharePoint option is on. |
Clearing Tenant ID and saving disables Microsoft login and removes the stored tenant, client ID, client secret, SharePoint option, tenant name, and scope selection.
The values live in the workspace's own configuration, stored in the graph under the keys
MSK_AZURE_LOGIN_TENANT_ID, MSK_AZURE_LOGIN_CLIENT_ID, MSK_AZURE_LOGIN_CLIENT_SECRET,
MSK_AZURE_LOGIN_CREATE_SHAREPOINT, MSK_AZURE_CUSTOM_TENANT_NAME, and MSK_AZURE_CUSTOM_SCOPES.
They travel with the graph and with
configuration sync.
These keys are not environment variables
Despite the MSK_ prefix, the login endpoints read these values only from the stored workspace
configuration. Exporting MSK_AZURE_LOGIN_CLIENT_ID (or the tenant ID or secret) in the container's
environment does not enable Microsoft login — configure it on this screen, or ship it in a
configuration sync payload.
SSO is a licensed feature — the settings screen prompts for a license upgrade if the workspace's license does not include it.
Connecting SharePoint at login
With Connect Sharepoint on Login enabled, a successful Microsoft SSO login does double duty: the tokens it received are also stored as SharePoint connector credentials for that user, so the user never sees a second consent screen.
What happens on the first such login:
- A SharePoint connector is created for the account's tenant, keyed by domain — its source is
SharePoint (<domain>), orSharePoint (<upn> Personal)for an account with no mailbox domain. Tenant Name, when set, replaces the domain in that label. - The connector runs once immediately, then on its normal cadence (every 59 minutes).
- Every later user from the same domain is added to that same connector as they log in.
Two consequences worth knowing:
- One Microsoft account per domain per user. If a user logs in with a second account on a domain that is already connected, the login fails with "Domain is already connected."
- A login is never failed for missing SharePoint. An account without SharePoint access still logs in; the connector simply finds nothing for that user.
Leave the optional scopes off
The login request carries the SharePoint scope set only while both optional scopes
(Contacts.Read, Contacts.Read.Shared) are toggled off. Turning either on makes the login fall
back to the base scopes (openid profile email offline_access), which is not what the screen
implies. Neither optional scope is used by any feature today, so leaving both off costs nothing.
Adding SharePoint access to this registration
Each Microsoft connector (SharePoint, OneDrive, Teams, Outlook) resolves its Entra credentials in this order:
- Tenant ID + Client ID + Client Secret set on the connector itself — use this to point a connector at a different app registration or a different tenant.
- The SSO credentials configured above — the fallback when the connector carries none. One registration, one secret to rotate.
If neither is available, the connector run fails with "missing Azure Application Credentials".
The connector's own consent request asks for openid profile email offline_access. The Graph
permissions the resulting token actually carries are the delegated permissions granted on the app
registration, which is why Sites.Read.All has to be configured and consented there rather than
selected per connector.
Reusing the SSO registration for a SharePoint connector
Add the connector callback to the registration
In App registrations → your app → Authentication, add the Web redirect URI
{public-workspace-url}/api/oauth/microsoft/authorize alongside the login one. Users are sent to
this URI after they consent, and Microsoft rejects the round trip if it is missing.
Grant Sites.Read.All
In API permissions, add the delegated Microsoft Graph permission Sites.Read.All (on top of the
login permissions from step 3), then Grant admin consent. Without
consent, users either see a "Need admin approval" screen or connect successfully and find no sites.
Create the connector with no credentials of its own
In Manage → Configure → Integrations, add a Microsoft SharePoint connector. Set Source to the
name you want the content grouped under and leave TenantID, ClientID, and ClientSecret empty
— that is what makes it fall back to the SSO registration. Optionally set
SitesAllowListWebUrls, below.
Have users connect their accounts
Each user opens Connect apps (#/spaces/connect-apps), connects Microsoft SharePoint, and
consents. Their refresh token is stored on the connector. Until at least one user has done this, runs
end with "No user has authorized access to their data yet".
Verify
Trigger a sync and watch the connector's progress messages ("Syncing SharePoint" → "Finished syncing
SharePoint"), then search for a document you know lives in one of the sites. A run that ends
immediately usually means the allowlist matches no site, or Sites.Read.All is not consented.
To skip the per-user consent step entirely, enable
Connect Sharepoint on Login instead — the connector is created and
enrolled from the login tokens, using the same registration and the same Sites.Read.All grant.
The OneDrive, Teams, and Outlook connectors work the same way, through the same
/api/oauth/microsoft/authorize callback and the same credential fallback. None of them pins a scope
list of its own, so what they can read is decided entirely by the delegated permissions granted and
consented on the registration — drive, chat, or mail permissions respectively, per
Microsoft's Graph permissions reference.
Limiting which sites are synced
SitesAllowListWebUrls, on the SharePoint connector's settings, is a JSON array of site URLs. Empty
(the default) means every site the connecting user can read; non-empty means only the listed sites:
[
"https://contoso.sharepoint.com",
"https://contoso.sharepoint.com/sites/Marketing",
"https://contoso.sharepoint.com/sites/Marketing/Campaigns",
"https://contoso.sharepoint.com/teams/Engineering"
]
Entries are compared to each site's Graph webUrl with an exact string match — no prefix
matching, because the tenant root is itself a site and a prefix rule would pull in the whole tenant.
In practice:
- List the root site (
https://contoso.sharepoint.com) explicitly if you want it. - List every subsite separately; allowing a parent does not include its children.
- No trailing slash, and match the casing Microsoft Graph returns.
- The filter applies to both document libraries and site pages — anything not on the list is never enumerated.
Allowlist changes wait on the per-user site cache
The filtered site list is cached per user for 120 hours, so editing the allowlist does not take effect on the next run. A user's cache is reset when that user triggers a sync from their own data-source card; an admin "Run now" from the scheduling page does not reset it.
Two things the allowlist does not do: it cannot widen access (each user still only sees what their own Microsoft account can read), and removing a site from the list does not delete content already ingested from it — the site simply stops being refreshed. To remove the content, reset the connector or the user.
See Microsoft SharePoint for the connector's remaining settings, what it ingests, and how it maps access.
Tokens, expiry, and reconnects
- Each user's refresh token is stored with the connector in the graph and used to mint access tokens; a token is refreshed about six minutes before it expires.
- When a refresh fails, the user is marked as authentication-expired: their sync is skipped and the workspace prompts them to reconnect. Other users on the same connector keep syncing.
- Users who have not logged in for more than seven days are skipped by connector runs until they return.
Rotating the client secret
A secret in use by both SSO and the connectors takes both down when it expires. To rotate without a gap: add a second secret in the portal, paste it into Manage → Access → Single Sign-On → Microsoft (and into any connector carrying its own credentials), confirm a login and a connector run succeed, then delete the old secret.
Troubleshooting
| Symptom | Cause |
|---|---|
AADSTS50020: User account … does not exist in tenant |
The user picked an account outside the tenant the registration is scoped to (often a personal account). |
"Got empty refresh token (The offline_access scope is missing…)" |
offline_access is not granted on the registration. |
"Got empty id token (The openid scope is missing…)" |
openid is not granted. |
| "Failed to get user summary, missing permissions" | User.Read is missing or not consented. |
| "missing Azure Application Credentials" when a connector runs | The connector has no credentials of its own and Microsoft SSO is not configured. |
| Redirect URI mismatch at Microsoft | MSK_PUBLIC_ADDRESS differs from the registered URI, or only one of the two callbacks was registered. |
| "Domain is already connected. More than one account per domain…" | A second Microsoft account was used for a domain that already has a connector. |
| Users see "Need admin approval" instead of a consent screen | The tenant's consent policy requires admin consent — grant it on the registration (or, for Curiosity-hosted apps, on the enterprise application). |
| The SharePoint connector finds no sites | Sites.Read.All is not consented, or the connector's site allowlist does not match any site URL exactly. |
Related
- Single Sign-On overview — redirect URI shape shared by all providers.
- Microsoft SharePoint connector
- Microsoft OneDrive connector
- Microsoft Teams connector
- Configuring Curiosity for an Office 365 tenant — tenant consent settings for the Curiosity-hosted apps.