Skip to content

[FEATURE][AUTH]: Limitation for number of groups that can be fetched with EntraID #2201

@SharkyND

Description

@SharkyND

🧭 Type of Feature

  • Enhancement to existing functionality
  • New feature or capability
  • New MCP-compliant server
  • New component or integration
  • Developer tooling or test improvement
  • Packaging, automation and deployment (ex: pypi, docker, quay.io, kubernetes, terraform)
  • Other (please describe below)

🧭 Epic

Title: Handle Azure AD/Entra ID Groups Overage Claims for Enterprise SSO Authentication

Goal: Enable admin group assignment and role mappings to work correctly for enterprise users with 100+ Azure AD group memberships by implementing Microsoft Graph API calls to retrieve groups when overage claims are detected.

Why now: Organizations with large Azure AD deployments cannot use admin groups or role mappings features because users with many group memberships (>200) trigger groups overage claims. This results in silent failures where users are created without proper permissions, requiring manual database updates as a workaround. This affects enterprise adoption and security posture.


🙋♂️ User Story 1: Admin Group Assignment with Groups Overage

As a: Platform Administrator configuring SSO authentication

I want: Users in designated Azure AD admin groups to automatically receive admin privileges upon SSO login

So that: I don't have to manually update the database to grant admin access to users who have 100+ total Azure AD group memberships

✅ Acceptance Criteria

Scenario: User with groups overage claim logs in for first time
  Given a user "alice@enterprise.com" with 150 Azure AD group memberships
  And the user is a member of admin group "89f32b79-914c-4322-aa3f-6c95765e2073"
  And SSO_ENTRA_ADMIN_GROUPS is configured as ["89f32b79-914c-4322-aa3f-6c95765e2073"]
  And the Azure AD token contains _claim_names and _claim_sources instead of direct groups
  When the user completes SSO authentication
  Then the application should detect the groups overage claim
  And the application should call Microsoft Graph API to retrieve all user groups
  And the user should be created with is_admin=true
  And a log entry should indicate "Retrieved {count} groups from Graph API for alice@enterprise.com"

Scenario: User with groups overage claim logs in on subsequent visits
  Given a user with groups overage claim already exists with is_admin=false
  And the user is a member of an admin group
  When the user logs in via SSO
  Then the application should retrieve groups from Graph API
  And the user's is_admin status should be upgraded to true
  And a log entry should indicate "Upgrading is_admin to True for {email} based on SSO admin groups"

Scenario: User with groups overage claim is not in admin group
  Given a user with 150 Azure AD group memberships
  And the user is NOT a member of any configured admin groups
  When the user completes SSO authentication
  Then the application should retrieve groups from Graph API
  And the user should be created with is_admin=false
  And no admin upgrade should occur

🙋♂️ User Story 2: Role Mappings with Groups Overage

As a: Platform Administrator managing RBAC via Azure AD groups

I want: SSO_ENTRA_ROLE_MAPPINGS to apply correctly for users with groups overage claims

So that: Users automatically receive appropriate RBAC roles based on their Azure AD group memberships regardless of total group count

✅ Acceptance Criteria

Scenario: User with groups overage receives mapped roles
  Given a user with 150 Azure AD group memberships
  And the user is a member of groups ["group-admin", "group-developer"]
  And SSO_ENTRA_ROLE_MAPPINGS is configured as:
    """
    {
      "group-admin": "platform_admin",
      "group-developer": "developer"
    }
    """
  And the Azure AD token contains _claim_sources for groups
  When the user logs in via SSO
  Then the application should retrieve groups from Graph API
  And the user should be assigned both "platform_admin" and "developer" roles
  And a log entry should indicate role assignments

Scenario: User with groups overage loses role when removed from group
  Given a user with groups overage claim has role "developer" from "group-developer"
  And the user is removed from "group-developer" in Azure AD
  When the user logs in via SSO again
  Then the application should retrieve updated groups from Graph API
  And the "developer" role should be revoked
  And a log entry should indicate "Revoked SSO role 'developer' from {email}"

🙋♂️ User Story 3: Graceful Degradation When Graph API Fails

As a: Platform Administrator

I want: The application to handle Graph API failures gracefully without breaking SSO authentication

So that: Users can still log in even if group retrieval fails, with appropriate error logging for troubleshooting

✅ Acceptance Criteria

Scenario: Graph API call fails with 401 Unauthorized
  Given a user with groups overage claim
  And the Graph API call returns HTTP 401
  When the user attempts SSO login
  Then the user should still be created/authenticated
  And is_admin should remain false (safe default)
  And an error log should indicate "Failed to retrieve groups from Graph API: HTTP 401"
  And the error log should suggest checking API permissions

Scenario: Graph API call times out
  Given a user with groups overage claim
  And the Graph API call times out after 30 seconds
  When the user attempts SSO login
  Then the user should still be created/authenticated
  And is_admin should remain false
  And an error log should indicate the timeout

📐 Design Sketch

Current Behavior (Broken)

sequenceDiagram
    participant User
    participant Gateway as MCP Gateway
    participant EntraID as Azure AD
    participant DB as Database

    User->>Gateway: SSO Login
    Gateway->>EntraID: OAuth Token Exchange
    EntraID-->>Gateway: ID Token with _claim_sources (100+ groups)
    Gateway->>Gateway: Detect groups overage
    Gateway->>Gateway: Log warning, groups=[]
    Gateway->>DB: CREATE USER (is_admin=false)
    Note over Gateway,DB: ❌ Admin group check fails<br/>❌ Role mappings fail
    Gateway-->>User: Login successful (no admin access)
Loading

Proposed Behavior (Fixed)

sequenceDiagram
    participant User
    participant Gateway as MCP Gateway
    participant EntraID as Azure AD
    participant GraphAPI as Microsoft Graph API
    participant DB as Database

    User->>Gateway: SSO Login
    Gateway->>EntraID: OAuth Token Exchange
    EntraID-->>Gateway: ID Token with _claim_sources
    Gateway->>Gateway: Detect _claim_sources in token
    Gateway->>GraphAPI: POST /users/{id}/getMemberObjects
    Note over Gateway,GraphAPI: Use access_token from OAuth
    GraphAPI-->>Gateway: {"value": ["group-1", "group-2", ...]}
    Gateway->>Gateway: Check groups vs SSO_ENTRA_ADMIN_GROUPS
    Gateway->>Gateway: Map groups to roles via SSO_ENTRA_ROLE_MAPPINGS
    Gateway->>DB: CREATE USER (is_admin=true, roles assigned)
    Note over Gateway,DB: ✅ Admin group check works<br/>✅ Role mappings work
    Gateway-->>User: Login successful (admin access granted)
Loading

🔗 MCP Standards Check

  • Change adheres to current MCP specifications
  • No breaking changes to existing MCP-compliant integrations
  • If deviations exist, please describe them below:

N/A - This enhancement only affects SSO authentication flow and does not modify any MCP protocol interactions.


🔄 Alternatives Considered

Alternative 1: Azure AD Group Filtering (Attempted - Does NOT Work)

  • Approach: Configure groupMembershipClaims: "ApplicationGroup" and assign only specific groups to the Enterprise Application
  • Why rejected: Microsoft's documentation reveals a hidden limitation: "Group filtering only applies if a user belongs to 1,000 or fewer groups (including direct and transitive memberships)". Users with 100+ groups still trigger overage even with filtering enabled.
  • Evidence: Tested with a user having 100+ memberships, only 1 group assigned to app, groupMembershipClaims: "ApplicationGroup" configured - still created with is_admin=false.

Alternative 2: Use App Roles Instead of Groups (Limited Use Case)

  • Approach: Create Azure AD App Roles (e.g., "admin" role), assign groups to roles, use SSO_ENTRA_GROUPS_CLAIM: "roles"
  • Why rejected: Only works for simple single-role scenarios. Doesn't support SSO_ENTRA_ROLE_MAPPINGS with multiple group-to-role mappings. Users need different roles from different groups (e.g., admin from one group, developer from another).

Alternative 3: Manual Database Updates (Current Workaround)

  • Approach: UPDATE email_users SET is_admin = true WHERE email = 'user@domain.com'
  • Why rejected: Not scalable, requires manual intervention for each user, breaks on re-login if user is deleted/recreated.

Alternative 4: Reduce User's Group Memberships (Not Practical)

  • Approach: Remove user from unnecessary Azure AD groups to get below overage threshold
  • Why rejected: Enterprise users need those groups for other systems (file shares, applications, etc.). Not feasible in large organizations.

📓 Additional Context

Current Code Detection (Already Exists):

The application already detects groups overage on lines 765-772 of mcpgateway/services/sso_service.py:

claim_names = id_token_claims.get("_claim_names", {})
if isinstance(claim_names, dict) and "groups" in claim_names:
    logger.warning(
        f"Group overage detected for user {user_email} - "
        f"token contains too many groups (>200). "
        f"Role mapping may be incomplete. Consider using App Roles or Azure group filtering. "
        f"See docs/docs/manage/sso-entra-role-mapping.md#token-size-considerations"
    )

What's Missing: The code detects the problem but doesn't solve it - it just logs a warning and proceeds with empty groups.

Microsoft Documentation:

Affected Code Locations:

  • Detection: mcpgateway/services/sso_service.py lines 765-772
  • Groups extraction: mcpgateway/services/sso_service.py lines 773-775
  • Admin check: mcpgateway/services/sso_service.py lines 1016-1023 (_should_user_be_admin)
  • Role mapping: mcpgateway/services/sso_service.py lines 1006-1075 (_map_groups_to_roles)

Test Environment:

  • MCP Context Forge Version: 1.0.0-BETA-1
  • Deployment: Kubernetes (AWS EKS)
  • SSO Provider: Microsoft Entra ID (Azure AD)
  • Tenant: Enterprise production environment
  • User Group Count: 100+ direct + transitive memberships
  • Groups Assigned to App: 1 (admin group)
  • Configuration: groupMembershipClaims: "ApplicationGroup", optional claims with groups

Impact Scope:

  • ✅ SSO authentication flow (works)
  • ✅ User auto-creation (works)
  • ✅ Personal team creation (works)
  • ❌ Admin group assignment via SSO_ENTRA_ADMIN_GROUPS (fails silently)
  • ❌ Role mappings via SSO_ENTRA_ROLE_MAPPINGS (fails silently)

Organizations Affected:

  • Large enterprises with complex Azure AD structures
  • Organizations with 100+ security groups per user
  • Teams using Azure AD group-based access control
  • Multi-tenant environments with inherited group memberships

Would you accept a pull request for this feature?

Current Behavior

What happens:

  1. User with 100+ Azure AD groups authenticates via SSO
  2. Azure AD returns _claim_names and _claim_sources instead of direct groups array in the token
  3. Application logs warning: "Group overage detected for user {email} - token contains too many groups (>200)"
  4. user_data["groups"] is empty (no groups extracted from token)
  5. _should_user_be_admin() fails → user created with is_admin=false despite being in admin group
  6. _map_groups_to_roles() fails → no RBAC role assignments despite group-to-role mappings

Configuration that fails:

SSO_ENTRA_ADMIN_GROUPS: ["89f32b79-914c-4322-aa3f-6c95765e2073"]

Expected Behavior

When groups overage is detected, the application should:

  1. Check for _claim_names and _claim_sources in the ID token
  2. Extract the Graph API endpoint from _claim_sources.src1.endpoint
  3. Call the Microsoft Graph API to retrieve the user's actual group memberships
  4. Use the retrieved groups for admin checks and role mappings

Technical Details

Token structure with groups overage:

{
  "_claim_names": {
    "groups": "src1"
  },
  "_claim_sources": {
    "src1": {
      "endpoint": "https://graph.microsoft.com/v1.0/users/{user_id}/getMemberObjects"
    }
  }
}

Microsoft Documentation:

Code Location:

  • Detection: mcpgateway/services/sso_service.py lines 765-772
  • Groups extraction: mcpgateway/services/sso_service.py lines 773-775
  • Admin check: mcpgateway/services/sso_service.py lines 1016-1023
  • Role mapping: mcpgateway/services/sso_service.py lines 1006-1075

Reproduction Steps

  1. Configure Azure AD app with admin group and role mappings
  2. Authenticate as user with 100+ Azure AD group memberships
  3. User is created successfully but is_admin=false
  4. No RBAC roles assigned despite role mappings configuration

Environment

  • MCP Context Forge Version: 1.0.0-BETA-1
  • Deployment: Kubernetes (AWS EKS)
  • SSO Provider: Microsoft Entra ID (Azure AD)
  • User Group Count: 100+

Proposed Solution

Add groups overage handling to _get_user_info() method in sso_service.py:

# After detecting groups overage (line 772)
if isinstance(claim_names, dict) and "groups" in claim_names:
    user_email = user_data.get("email") or user_data.get("preferred_username") or "unknown"
    logger.warning(
        f"Group overage detected for user {user_email} - "
        f"token contains too many groups (>200). "
        f"Calling Graph API to retrieve groups..."
    )
    
    # Extract Graph API endpoint from _claim_sources
    claim_sources = id_token_claims.get("_claim_sources", {})
    src1 = claim_sources.get("src1", {})
    groups_endpoint = src1.get("endpoint")
    
    if groups_endpoint:
        # Call Graph API to get all groups
        from mcpgateway.services.http_client_service import get_http_client
        client = await get_http_client()
        response = await client.post(
            groups_endpoint,
            json={"securityEnabledOnly": False},
            headers={"Authorization": f"Bearer {access_token}"}
        )
        
        if response.status_code == 200:
            groups_data = response.json()
            user_data["groups"] = groups_data.get("value", [])
            logger.info(f"Retrieved {len(user_data['groups'])} groups from Graph API for {user_email}")
        else:
            logger.error(f"Failed to retrieve groups from Graph API: HTTP {response.status_code}")

Workarounds

Current workarounds available to users:

  1. Manual admin assignment:

    UPDATE email_users SET is_admin = true WHERE email = 'user@domain.com';
  2. Azure AD Group Filtering:

    • Configure Azure AD to only emit specific security groups in the token
    • Reduces group count to avoid overage threshold
  3. App Roles (limited):

    • Use Azure AD App Roles instead of groups for admin-only scenarios
    • Doesn't work if you need multiple group-to-role mappings

Impact

Affected Features:

  • ✅ SSO authentication (works)
  • ✅ User auto-creation (works)
  • ✅ Personal team creation (works)
  • ❌ Admin group assignment (SSO_ENTRA_ADMIN_GROUPS)
  • ❌ Role mappings (SSO_ENTRA_ROLE_MAPPINGS)

User Impact:

  • Organizations with large Azure AD deployments (100+ groups per user)
  • Enterprise environments with complex RBAC requirements
  • Silent failure - users created without proper permissions

Additional Context

The warning message on line 768-772 suggests using App Roles or Azure group filtering, but:

  • App Roles don't support multiple group-to-role mappings (users need different roles from different groups)
  • Group filtering is a manual Azure AD configuration burden for each deployment

Implementing groups overage handling would provide a better out-of-box experience for enterprise Azure AD environments.

Related Files

  • mcpgateway/services/sso_service.py - SSO service implementation
  • docs/docs/manage/sso-entra-role-mapping.md - Entra role mapping documentation (referenced in warning)

Would you accept a pull request for this feature?

Metadata

Metadata

Assignees

Labels

SHOULDP2: Important but not vital; high-value items that are not crucial for the immediate releaseenhancementNew feature or requestpythonPython / backend development (FastAPI)securityImproves security

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions