MP-301f · Module 3

Identity Propagation

3 min read

Identity propagation ensures that the end user's identity — not the MCP server's service account — determines what data is accessible. When a sales rep asks the AI about their pipeline, the MCP server should query Salesforce as that sales rep, not as a system administrator with full access. This requires threading the user's identity from the AI client through the MCP server to the enterprise platform. The user authenticates to the AI client, the client passes identity claims to the MCP server, and the server uses those claims to authenticate to the enterprise platform on behalf of the user.

OAuth token exchange is the standard mechanism. The user authenticates to the AI platform with their corporate identity (SSO via SAML or OIDC). The AI platform issues a token scoped to the user. The MCP server receives this token, validates it, and exchanges it for a platform-specific token — using OAuth On-Behalf-Of flow for Salesforce, or impersonation for ServiceNow. The enterprise platform then applies its native access controls (Salesforce sharing rules, ServiceNow ACLs) based on the propagated identity.

interface UserContext {
  userId: string;
  email: string;
  roles: string[];
  platformTokens: Record<string, string>; // platform → access token
}

// Extract user context from MCP client connection
function getUserContext(request: McpRequest): UserContext {
  const authHeader = request.headers?.authorization;
  if (!authHeader) throw new Error("Authentication required");

  // Validate the JWT from the AI platform
  const claims = verifyJwt(authHeader.replace("Bearer ", ""));
  return {
    userId: claims.sub,
    email: claims.email,
    roles: claims.roles ?? [],
    platformTokens: {},
  };
}

// Token exchange — get a Salesforce token for this user
async function getSalesforceToken(user: UserContext): Promise<string> {
  // Check cache first
  if (user.platformTokens.salesforce) {
    return user.platformTokens.salesforce;
  }

  // OAuth On-Behalf-Of exchange
  const response = await fetch(`${SF_LOGIN_URL}/services/oauth2/token`, {
    method: "POST",
    body: new URLSearchParams({
      grant_type: "urn:ietf:params:oauth:grant-type:jwt-bearer",
      assertion: createJwtAssertion(user.email), // Impersonate this user
    }),
  });
  const { access_token } = await response.json();
  user.platformTokens.salesforce = access_token;
  return access_token;
}

// Resource handler with identity propagation
server.resource("my-pipeline",
  "salesforce://my/pipeline",
  async (uri, _params, request) => {
    const user = getUserContext(request);
    const sfToken = await getSalesforceToken(user);

    // Query Salesforce AS this user — their sharing rules apply
    const pipeline = await salesforceQuery(
      sfToken,
      "SELECT Name, Amount, StageName FROM Opportunity WHERE OwnerId = :userId"
    );
    return {
      contents: [{ uri: uri.href, mimeType: "application/json",
        text: JSON.stringify(pipeline) }],
    };
  }
);

Do This

  • Propagate the end user's identity to enterprise platforms — query as the user, not a service account
  • Use OAuth token exchange (On-Behalf-Of) for standards-compliant identity propagation
  • Cache platform tokens per user session to avoid re-authentication on every read
  • Log both the MCP user identity and the platform identity in audit entries

Avoid This

  • Use a single service account for all users — everyone gets the same (usually too broad) access
  • Pass user credentials through the MCP server — use token exchange, never handle passwords
  • Skip identity validation — trust but verify the JWT claims from the AI platform
  • Propagate identity without audit logging — you lose the ability to trace who accessed what