Skip to main content
This reference describes the ID-JAG authentication flow. For conceptual understanding of why identity delegation matters, see Why Char Exists and Federated Authentication.

Standards

ID-JAG (Identity Assertion Authorization Grant) uses four OAuth/OIDC specifications:
StandardRole in ID-JAG
RFC 8693Token Exchange - Hub obtains ID-JAG from IDP
RFC 7523JWT Bearer Grant - Hub exchanges ID-JAG for access token at MCP server
RFC 8707Resource Indicators - scopes tokens to specific MCP servers
RFC 9728Protected 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

RequirementNotes
Token Exchange (RFC 8693)Required
Configurable resource serversFor audience scoping
JWKS endpointFor signature validation

IDP Support Matrix

ProviderToken ExchangeConfiguration
OktaSupportedVia Authorization Servers
Azure ADSupportedVia App Registrations
Auth0SupportedVia APIs
AWS CognitoSupportedVia Resource Servers
KeycloakSupportedBuilt-in
Google WorkspaceLimitedRequires Workload Identity

MCP Server Requirements

Protected Resource Metadata (RFC 9728)

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

CheckRequirement
SignatureValidate against IDP’s JWKS
typ headerMust be oauth-id-jag+jwt
issMust match trusted IDP issuer
audMust match MCP server’s resource ID
client_idMust match expected Tool Hub client
expMust be in future (60s clock skew tolerance)

Access Token Validation

MCP servers validate incoming access tokens on protected endpoints:
ClaimValidation
issMust match MCP server’s token endpoint
audMust match MCP server’s resource ID
subUser identifier for authorization
expMust be in future (60s clock skew)

Optional Claims

ClaimPurpose
emailUser email address
groupsGroup memberships
rolesRole assignments
scopeGranted capabilities

Token Properties

Token TypeIssuerAudienceTypical Lifetime
User ID TokenEnterprise IDPFrontend app1 hour
ID-JAGEnterprise IDPMCP server5 minutes
Access TokenMCP serverMCP server5-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:
PropertyRequirement
AudienceBound to specific internal service
ScopeLimited to specific operation
LifetimeSeconds to minutes
User contextEmbedded in signed claims

Fallback Authentication

When ID-JAG is not available:
MethodUse Case
passthroughForward user’s IDP token directly (legacy compatibility)
serviceService credentials without user context (mTLS, API key)
oauth-pkceUser-consented OAuth for external MCPs
passthrough bypasses audience scoping. Use only for legacy systems that cannot support ID-JAG.

Specification Status

ComponentStatus
RFC 8693 (Token Exchange)Stable
RFC 7523 (JWT Bearer Grant)Stable
RFC 8707 (Resource Indicators)Stable
RFC 9728 (Protected Resource Metadata)Stable
ID-JAG DraftDraft, implementations emerging
MCP Enterprise-Managed AuthorizationDraft

References

OAuth/OIDC: MCP:

See Also