Private Beta — Remote MCP authentication is currently in private beta. Contact us to get access.
This reference describes the ID-JAG authentication flow. For conceptual understanding of why identity delegation matters, see Why Char Exists and Federated Authentication.
Prerequisites: ID-JAG requires one-time IDP setup. Your IDP admin must register Char as an OAuth client and grant token exchange permissions before the flows described here will work.
Standards
ID-JAG (Identity Assertion Authorization Grant) uses four OAuth/OIDC specifications:
| Standard | Role in ID-JAG |
|---|
| RFC 8693 | Token Exchange - Hub obtains ID-JAG from IDP |
| RFC 7523 | JWT Bearer Grant - Hub exchanges ID-JAG for access token at MCP server |
| RFC 8707 | Resource Indicators - scopes tokens to specific MCP servers |
| RFC 9728 | Protected Resource Metadata - MCP servers advertise auth requirements |
Authentication Flow
Step 1: Token Exchange Request
The Hub requests an ID-JAG from the IDP:
POST /oauth/token HTTP/1.1
Host: your-idp.example.com
Content-Type: application/x-www-form-urlencoded
grant_type=urn:ietf:params:oauth:grant-type:token-exchange
&subject_token={user_id_token}
&subject_token_type=urn:ietf:params:oauth:token-type:id_token
&requested_token_type=urn:ietf:params:oauth:token-type:id-jag
&audience=https://mcp-server.internal.com
&resource=https://mcp-server.internal.com
&scope=mcp:tools:read mcp:tools:call
Step 2: ID-JAG Structure
The IDP returns a JWT with type oauth-id-jag+jwt:
// Header
{
"typ": "oauth-id-jag+jwt",
"alg": "RS256",
"kid": "idp-signing-key-id"
}
// Payload
{
"iss": "https://sso.enterprise.com",
"sub": "[email protected]",
"aud": "https://mcp-server.internal.com",
"client_id": "char-tool-hub",
"resource": "https://mcp-server.internal.com",
"scope": "mcp:tools:read mcp:tools:call",
"exp": 1737100000,
"iat": 1737099700,
"jti": "unique-assertion-id"
}
Step 3: JWT Bearer Grant
The Hub exchanges the ID-JAG for an access token at the MCP server:
POST /oauth/token HTTP/1.1
Host: mcp-server.internal.com
Content-Type: application/x-www-form-urlencoded
grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer
&assertion={id_jag_jwt}
&client_id=char-tool-hub
&client_secret={tool_hub_secret}
Step 4: Access Token Response
{
"access_token": "eyJhbGciOiJSUzI1NiIs...",
"token_type": "Bearer",
"expires_in": 3600
}
IDP Requirements
ID-JAG authentication requires your identity provider to support:
- RFC 8693 Token Exchange — The ability to exchange tokens at the
/token endpoint
- ID-JAG Token Type — Issuing JWTs with
typ: "oauth-id-jag+jwt" and accepting requested_token_type=urn:ietf:params:oauth:token-type:id-jag
Many providers support the underlying Token Exchange primitive but not the specific ID-JAG profile yet.
| Requirement | Notes |
|---|
| Token Exchange (RFC 8693) | Required |
| ID-JAG Token Type | Required for full compatibility |
| Configurable resource servers | For audience scoping |
| JWKS endpoint | For signature validation |
IDP Support Matrix
ID-JAG requires both RFC 8693 (Token Exchange) and support for the oauth-id-jag+jwt token type. Support varies significantly across providers.
| Provider | RFC 8693 | ID-JAG Token Type | Status | Tracking |
|---|
| Okta | ✅ | ⚠️ Early Access | XAA Early Access | Active development |
| PingFederate | ✅ | ❓ Unconfirmed | Full RFC 8693, ID-JAG unverified | — |
| PingOne | ✅ | ❓ Unconfirmed | Full RFC 8693, ID-JAG unverified | — |
| Auth0 | ✅ | ⚠️ Beta | XAA Resource App Beta | Active development |
| Keycloak | ✅ (v26.2+) | ❌ Not yet | Token Exchange GA, ID-JAG pending | #43971 |
| Azure AD / Entra | ❌ | ❌ | Uses proprietary On-Behalf-Of | — |
| AWS Cognito | ❌ | ❌ | No RFC 8693 support | — |
| Google Workspace | ⚠️ Limited | ❌ | Workload Identity Federation only | — |
Legend: ✅ = Supported, ⚠️ = Partial/Early Access, ❌ = Not supported, ❓ = UnconfirmedThis matrix reflects the state as of January 2026. ID-JAG is an emerging IETF draft specification (draft-ietf-oauth-identity-assertion-authz-grant). Provider support is evolving rapidly.
MCP Server Requirements
MCP servers must expose /.well-known/oauth-protected-resource:
{
"resource": "https://mcp-server.internal.com",
"authorization_servers": ["https://mcp-server.internal.com"],
"scopes_supported": ["mcp:tools:read", "mcp:tools:call"]
}
Token Endpoint
MCP servers must implement a token endpoint accepting JWT Bearer Grants:
// Token endpoint implementation
if (url.pathname === '/oauth/token' && request.method === 'POST') {
const body = await request.formData();
const grantType = body.get('grant_type');
// Only accept JWT Bearer Grant
if (grantType !== 'urn:ietf:params:oauth:grant-type:jwt-bearer') {
return Response.json({ error: 'unsupported_grant_type' }, { status: 400 });
}
const assertion = body.get('assertion') as string;
// Validate ID-JAG
const validation = await validateIdJag(assertion, {
trustedIssuers: [env.IDP_ISSUER],
expectedAudience: env.MCP_RESOURCE_ID,
idpJwksUrl: env.IDP_JWKS_URL,
});
if (!validation.valid) {
return Response.json({ error: 'invalid_grant' }, { status: 400 });
}
// Issue access token
const accessToken = await issueAccessToken({
sub: validation.claims.sub,
aud: env.MCP_RESOURCE_ID,
scope: validation.claims.scope,
exp: Math.floor(Date.now() / 1000) + 3600,
}, env.SIGNING_KEY);
return Response.json({
access_token: accessToken,
token_type: 'Bearer',
expires_in: 3600,
});
}
ID-JAG Validation
| Check | Requirement |
|---|
| Signature | Validate against IDP’s JWKS |
typ header | Must be oauth-id-jag+jwt |
iss | Must match trusted IDP issuer |
aud | Must match MCP server’s resource ID |
client_id | Must match expected Tool Hub client |
exp | Must be in future (60s clock skew tolerance) |
Access Token Validation
MCP servers validate incoming access tokens on protected endpoints:
| Claim | Validation |
|---|
iss | Must match MCP server’s token endpoint |
aud | Must match MCP server’s resource ID |
sub | User identifier for authorization |
exp | Must be in future (60s clock skew) |
Optional Claims
| Claim | Purpose |
|---|
email | User email address |
groups | Group memberships |
roles | Role assignments |
scope | Granted capabilities |
Token Properties
| Token Type | Issuer | Audience | Typical Lifetime |
|---|
| User ID Token | Enterprise IDP | Frontend app | 1 hour |
| ID-JAG | Enterprise IDP | MCP server | 5 minutes |
| Access Token | MCP server | MCP server | 5-60 minutes |
Hub Token Caching
Cache key format: token:{org_id}:{connector_id}:{user_id}
interface CachedToken {
access_token: string;
expires_at: number;
refresh_token?: string;
scopes: string[];
}
Cache TTL: min(token_expiry - 300, 3600) seconds.
Tokens are stored in encrypted KV. Never stored in queryable databases.
Transaction Tokens
For MCP server internal service calls, use downscoped Transaction Tokens:
| Property | Requirement |
|---|
| Audience | Bound to specific internal service |
| Scope | Limited to specific operation |
| Lifetime | Seconds to minutes |
| User context | Embedded in signed claims |
External MCP Servers
For MCP servers operated by third parties (not your organization), use OAuth 2.1 with PKCE:
- User explicitly consents via OAuth flow
- Requires registration with the external MCP’s auth server (not your enterprise IDP)
- User identity is established through the external provider’s OAuth, not your SSO
This is the standard OAuth pattern—the user sees a consent screen and authorizes the Tool Hub to act on their behalf with that specific external service.
ID-JAG is for internal MCP servers that trust your enterprise IDP. External MCP servers have their own identity systems and require explicit user consent.
Specification Status
| Component | Status |
|---|
| RFC 8693 (Token Exchange) | Stable |
| RFC 7523 (JWT Bearer Grant) | Stable |
| RFC 8707 (Resource Indicators) | Stable |
| RFC 9728 (Protected Resource Metadata) | Stable |
| ID-JAG Draft | Draft, implementations emerging |
| MCP Enterprise-Managed Authorization | Draft |
References
OAuth/OIDC:
MCP:
See Also