---
title: "Enterprise-managed authorization (EMA) with a static key"
img: https://s3.us-east-2.amazonaws.com/unified-article-images/enterprise_managed_authorization_ema_with_a_static_key-icon.png
date: 2026-09-22T00:00:00.000Z
updated: "2026-10-02T16:57:40.654Z"
tag: Guides
integrations: 
description: "Use a key pair that you generate yourself to sign enterprise-managed authorization assertions. Register only the public key on the trusted issuer, with key..."
url: "https://docs.unified.to/guides/enterprise_managed_authorization_ema_with_a_static_key"
---

# Enterprise-managed authorization (EMA) with a static key
------
_September 22, 2026_

Use a key pair that you generate yourself to sign enterprise-managed authorization assertions. Register only the public key on the trusted issuer, with key source Static Key. Keep the private key on your backend. Unified.to verifies each assertion against that one public key.


> ⚠️ Paste a PEM that starts with -----BEGIN PUBLIC KEY-----. A PKCS#1 block (-----BEGIN RSA PUBLIC KEY-----) or a private key will fail verification. There is only one registered key, so replacing it stops the previous private key immediately.


## Before you start

- You are a workspace admin.
- This key source is for assertions you sign. It does not use the workspace secret or a JWKS URL.
- Supported algorithms: RS256, RS384, RS512, PS256, PS384, PS512, ES256, ES384, and ES512. RS256 is the usual choice. HS256 is only for the Workspace secret key source.

## 1. Turn the feature on


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


![Screenshot_2026-09-22_at_5.03.31_PM.png](https://s3.us-east-2.amazonaws.com/unified-article-images/enterprise_managed_authorization_ema_with_a_static_key-0.png)


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.


## 2. Generate a key pair


Generate an RSA key on your backend and export the public key in SPKI form. Store `private.pem` where your signing code can read it. Do not send it to Unified.to.


```shell
openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:2048 -out private.pem
openssl pkey -in private.pem -pubout -out public.pem
```


`public.pem` is the value you will paste as the static public key. It looks like this:


```plain text
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A...
-----END PUBLIC KEY-----
```


## 3. Add a trusted issuer


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

- Topology: Brokered (ISV)
- Issuer (iss): the exact issuer string you will put in the assertion, for example https://your-app.example
- Key source: Static key
- Static public key (PEM): the full contents of public.pem, including the BEGIN and END lines
- Allowed client IDs: leave blank for a brokered issuer
- Default scopes: the categories the token should receive when the assertion has no scope, for example crm.read
- Just-in-time identity mapping: on, if you want Unified.to to match the assertion email to a workspace user
- Enabled: on

![Screenshot_2026-09-22_at_5.13.56_PM.png](https://s3.us-east-2.amazonaws.com/unified-article-images/enterprise_managed_authorization_ema_with_a_static_key-1.png)


![Screenshot_2026-09-22_at_5.12.26_PM.png](https://s3.us-east-2.amazonaws.com/unified-article-images/enterprise_managed_authorization_ema_with_a_static_key-2.png)


The same record over the API. Put the PEM in static_public_key as one string, with newline characters:


```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-app.example",
    "key_source": "static_key",
    "static_public_key": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A...\n-----END PUBLIC KEY-----\n",
    "default_scopes": ["crm.read"],
    "jit_enabled": true,
    "enabled": true
  }'
```


A Direct (XAA) issuer can also use Static key. It still needs a non-empty Allowed client IDs list, and each assertion must include a client_id from that list.


## 4. Sign an assertion


Sign with the private key. The header typ must be oauth-id-jag+jwt. The alg must be one of the algorithms listed above and must match the key (RS256 for the RSA key from step 2).


```javascript
const fs = require('fs');
const jwt = require('jsonwebtoken');

const assertion = jwt.sign(
  {
    resource: 'https://api.unified.to',
    email: 'user@your-customer.com',
    scope: 'crm.read crm.write',
    jti: 'assertion-' + Date.now(),
  },
  fs.readFileSync('private.pem'),
  {
    algorithm: 'RS256',
    issuer: 'https://your-app.example',
    audience: 'https://api.unified.to',
    subject: 'customer-user-1',
    expiresIn: '5m',
    header: { alg: 'RS256', typ: 'oauth-id-jag+jwt' },
  }
);
```


Required claims:

- iss matches the trusted issuer string exactly
- 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 id in your app
- 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

## 5. Test, then exchange


On the issuer row, choose Test and paste the assertion. A passing trace means the signature, issuer, audience, resource, 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 or create API keys.


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


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


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


Sign a new assertion for every exchange. The same jti is rejected as a replay. The Test button checks the assertion and does not return an access token, and it does not consume jti.


> ⚠️ 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 /storage/{connection_id}/file, and only when the scope matches the category and the method (storage.read for GET, storage.write for writes).
- MCP: set resource to that same origin plus /mcp, for example https://api.unified.to/mcp. Send the bearer token to the MCP server. Add ?connection={connection_id} to run the call against one connection in the workspace.

```shell
curl https://api.unified.to/storage/$CONNECTION_ID/file \
  -H "Authorization: Bearer $ACCESS_TOKEN"
```


```shell
curl "https://api.unified.to/mcp?connection=$CONNECTION_ID" \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "jsonrpc": "2.0", "id": 1, "method": "tools/list" }'
```


A REST token sent to MCP is refused, and an MCP token sent to the REST API returns token issued for another resource. These tokens cannot manage the workspace, create API keys, or change enterprise-auth settings.


## 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 email matches a user in the workspace. Otherwise the row stays unmapped. Open Identities, choose Map on that subject, and enter the workspace user id. The identity row is created only after an assertion passes signature verification.


## Rotating the key


Generate a new pair, then edit the trusted issuer and replace Static public key with the new public PEM. Assertions signed by the old private key fail as soon as you save. To rotate without a gap, switch the issuer to key source JWKS and publish both keys at jwks_uri.