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

  1. Sign in to the Azure Portal and open App registrations.

  2. 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.

  3. Under Redirect URI, choose Web and add the callbacks the workspace uses:

    Purpose Redirect URI
    SSO login {public-workspace-url}/api/microsoftsso/completed-login-attempt
    Microsoft 365 connector authorization {public-workspace-url}/api/oauth/microsoft/authorize

    Add the second URI only if this registration is also used by the connectors.

  4. 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

  1. In the registration, open Certificates & secrets → + New client secret.
  2. 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:

  1. A SharePoint connector is created for the account's tenant, keyed by domain — its source is SharePoint (<domain>), or SharePoint (<upn> Personal) for an account with no mailbox domain. Tenant Name, when set, replaces the domain in that label.
  2. The connector runs once immediately, then on its normal cadence (every 59 minutes).
  3. 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:

  1. 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.
  2. 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

1

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.

2

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.

3

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.

4

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".

5

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.
© 2026 Curiosity. All rights reserved.
Powered by Neko