Skip to main content

Microsoft Entra ID

muvee supports Microsoft Entra ID (formerly Azure AD) via OpenID Connect on top of the OAuth 2.0 authorization code flow. It is the only provider wired into both planes from a single set of credentials:

PlaneWho signs inToggle
Platformmuvee control-panel users (muvee_session)platform_entra_login_enabled
Projectend users of a project subdomain, through ForwardAuth (muvee_fwd_session)entra_enabled

Unlike Google / Feishu / WeCom / DingTalk, Entra is not configured with environment variables: everything lives in Admin → Settings (stored in system_settings), so no redeploy is needed. ENTRA_* env vars are only a fallback for values left blank in the UI.

Just want the click-by-click version?

See Entra ID — admin setup walkthrough, which walks an administrator through the Azure app registration and the muvee settings card with screenshots. The page you are reading is the reference behind it.

Setup​

1. Register the application in Azure​

  1. In the Azure portal, go to Microsoft Entra ID → App registrations → New registration

  2. Pick the Supported account types that match your intent — this determines the tenant value below

  3. Under Redirect URI, choose the Web platform and add the URIs you need (replace example.com with your BASE_DOMAIN):

    https://example.com/auth/entra/callback   # platform login
    https://example.com/_oauth/entra # project subdomains (ForwardAuth)

    Both can live on the same app registration — add only the ones for the planes you enable.

    Multi-domain

    If you serve muvee under multiple base domains (BASE_DOMAINS), add both URIs for each domain — muvee sends the user back to whichever base domain they started on. See Configuration → Multi-domain.

  4. Go to Certificates & secrets → New client secret and copy the secret Value (not its ID). Entra secrets expire — note the expiry date, because sign-in breaks on that day.

2. Configure muvee​

In Admin → Settings → Social Login Providers, fill in the Microsoft Entra ID card:

FieldWhere it comes from
Directory (tenant) IDAzure app registration Overview
Application (client) IDAzure app registration Overview
Client Secretthe secret Value from step 4

Then tick the planes you want and press Save:

  • Enable on project subdomains — muvee-server reloads muvee-authservice's provider set immediately, so the button shows up on project login pages without a restart.
  • Enable on the muvee platform login page — registered in-process, also without a restart.

The card shows both redirect URIs read-only, with a copy button, so you can paste them straight into Azure.

3. Pick the projects that offer it​

Enabling Entra downstream makes it available; each project's Auth tab decides what its login page shows. Leave Sign-in providers empty to offer every configured provider, or tick a subset (now including Microsoft) to restrict it.

Tenant values and what they imply​

entra_tenant_id accepts three shapes, and the choice changes how strictly muvee validates tokens:

ValueWho can sign inID-token checkALLOWED_DOMAINS
Directory GUID (recommended)that one directoryissuer and tid must equal the configured tenantskipped (org-scoped)
Verified domain (contoso.onmicrosoft.com)that one directoryissuer must match the tid it resolves toskipped (org-scoped)
organizations / common / consumersany work/school directory (plus personal accounts for the latter two)issuer must match the token's tidenforced

In every case the audience must equal your client ID, and the ID token signature is verified against the tenant's JWKS at login.microsoftonline.com.

Single-tenant configurations are treated as org-scoped, the same way Feishu / WeCom / DingTalk are: membership in the directory is itself the authorisation, so the platform's ALLOWED_DOMAINS email-domain whitelist is skipped. With a multi-tenant alias anyone in the world can complete the flow, so ALLOWED_DOMAINS (and access_mode) remain your only gate — set them.

Claims muvee reads​

ClaimUsed for
email, falling back to preferred_username (the UPN)the platform / project identity
namedisplay name (the composed one — given_name / family_name are optional claims muvee has no separate fields for)
oid + tidstable subject key
tid, iss, audvalidation (see above)

name and oid need the openid profile scopes and email needs the email scope; muvee requests all of them. The email claim is present by default only for guest accounts — for members of the tenant it is the email scope that produces it, and if the directory has no mail attribute for the user, muvee falls back to the UPN in preferred_username.

Profile photos​

Entra v2.0 ID tokens carry no picture claim, and Graph profile photos sit behind a bearer token rather than a public URL. So when Fetch profile photos is on (the default), muvee adds the delegated Graph scope User.Read to the login request and, right after the token exchange, reads GET /me/photos/96x96/$value.

Those bytes then get written to disk and the user record stores an ordinary URL (https://<base-domain>/api/public/avatars/avatar-<hash>.jpg), so an Entra avatar behaves exactly like Google's picture or Lark's avatar_url everywhere downstream. The file lives in the branding asset volume (BRANDING_DIR, default /data/branding) under avatars/, is named by content hash — so repeat logins reuse one file — and is served publicly and cached immutably, because the <img> tags loading it sit on project subdomains and carry no credentials.

Why not keep the image inline

An earlier build stored the photo as a data: URI on the user record. That value also travelled into the forward-auth session JWT, and a 2-8 KB photo becomes a 4-15 KB cookie once base64 and the JWT envelope have each inflated it by 4/3 — past the 4096-byte limit every browser enforces. Browsers drop an oversized cookie silently, so the login appeared to succeed while no session stuck and the project subdomain bounced the user back to the login page indefinitely.

Existing users still holding an inline data: URI are migrated lazily — the next sign-in replaces it. A session is never signed with an avatar over 2048 bytes regardless, which is the backstop for a deployment with no asset storage configured.

It is best-effort: a user with no photo (Graph answers 404), a slow Graph, or a missing consent all degrade to an empty avatar and never fail the login. Turning the option off drops User.Read from the authorize request entirely, which is what you want in a tenant that withholds consent for it.

Guest accounts that expose neither email nor a UPN-shaped preferred_username cannot be admitted to the platform plane — there is no address to match against ADMIN_EMAILS, the invite white-list, or ALLOWED_DOMAINS.

Settings reference​

SettingValuesDescription
entra_enabledtrue / falseOffer Entra on project subdomains
platform_entra_login_enabledtrue / falseOffer Entra on the muvee platform login page
entra_tenant_idGUID / domain / common / organizations / consumersAzure directory
entra_client_id—Application (client) ID
entra_client_secret—Client secret value
entra_avatar_enabledtrue / false (default true)Read the profile photo from Microsoft Graph; adds the User.Read scope

Env fallbacks, used per-field only when the setting is blank: ENTRA_TENANT_ID, ENTRA_CLIENT_ID, ENTRA_CLIENT_SECRET, ENTRA_FETCH_AVATAR, plus ENTRA_REDIRECT_URL (overrides the derived platform callback) and PLATFORM_ENTRA_LOGIN (fallback for the platform toggle).

Secrets are stored unencrypted in system_settings, the same threat model as the other provider credentials — the admin-only settings API is the protection boundary.

Troubleshooting​

AADSTS50011: redirect URI mismatch — the URI in Azure must match byte for byte, including https:// and the trailing path. Copy it from the settings card rather than typing it.

The button doesn't appear after saving — the credentials are incomplete (a blank tenant, client ID or secret registers nothing on purpose) or the tenant is malformed; check the muvee-server log for entra provider. For project pages also confirm the project's Sign-in providers list isn't restricted to other providers.

id_token was issued by tenant …, not the configured … — the app registration is multi-tenant while entra_tenant_id pins a single GUID. Either point the tenant at the directory actually issuing tokens, or switch it to organizations.

Sign-in worked and then stopped — check whether the client secret expired in Azure.

Nobody gets an avatar — the tenant most likely never granted User.Read; the server logs entra: fetch avatar. Untick Fetch profile photos to stop asking for the scope. A single user without a photo is just an empty avatar and needs no action.