Federation Exchange Deep Dive
01 / 22

Federation Exchange
Under the Hood

IDP-Agnostic Identity Chain · Governance Enforcement · Error Reference

Works with Auth0 · Okta · Microsoft Entra ID · Any OIDC-compliant IDP

The Challenge

Your AI platform works for internal teams. Now the business needs it to work for everyone else.

Channel partners need the same governed analytics your internal sales team uses. External auditors need read access to compliance data without seeing customer records. Consultants need to query project data for the three months they are engaged, then lose access the day the contract ends.

None of these people are in your corporate directory. They authenticate through their own identity providers: Auth0, Okta, Entra, whatever their organization uses. They will never have accounts on your platform.

Federation Exchange is how you bridge this gap: an external identity provider's JWT is exchanged for a scoped platform token, mapped to a role, and governed by the same Unity Catalog policies that protect your internal users. This deck takes you through exactly how the token flows, where governance is enforced, and what can go wrong.

The Decision

Two Paths for External Users — and Why SPs Win

Your channel partners, external auditors, and consultants need governed access to Databricks. They have their own IDP. They are not in your directory. Two architectural options:

Option A: On-Behalf-Of (OBO)

Each external user is provisioned as a Databricks identity

How it works: External user authenticates → their token is exchanged for a Databricks token that represents them as an individual. current_user() returns their email. Row filters can use current_user() = email.

Pros
  • Individual audit — current_user() = real person
  • Per-user row filters possible (WHERE owner = current_user())
  • Native Databricks identity lifecycle
Blockers
  • User must exist in Databricks (synced via SCIM from your corporate IDP)
  • External users from different orgs can't be SCIM-synced — they're not in your directory
  • Guest account provisioning for every external user — lifecycle management nightmare
  • Cross-tenant IDP federation required at the directory level
  • Doesn't scale: 500 partner reps = 500 Databricks identities to manage

Option B: Role-Based Service Principals

N external users map to O(roles) SPs — no Databricks provisioning

How it works: External user authenticates with their IDP → JWT exchanged for Databricks token scoped to a role-SP. Governance fires via is_member('group'). Real identity preserved in JWT claims + audit.

Why this wins
  • No Databricks user provisioning — external users never touch your directory
  • Scales with roles, not users: 500 reps → 2 SPs (west + east)
  • Works with any OIDC IDP — no cross-tenant federation needed
  • UC governance fires natively via is_member() on SP groups
  • Identity preserved — JWT claims → audit table + MLflow traces
Trade-off
  • current_user() returns SP UUID, not individual email
  • Per-user row filters need is_member() groups, not current_user() = email
  • Individual attribution requires app-level audit (solved by JWT-tagged audit table)

The SP handles authorization. The JWT handles attribution. Together they give you governed access + full audit without provisioning a single Databricks user.

The Mental Model

Three Actors Make Federation Work

Every OIDC IDP has these three components. The names differ, the roles don't.

Actor 1: The API Declaration

"Databricks exists at this address"

Declares the audience — the workspace URL. Goes into the JWT aud claim.

Auth0API (Resource Server)
OktaAuthorization Server
EntraApp Reg → "Expose an API"
// IDP config
{
  "identifier": "https://adb-123.azuredatabricks.net"
}

// JWT claim produced
"aud": "https://adb-123.azuredatabricks.net"

// Databricks federation policy
"audiences": ["https://adb-123.azuredatabricks.net"]

Actor 2: The M2M App

"The machine identity of your app server"

App server uses client_credentials grant. client_id becomes JWT sub claim.

Auth0sub = {id}@clients
Oktasub = {client_id}
Entrasub = {app_id}
// App server token request
POST /oauth/token
{
  "client_id": "your-m2m-client-id",
  "client_secret": "***",
  "audience": "https://adb-123...",
  "grant_type": "client_credentials"
}

// JWT sub claim produced
"sub": "your-m2m-client-id@clients"

// Databricks federation policy
"subject": "your-m2m-client-id@clients"

Actor 3: The SPA App

"Identifies the human at the keyboard"

Browser PKCE login. IDP injects role, email, groups into ID token. Never talks to Databricks.

// You write this inside your IDP:
//   Auth0 → Action (Post-Login)
//   Okta  → Token Inline Hook
//   Entra → Claims Mapping Policy
// It runs at token issuance time.

token.claims["/role"] = user.role;
  // → "west_sales"
token.claims["/email"] = user.email;
  // → "sarah@partner.com"

// ID token the browser receives
{
  "email": "sarah@partner.com",
  "https://fed/role": "west_sales",
  "https://fed/groups": ["sales"]
}

// Your app reads role → picks SP
"west_sales" → SP sp-west-sales-id

The handshake: IDP produces a JWT with iss, aud, sub. Databricks federation policy validates all three. They're mirror images — if any field doesn't match: 401 / 403.

The Architecture

External User → IDP Token → Databricks Token

The external user authenticates with their own IDP. Their JWT is exchanged for a Databricks OAuth token scoped to a role-specific Service Principal. UC row filters, column masks, and connection grants enforce governance. The real external identity is preserved end-to-end for audit.

What Changes Per IDP

  • Token endpoint URL
  • JWKS endpoint URL
  • Custom claims injection method
  • M2M / client_credentials flow setup
  • Token dialect / claim namespace

What Stays the Same

  • Databricks /oidc/v1/token exchange endpoint
  • Federation policy on each SP (issuer + audience + subject)
  • Role → SP mapping
  • UC row filters, column masks, connection grants
  • Audit table, MLflow tracing
End-to-End Sequence

From Login to Governed Data Access

1
User authenticates with IDP

PKCE / Authorization Code flow

2
IDP issues JWT + custom claims

role, email, groups

3
App resolves role → SP

"west_sales" → sp-west-sales-id

4
Token Exchange

IDP JWT → scoped Databricks SP token

5
App calls MCP server

DB token + IDP JWT + role header

6
MCP verifies JWT independently

JWKS check → server-side exchange

7
UC governance fires

Row filter + Column mask + USE CONNECTION

8
Audit with external identity

Email, role, tool, latency recorded

Token Flow Summary

External IdP JWT is exchanged for a scoped Databricks SP token via RFC 8693. UC governance fires per SP group membership at every SQL query.

Token Anatomy

What's in Each Token at Each Stage

Stage 1: IDP JWT

Issued by external IDP

{
  "iss": "https://{idp-domain}/",
  "aud": "https://adb-{ws}.azuredatabricks.net",
  "sub": "{client_id}@clients",
  "exp": 1710612000,
  // Custom claims (namespace varies by IDP)
  "https://ns.example.com/groups": ["sales-west"],
  "https://ns.example.com/role": "sales-west",
  "https://ns.example.com/email": "sarah@partner.com"
}

Algorithm: RS256 · Signed by IDP's private key · Verifiable via JWKS

Stage 2: Databricks Token

From /oidc/v1/token exchange

{
  "access_token": "eyJhbG...",
  "token_type": "Bearer",
  "expires_in": 3600
}
// The access_token is a Databricks
// OAuth token scoped to the role-SP.
// current_user() = SP application_id
// is_member() = SP's group membership

Opaque to the caller · Encodes SP identity · UC enforcement via SP's groups

Stage 3: Governance Context

Derived from SP identity at query time

-- Row filter function
SELECT *
FROM sales.opportunities
-- UC auto-applies:
WHERE is_member('west_sales')
  AND opp_region = 'WEST'

-- Column mask function
CASE WHEN is_member('finance')
  THEN margin_pct
  ELSE NULL END

Fires automatically · No application code needed · Can't be bypassed

Governance Enforcement

7 Enforcement Points in the Chain

#Enforcement PointLayerWhat FiresFailure Mode
1IDP AuthenticationExternal IDPPKCE / password / SSO → JWT issuedLogin failed → no JWT issued
2Claims InjectionIDP Action/HookGroups, role, email injected into JWTMissing claims → role resolution fails at step 5
3Federation Policy ValidationDatabricks /oidc/v1/tokenIssuer + audience + subject checked against SP's policy400/401/403 → no Databricks token
4JWT Signature VerificationAPI Server (Databricks Apps)JWKS-based RS256 verification + audience + issuer401 → request rejected
5Role → SP MappingAPI Server (Databricks Apps)JWT role claim → SP application_id lookup403 → unknown role
6Tool Access ControlAPI Server (Databricks Apps)TOOL_ACCESS dict: role ∈ allowed_roles?ACCESS_DENIED logged to audit
7UC GovernanceDatabricks SQL EngineRow filters (is_member()), column masks, USE CONNECTION grantsFiltered/masked silently — never errors, just restricts

Key Insight: Silent vs. Loud Enforcement

Points 1–5 are loud — they return HTTP errors and block the request. Point 6 is semi-loud — it returns a structured ACCESS_DENIED response and logs to audit. Point 7 is silent — UC row filters and column masks never error; they just restrict what data you see. A query that returns 0 rows is governance working, not a bug.

Error Reference

What Goes Wrong and What It Means

HTTPSourceErrorCauseFix
400App Server (customer)Missing tool or roleRequest body missing tool or role fieldInclude both fields in POST body
400App Server (customer)Unknown roleRole not in ROLE_SP_MAPCheck role key matches config exactly
400/oidc/v1/tokeninvalid_requestMalformed JWT, missing fieldsVerify JWT is well-formed, all params present
401/oidc/v1/tokeninvalid_grantJWT signature invalid, issuer mismatchCheck JWKS keys, issuer URL (trailing slash matters!)
401API Server (Databricks)Invalid IDP tokenJWKS verification failed (expired, wrong key, tampered)Check IDP JWKS endpoint, token expiry, algorithm
403/oidc/v1/tokenOIDC policy validation failedNo federation policy on SP, or subject claim mismatchCreate/update federation policy: issuer + audience + subject
403API Server (Databricks)No role determinedJWT has no role claim, no X-Federation-Role headerAdd role to JWT claims or pass header
403API Server (Databricks)Unknown role: '{role}'Role not in server's ROLE_SP_MAPAdd role to ROLE_SP_MAP env var
429Federation Policy APIRate limitedToo many policy creation requestsExponential backoff: 2n+1 seconds
502App Server / API ServerToken exchange failedDatabricks rejected the exchangeCheck federation policy, SP status, JWT claims
200MCP ToolACCESS_DENIEDRole not in TOOL_ACCESS for this toolExpected behavior — logged to audit table
200SQL Engine0 rows returnedUC row filter excluded all rows for this SP's groupsExpected behavior — governance working correctly
200SQL EngineNULL in columnUC column mask returned NULL for non-privileged groupExpected behavior — column masking active
Troubleshooting

Decision Tree: Where Is It Failing?

IDP & Token Exchange
?
Can you get an IDP JWT?

Test: curl POST {idp}/oauth/token

N
→ IDP config issue

Check: client_id, client_secret, audience, grant_type

?
Does JWT have custom claims?

Test: echo $JWT | cut -d. -f2 | base64 -d | jq

N
→ Claims injection issue

Check IDP's claim injection mechanism: Action, Hook, or Policy

?
Does token exchange succeed?

Test: curl POST {workspace}/oidc/v1/token

N
→ Federation policy issue

Check: policy exists on SP, issuer matches (trailing slash!), audience = workspace URL, subject matches sub claim

API Server & Governance
?
Does API server accept the call?

Check: HTTP status from API endpoint

401
→ JWT verification failed

Check: JWKS endpoint accessible, RS256 algorithm, audience + issuer match server config

403
→ Role mapping failed

Check: JWT has role claim, role key exists in ROLE_SP_MAP config

?
Data returned but empty or masked?
OK
→ Governance is working correctly

0 rows = row filter active
NULL values = column mask active
ACCESS_DENIED = tool access control

Verify: SELECT is_member('group') as the SP

Setup Guide

What to Configure — In Order

Identity Provider Side

1Create an API / Resource Server

Register a custom API with audience = Databricks workspace URL. This tells the IDP: "JWTs for this audience are meant for Databricks."

audience: https://adb-{workspace_id}.azuredatabricks.net

2Create an M2M / Service App

For the external app server to get JWTs via client_credentials grant. This app's client_id becomes the sub claim in the JWT.

3Create a SPA App (if browser login needed)

For end-users to authenticate via PKCE in the browser. Redirect URI = your app's callback URL.

4Configure Users & Groups

Users typically already exist in the IDP. Assign them to groups that map to Databricks roles. Group membership drives which SP the user's token exchanges for.

Groups are native objects in Okta/Entra. In Auth0, groups are stored in app_metadata on each user.

5Add Claims Injection Logic

Inject groups, role, email into the access token. This is the IDP-specific part:

Auth0Action (post-login trigger)
OktaAuthorization Server custom claims
EntraClaims Mapping Policy + App Roles
Databricks Side

6Create Role-Based Service Principals

One SP per role (not per user). Each SP gets a unique application_id and numeric_id.

sp-role-west-sales, sp-role-finance, etc.

7Add SPs to Workspace Groups

Create workspace groups (e.g., west_sales) and add each SP. These groups are what is_member() checks in row filters and column masks.

8Create Federation Policies

On each SP, register the IDP's issuer, audience, and subject. This is what Databricks checks when validating the JWT during token exchange.

Account-level API — requires Databricks account admin.

9Configure UC Governance

Set up row filters (is_member('group')), column masks, and GRANT USE CONNECTION on tables. Grant SPs SELECT on data tables and SQL warehouse access.

10Deploy API Server + External App

API server on Databricks Apps (JWT verification + server-side exchange). External app anywhere (your infra). Configure both with the IDP's domain, client_id, JWKS URI, and the role → SP mapping.

The Connection

How IDP Config Maps to Databricks Config

IDP produces → Federation Policy consumes
IDP ConfigProducesDatabricks Field
IDP Tenant / Domainiss claimPolicy issuer
API / Resource Serveraud claimPolicy audiences
M2M App client_idsub claimPolicy subject
Claims injectionrole, groupsApp-level ROLE_SP_MAP
Create the policy (one API call per SP)
// Databricks Account API
POST accounts/{account_id}
  /servicePrincipals/{sp_id}
  /federationPolicies

{
  "oidc_policy": {
    "issuer":    "https://{idp-domain}/",
    "audiences": ["https://adb-{ws}...net"],
    "subject":   "{m2m_client_id}@clients"
  }
}

// Same 3 fields on all role SPs.
// Swap IDP → update these 3 → done.

The federation policy is the only config that connects the IDP to Databricks. Everything else (SPs, groups, UC governance) is IDP-independent.

Runtime Config

Role → SP Mapping + Grants Checklist

How the app server knows which SP to target

Static config map — created once when you set up the role SPs. Lives in the app server's env config.

// App server config (env var)
ROLE_SP_MAP = {
  "west_sales":  "sp-west-sales-id",
  "east_sales":  "sp-east-sales-id",
  "managers":    "sp-managers-id",
  "executive":   "sp-executive-id",
  "finance":     "sp-finance-id",
  "admin":       "sp-admin-id"
}

// Runtime flow:
// 1. SPA login → IDP token: role="west_sales"
// 2. App looks up ROLE_SP_MAP["west_sales"]
// 3. Token exchange: client_id=sp-west-sales-id

These are Databricks SP application_ids — not IDP client_ids. Created during setup, static thereafter. Only changes when you add/remove roles.

Databricks grants checklist
GrantOnTo
Federation PolicyEach role-SP
CAN_USESQL WarehouseAll SPs
USE CATALOGData catalogAll SPs
USE SCHEMAData schema(s)All SPs
SELECTData tablesRole SPs
SELECTVS indexApp SP
MODIFY + SELECTAudit tableApp SP only
USE CONNECTIONgithub_bearer_tokenExec + Admin
CAN_QUERYServing endpointExec + Admin
CAN_RUNGenie SpaceAll role SPs
Workspace groupGroup per roleEach role SP

Scopes (Token Exchange)

Currently: scope=all-apis. Minimum required per tool:

Role SPssql + genie + serving
App SPsql + vector-search
The Databricks Side

Federation Policy — Two Levels

SP-Level Policy Recommended for Federation Exchange

Policy is scoped to a specific Service Principal. Only that SP can exchange tokens from this IDP.

POST /accounts/{account_id}
     /servicePrincipals/{sp_numeric_id}
     /federationPolicies

When to use:

  • Federation Exchange — each role-SP trusts one IDP with one subject
  • Per-partner access — Partner A's IDP can only exchange for Partner A's SP
  • Least privilege — tight blast radius if credentials leak

Visibility: Account Console → Service principals → {SP} → Credentials & secrets → Federation policies.

Account-Level Policy

Policy applies to any identity in the account. Any SP or user can exchange tokens from this IDP.

POST /accounts/{account_id}
     /federationPolicies

When to use:

  • CI/CD — GitHub Actions, GitLab CI, Azure DevOps OIDC tokens
  • Workload identity — cloud-native services (EKS, GKE) with service account tokens
  • Broad automation — any SP should be able to use this IDP

Visibility: Visible in Account Console → Security → Authentication → Federation policies.

Both levels validate the same 3 fields against the JWT:

Policy Fields (Same for Both Levels)

{
  "oidc_policy": {
    "issuer":    "https://{idp-domain}/",
    "audiences": ["https://adb-{ws}.azuredatabricks.net"],
    "subject":   "{jwt-sub-claim}"
  }
}

Common Gotcha: Trailing Slash

The issuer URL must exactly match the iss claim in the JWT. Most IDPs include a trailing slash: https://login.example.com/. Omitting it = policy validation failure.

Subject Claim by IDP

Auth0 M2M{client_id}@clients
Okta Service{client_id}
Entra App{app_id} (GUID)
GitHub Actionsrepo:{org}/{repo}:ref:refs/heads/main

Token Exchange Parameters (IDP-Agnostic)

grant_type=urn:ietf:params:oauth:grant-type:token-exchange
subject_token={IDP_JWT}
subject_token_type=urn:ietf:params:oauth:token-type:jwt
client_id={SP_APPLICATION_ID}
scope=all-apis
IDP: Auth0

Auth0 Configuration

Endpoints
Tokenhttps://{tenant}.auth0.com/oauth/token
JWKShttps://{tenant}.auth0.com/.well-known/jwks.json
Issuerhttps://{tenant}.auth0.com/
Subject (M2M){client_id}@clients

Claims Injection: Auth0 Action

Trigger: post-login (v3)

const ns = 'https://federation.example.com';
const groups = event.user.app_metadata?.groups || [];
api.accessToken.setCustomClaim(
  `${ns}/groups`, groups);
api.accessToken.setCustomClaim(
  `${ns}/role`, resolveRole(groups));

Auth0-Specific Gotchas

  • Namespace: Must NOT be an Auth0 domain (silently strips claims)
  • Token dialect: Must set access_token_authz on Resource Server
  • SPA → API: Must authorize in Dashboard (not automatable)
  • M2M sub claim: Format is {client_id}@clients

Required Auth0 Apps

M2Mclient_credentials flow for external app
SPAPKCE flow for browser login
MGMTManagement API access (automation only)

Required Auth0 Config

API (Resource Server)Identifier = workspace URL
Actionpost-login: inject claims
Token Dialectaccess_token_authz
ConnectionUsername-Password-Authentication
Usersapp_metadata: groups, company, title

Federation Policy Subject

"subject": "{M2M_CLIENT_ID}@clients"

OAuth Scopes at Each Stage

SPA Loginopenid profile email
Standard OIDC scopes → ID token + user info
M2M Tokenaudience={workspace_url} (implicit)
Auth0 M2M uses audience, not explicit scopes. Token includes all permissions granted to the M2M app for that API.
Token Exchangescope=all-apis
Databricks-side scope. Grants full API access within SP's role. UC governance restricts at data layer.
IDP: Okta

Okta Configuration

Endpoints
Tokenhttps://{org}.okta.com/oauth2/{auth_server_id}/v1/token
JWKShttps://{org}.okta.com/oauth2/{auth_server_id}/v1/keys
Issuerhttps://{org}.okta.com/oauth2/{auth_server_id}
Subject (M2M){client_id} (no @clients suffix)

Claims Injection: Authorization Server Claims

Admin → Security → API → Authorization Server → Claims tab

// Custom claim: "groups"
Name:       groups
Value:      user.getGroups().filter(
              g, g.name.startsWith("fed_"))
Include in: Access Token
Scope:      Any scope

Okta-Specific Gotchas

  • Authorization Server: Use custom (not "default" org server) for custom claims
  • Issuer URL: Includes /oauth2/{server_id} — NOT just the org URL
  • No trailing slash on issuer (unlike Auth0)
  • M2M sub claim: Just {client_id} (no @clients)
  • Group filter: Use Okta Expression Language in claim value

Required Okta Apps

ServiceOAuth 2.0 Service app (client_credentials)
SPAOIDC SPA app (PKCE, authorization code)

Required Okta Config

Authorization ServerCustom server with audience = workspace URL
ScopesAdd custom scope if needed
Claimsgroups, role, email (custom claims on access token)
Access PoliciesRule for service app + SPA app
GroupsAssign users to Okta groups (mapped to Databricks roles)

Federation Policy Subject

"subject": "{SERVICE_APP_CLIENT_ID}"
// No @clients suffix for Okta

OAuth Scopes at Each Stage

SPA Loginopenid profile email + custom scopes
Custom scopes gate which claims are included in token
Service Appscope={custom_scope} on Auth Server
Okta requires at least one scope. Create a custom scope on the Authorization Server (e.g., federation). Access policy rules filter by scope.
Token Exchangescope=all-apis
Same Databricks-side scope regardless of IDP
IDP: Microsoft Entra ID

Entra ID Configuration

Endpoints
Tokenhttps://login.microsoftonline.com/{tenant_id}/oauth2/v2.0/token
JWKShttps://login.microsoftonline.com/{tenant_id}/discovery/v2.0/keys
Issuerhttps://login.microsoftonline.com/{tenant_id}/v2.0
Subject (M2M){app_id} (GUID of the App Registration)

Claims Injection: Claims Mapping Policy

PowerShell / MS Graph API → Claims Mapping Policy

// Claims Mapping Policy (JSON)
{
  "ClaimsMappingPolicy": {
    "Version": 1,
    "IncludeBasicClaimSet": "true",
    "ClaimsSchema": [{
      "Source": "user",
      "ID": "groups",
      "JwtClaimType": "groups"
    }]
  }
}

Entra-Specific Gotchas

  • Issuer format: .../v2.0 (v2 endpoints) vs .../ (v1) — must match exactly
  • App vs Service Principal: App Registration creates both — use App ID for subject
  • Groups claim: Returns GUIDs by default, not names. Use groupMembershipClaims: "SecurityGroup" in manifest
  • Token audience: Set via "Expose an API" → Application ID URI = workspace URL
  • Client secret: Expires (max 2 years). Use certificates for production

Required Entra Config

App Reg (M2M)Client credentials with secret/cert
App Reg (SPA)PKCE redirect, SPA platform
API ScopeExpose an API → Application ID URI = workspace URL
Claims PolicyAssign to Service Principal
Security GroupsMap to Databricks roles

Federation Policy Subject

"subject": "{APP_REGISTRATION_APP_ID}"
// The Application (client) ID GUID
// NOT the Object ID

OAuth Scopes at Each Stage

SPA Loginopenid profile email User.Read
MS Graph delegated permissions for user info
M2M (App)scope={app_id_uri}/.default
Entra uses .default scope pattern. Requests all permissions pre-consented for the app. The app_id_uri = workspace URL.
Token Exchangescope=all-apis
Same Databricks-side scope regardless of IDP

Role Resolution Strategy

Option A: Map Entra Security Group GUIDs to roles in app config
Option B: Use App Roles (defined in App Registration manifest) — appear as roles claim
Option C: Use Claims Mapping to emit group names instead of GUIDs

Comparison

Auth0 vs Okta vs Entra — Side by Side

ConfigurationAuth0OktaEntra ID
Issuer URL https://{tenant}.auth0.com/
trailing slash
https://{org}.okta.com/oauth2/{server_id}
no trailing slash
https://login.microsoftonline.com/{tenant}/v2.0
v2.0 suffix
JWKS URL .../.well-known/jwks.json .../v1/keys .../discovery/v2.0/keys
M2M Sub Claim {client_id}@clients {client_id} {app_id} (GUID)
Claims Injection Auth0 Action (post-login) Authorization Server custom claims Claims Mapping Policy + manifest
Namespace Required? Yes Must be non-Auth0 URL No Top-level claims OK No Top-level claims OK
Token Dialect Fix? Yes access_token_authz No Custom claims included by default No Claims policy handles it
Group Format String names (from app_metadata) String names (Okta groups) GUIDs (Security Groups) by default
M2M App Type Machine-to-Machine Application OAuth 2.0 Service App App Registration + Client Secret/Cert
SPA App Type Single Page Application OIDC SPA App App Registration (SPA platform)
Automation Management API v2 Okta Admin API MS Graph API
SPA Scopes openid profile email openid profile email + custom openid profile email User.Read
M2M Scopes Implicit via audience
No explicit scope param
scope={custom_scope}
Must create on Auth Server
scope={app_id_uri}/.default
.default = all pre-consented
Exchange Scope scope=all-apis ← SAME for all IDPs (Databricks-side)
Free Tier 25k MAU, 7.5k M2M tokens/mo 100 MAU (Workforce), limited M2M Part of Azure AD (included with M365)
Migration

Swapping IDP — Checklist

What You Change

1
IDP Apps

Create M2M + SPA apps in new IDP

2
Claims Injection

Configure groups/role/email claims in new IDP's mechanism

3
Federation Policies

Update issuer and subject on each SP's policy

4
External App Config

Update IDP domain, client_id, JWKS URI, token endpoint, claims namespace

5
API Server Config

Update IDP_DOMAIN, IDP_AUDIENCE, IDP_ISSUER, JWKS_URI env vars

6
Test Users

Create test users with groups/roles in new IDP

What You Keep

Databricks Service Principals

Same SPs, same application_ids, same group membership

UC Row Filters & Column Masks

Same is_member() functions, same tables

UC Connection Grants

Same GRANT/REVOKE USE CONNECTION SQL

Role → SP Mapping

Same ROLE_SP_MAP config (roles are app-defined, not IDP-defined)

Token Exchange Endpoint

Same /oidc/v1/token with same grant_type

Audit Table + MLflow Traces

Same schema, same tags, same correlation

Takeaways

The Identity Chain Is the Architecture

1. IDP Is Swappable

The IDP provides the JWT. Databricks validates it via federation policy. Swap the JWT issuer, update the policy — everything downstream stays the same. The governance layer (UC) is completely IDP-agnostic.

2. Two Token Exchanges, One Identity

External app exchanges IDP JWT → DB token (for proxy auth). MCP server independently exchanges the same IDP JWT → DB token (for SQL execution). Both resolve to the same role-SP. The external user's identity is preserved in JWT claims, audit records, and MLflow traces.

3. Governance Is Native

UC row filters and column masks fire at the SQL engine level. They can't be bypassed by changing the application, the IDP, or the query. is_member() checks the SP's group membership — not the caller's headers.

4. Silent vs. Loud Failures

Auth failures (401/403) are loud and visible. Governance enforcement (0 rows, NULL columns) is silent and correct. Know which one you're looking at before debugging.

5. The Subject Claim Is the Linchpin

90% of federation failures are a mismatch between the JWT's sub claim and the federation policy's subject field. Every IDP formats this differently: client_id@clients (Auth0), client_id (Okta), app_id (Entra). Decode the JWT first, then write the policy.

6. Test the Chain, Not the Parts

A working IDP token + a working federation policy can still fail if the subject claim doesn't match. Test the full chain: get IDP JWT → exchange → call /api/2.0/preview/scim/v2/Me → verify SP identity.

Demo Reference

6 Personas × 9 Tools — Access Matrix

Persona Role Group Regions Margins Identity Regions Sales Genie KB Supervisor GitHub Ext API Audit
Sarah Chen
West Territory Lead
west_saleswest_salesWEST onlyNULLYYYYY
Marcus Johnson
East Territory Lead
east_saleseast_salesEAST onlyNULLYYYYY
David Park
Sales Manager
managersmanagersALLNULLYYYYY
Priya Sharma
VP of Sales
executiveexecutivesALLRealYYYYYYYYY
Lisa Okafor
External Auditor
financefinanceALLRealYYYYYY
Raj Patel
Platform Admin
adminadminALLRealYYYYYYYYY

Governance Layers Demonstrated

  • Row filter: Sarah sees 16 WEST rows, Marcus sees 15 EAST rows, Priya sees all 41
  • Column mask: margin_pct → NULL for sales/managers, real values for finance/exec/admin
  • Tool access: Supervisor + GitHub + External API restricted to executive + admin
  • UC Connection: GRANT/REVOKE USE CONNECTION — instant, no deploy
  • Audit isolation: role-SPs have zero write access to audit table

5 Demo Flows

  1. Data Isolation: Sarah → Genie "top deals" → WEST. Marcus → same → EAST.
  2. Column Masking: Sarah → Sales Data → margins —. Lisa → same → real values.
  3. AI Governance: Sarah → Genie "pipeline by region" → WEST. Priya → all regions.
  4. UC Connections: Priya → GitHub → succeeds. Sarah → ACCESS_DENIED.
  5. Audit Trail: Raj → Audit Log → all actions with external user identity.
Impact

External users get governed access.
Without ever touching Databricks.

0
Databricks users provisioned
N:1
Users to SPs ratio per role
1
SQL statement to toggle access

For the business: Partners, auditors, and consultants get self-service access to exactly the data they need — no IT tickets, no shared credentials, no waiting for provisioning.

For security: Every query runs through UC row filters and column masks. Every action is audited with the real external identity. Access is revocable with one SQL statement.

For engineering: Swap the IDP by updating the federation policy — no code changes. Add a new role by creating one SP and one workspace group. The architecture scales horizontally.

For compliance: External identity preserved end-to-end. Correlatable audit trail across custom tables, MLflow traces, and system.access.audit. No identity gaps.