---
title: "Enterprise-managed authorization (EMA) with Microsoft Entra ID"
img: https://s3.us-east-2.amazonaws.com/unified-article-images/enterprise_managed_authorization_ema_with_microsoft_entra_id-icon.png
date: 2026-09-24T00:00:00.000Z
updated: "2026-10-02T16:57:39.139Z"
tag: Guides
integrations: 
description: "Register a Microsoft Entra ID tenant as a trusted issuer so Entra signs the enterprise-managed authorization assertion. Unified.to checks that signature..."
url: "https://docs.unified.to/guides/enterprise_managed_authorization_ema_with_microsoft_entra_id"
---

# Enterprise-managed authorization (EMA) with Microsoft Entra ID
------
_September 24, 2026_

Register a Microsoft Entra ID tenant as a trusted issuer so Entra signs the enterprise-managed authorization assertion. Unified.to checks that signature against the tenant's keys and returns an access token for the end user. Add one trusted issuer per Entra tenant.


> ⚠️ Direct mode accepts an ID-JAG only. The JWT header typ must be oauth-id-jag+jwt. A normal Entra access token or ID token is rejected. Entra does not generally issue that grant yet. If you only have ordinary Entra sign-in, use the brokered setup at the end of this guide. In that setup Entra is the login directory, and your own issuer is the trusted issuer.


## Before you start

- You are a workspace admin.
- You can open the Entra admin center for the customer tenant.
- You have the tenant (directory) id and the application (client) id of the app that will request tokens. These are different ids.
- The issuer string is the Entra v2 issuer, including /v2.0. https://sts.windows.net/<tenant-id>/ does not match.

## 1. Turn the feature on


In the app, open Settings, then Enterprise Auth. Turn Enterprise-Managed Authorization on and save.


On EU or AU, use api-eu.unified.to or api-au.unified.to. The audience and resource claims later in this guide must use that same host. The examples below use the US host.


## 2. Copy the ids from Entra


In the Entra admin center, open the tenant and the app registration that will request tokens for Unified.to.

- **Tenant id** — Entra ID, Overview. This is the directory id. It goes in the issuer URL.
- **Application (client) id** — the requesting app. It goes in Allowed client IDs. A direct issuer with an empty list is rejected.

The issuer you will register is:


```plain text
https://login.microsoftonline.com/<tenant-id>/v2.0
```


## 3. Add a trusted issuer


On Enterprise Auth, choose 'Add trusted issuer' and set:

- Topology: Direct (XAA)
- Issuer (iss): https://login.microsoftonline.com/<tenant-id>/v2.0
- Key source: JWKS
- JWKS URL: leave blank. Unified.to loads the keys from the tenant's OpenID configuration. To set it yourself, use https://login.microsoftonline.com/<tenant-id>/discovery/v2.0/keys
- Allowed client IDs: the Entra application (client) id. Required for a direct issuer.
- Default scopes: the categories the token should receive when the assertion has no scope, for example crm.read
- Environment: optional. Set it when tokens from this tenant must stay in one connection environment, such as Production
- Just-in-time identity mapping: on, if you want Unified.to to match the assertion email to a workspace user
- Enabled: on

The same record over the API:


```shell
curl -X POST https://api.unified.to/unified/enterprise-auth/trusted-issuers \
  -H "Authorization: Bearer $UNIFIED_WORKSPACE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "mode": "direct_xaa",
    "issuer": "https://login.microsoftonline.com/<tenant-id>/v2.0",
    "key_source": "jwks",
    "client_ids": ["<entra-application-client-id>"],
    "default_scopes": ["crm.read"],
    "jit_enabled": true
  }'
```


Repeat this for each Entra tenant. The tenant id is part of iss, so two tenants cannot share one row.


## 4. What Entra must put on the assertion


Entra signs the JWT. Your app redeems that JWT. It does not re-sign it. The header typ must be oauth-id-jag+jwt.

- iss matches the trusted issuer string exactly, including /v2.0
- aud is the API origin, such as https://api.unified.to
- resource is that same origin for REST calls, or that origin plus /mcp for MCP calls. A token for one resource is rejected by the other
- sub is the end user. Entra's oid is the stable subject
- client_id is the application id in Allowed client IDs
- jti is unique for each redemption. The test panel does not consume jti
- scope is space-separated, such as crm.read crm.write. A comma-separated list is treated as one unknown scope and dropped
- email is a user who already belongs to the workspace, when just-in-time mapping is on. Unified.to does not read preferred_username or upn

## 5. Test, then exchange


On the issuer row, choose Test and paste the assertion. A passing trace means the signature, issuer, audience, resource, client id, and identity checks succeeded. Test does not issue a token.


Exchange the assertion once at the token endpoint. The access_token starts with ema_. Send it as Authorization: Bearer on integration data routes. It can call only the categories in its scopes. It cannot manage the workspace, create API keys, or change enterprise-auth settings.


```shell
curl -X POST https://api.unified.to/oauth/token \
  -H "Content-Type: application/x-www-form-urlencoded" \
  --data-urlencode "grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer" \
  --data-urlencode "assertion=$ASSERTION_JWT" \
  --data-urlencode "scope=crm.read"
```


### What comes back


The signed JWT is only the assertion. Do not put ema_ in front of it. Unified.to mints a separate opaque access token and returns it already prefixed. There is no refresh token. When it expires, redeem a new assertion.


```json
{
  "access_token": "ema_…",
  "token_type": "Bearer",
  "expires_in": 3600,
  "scope": "crm.read"
}
```


> ⚠️ The token endpoint requires the workspace Enterprise Auth switch to be on. Test still runs when that switch is off. A disabled switch returns issuer not trusted.


### Call the API or MCP


The resource claim on the assertion chooses where this access token works. One token does not work for both. Send it as Authorization: Bearer followed by the access_token value.

- REST API: set resource to https://api.unified.to (or the EU or AU host). The token is accepted only on integration data routes such as GET /crm/{connection_id}/contact, and only when the scope matches the category and the method.
- MCP: set resource to that same origin plus /mcp, for example https://api.unified.to/mcp. Send the bearer token to the MCP server.

A REST token sent to MCP is refused, and an MCP token sent to the REST API returns token issued for another resource.


## 6. Map the user


The trusted issuer does not store a user id. Each assertion subject gets an identity row. With just-in-time mapping on, Unified.to sets the workspace user id when the email claim matches a user in the workspace. Otherwise open the identity and set the workspace user yourself. No match and no just-in-time mapping returns identity not provisioned.


## When Entra cannot issue an ID-JAG


Sign the user in with ordinary Entra sign-in, then register your own issuer as the trusted issuer. Topology is Brokered (ISV). Key source is JWKS (or Static key, or Workspace secret). Issuer (iss) is your issuer, not the Entra tenant. Put the Entra oid in sub and the user's email in the email claim. aud, resource, and a fresh jti are the same as above. A brokered assertion may omit typ. A direct assertion may not.


```shell
curl -X POST https://api.unified.to/unified/enterprise-auth/trusted-issuers \
  -H "Authorization: Bearer $UNIFIED_WORKSPACE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "mode": "isv_brokered",
    "issuer": "https://your-issuer.example.com",
    "key_source": "jwks",
    "jwks_uri": "https://your-issuer.example.com/.well-known/jwks.json",
    "default_scopes": ["crm.read"],
    "jit_enabled": true
  }'
```


## If the exchange fails

- **issuer not trusted** — iss is not the v2 issuer, the row is disabled, or Enterprise Auth is off.
- **direct_xaa requires typ oauth-id-jag+jwt** — Entra sent an access token or ID token, not an ID-JAG. Use the brokered setup, or wait until Entra issues that grant.
- **resource mismatch** — resource is not this region's API host, or you set /mcp when the call is REST.
- **client_id not allowed** — the assertion client_id is not in Allowed client IDs.
- **identity not provisioned** — no email match, or just-in-time mapping is off and the subject is unmapped.
- **assertion replayed** — that jti was already exchanged. Sign a new one.
- **insufficient_scope** — the token's scopes do not cover that category and method.