Skip to main content

Authentication & Authorization

Agent Mesh Enterprise secures access through two complementary layers: authentication validates user identity via OAuth2 or SAM Access Tokens, and authorization determines permissions via RBAC scopes. Both the WebUI Gateway and Platform Service share the same authentication middleware, ensuring consistent security across all endpoints.

Architecture Overview

The authentication system consists of three main components:
  1. OAuth2 Service (port 8080): Manages authentication flow with identity providers
  2. WebUI Gateway (port 8000): Handles browser login and serves web interface
  3. Platform Service (port 8001): Provides REST API for programmatic access

Authentication Flow

OAuth2 Configuration

Supported Providers

Agent Mesh Enterprise supports any OAuth2/OIDC-compliant provider:
  • Azure (Microsoft Entra ID): Enterprise identity management
  • Google: Google Workspace integration
  • Auth0: Third-party identity platform
  • Okta: Enterprise SSO provider
  • Keycloak: Self-hosted open-source IAM
  • Custom OIDC: Any standards-compliant provider

OAuth2 Flows

The system implements OAuth2 flows from RFC 6749:

Client Credentials Flow

Server-to-server authentication where the client acts on its own behalf:
from solace_agent_mesh.common.oauth import OAuth2Client

client = OAuth2Client()
token_data = await client.fetch_client_credentials_token(
    token_url="https://auth.example.com/token",
    client_id="your-client-id",
    client_secret="your-client-secret",
    scope="read write"
)
access_token = token_data["access_token"]
Response includes:
  • access_token: The access token string
  • expires_in: Token lifetime in seconds
  • token_type: Usually “Bearer”
  • scope: Granted scopes (optional)
  • expires_at: Unix timestamp when token expires

Authorization Code Flow

User-delegated authentication where the application acts on behalf of a user:
token_data = await client.fetch_authorization_code_token(
    token_url="https://auth.example.com/token",
    client_id="your-client-id",
    client_secret="your-client-secret",
    code="authorization-code-from-callback",
    redirect_uri="https://yourdomain.com/callback"
)
Response includes:
  • access_token: The access token string
  • refresh_token: Token for obtaining new access tokens
  • expires_in: Token lifetime in seconds
  • token_type: Usually “Bearer”

Refresh Token Flow

Obtain new access tokens without user interaction:
token_data = await client.fetch_refresh_token(
    token_url="https://auth.example.com/token",
    client_id="your-client-id",
    client_secret="your-client-secret",
    refresh_token="existing-refresh-token"
)

Provider Configuration

Create oauth2_config.yaml with your provider settings:
enabled: ${OAUTH2_ENABLED:false}
development_mode: ${OAUTH2_DEV_MODE:false}

providers:
  # Azure/Microsoft
  azure:
    issuer: https://login.microsoftonline.com/${AZURE_TENANT_ID}/v2.0
    client_id: ${AZURE_CLIENT_ID}
    client_secret: ${AZURE_CLIENT_SECRET}
    redirect_uri: ${AZURE_REDIRECT_URI:http://localhost:8080/callback}
    scope: "openid email profile offline_access"

  # Google
  google:
    issuer: "https://accounts.google.com"
    client_id: ${GOOGLE_CLIENT_ID}
    client_secret: ${GOOGLE_CLIENT_SECRET}
    redirect_uri: ${GOOGLE_REDIRECT_URI:http://localhost:8080/callback}
    scope: "openid email profile"

  # Auth0
  auth0:
    issuer: ${AUTH0_ISSUER:https://your-domain.auth0.com/}
    client_id: ${AUTH0_CLIENT_ID}
    client_secret: ${AUTH0_CLIENT_SECRET}
    redirect_uri: ${AUTH0_REDIRECT_URI:http://localhost:8080/callback}
    scope: "openid email profile"
    audience: ${AUTH0_AUDIENCE:}  # Optional

  # Okta
  okta:
    issuer: ${OKTA_ISSUER:https://your-okta-domain.okta.com/oauth2/default}
    client_id: ${OKTA_CLIENT_ID}
    client_secret: ${OKTA_CLIENT_SECRET}
    redirect_uri: ${OKTA_REDIRECT_URI:http://localhost:8080/callback}
    scope: "openid email profile"

  # Keycloak
  keycloak:
    issuer: ${KEYCLOAK_ISSUER:https://your-keycloak.com/auth/realms/your-realm}
    client_id: ${KEYCLOAK_CLIENT_ID}
    client_secret: ${KEYCLOAK_CLIENT_SECRET}
    redirect_uri: ${KEYCLOAK_REDIRECT_URI:http://localhost:8080/callback}
    scope: "openid email profile"

# Session configuration
session:
  timeout: ${OAUTH2_SESSION_TIMEOUT:3600}  # 1 hour

# Security settings
security:
  cors:
    enabled: ${OAUTH2_CORS_ENABLED:true}
    origins: ${OAUTH2_CORS_ORIGINS:*}
  rate_limit:
    enabled: ${OAUTH2_RATE_LIMIT_ENABLED:true}
    requests_per_minute: ${OAUTH2_RATE_LIMIT_RPM:60}

OIDC Discovery

The system uses OpenID Connect Discovery to automatically find endpoints:
  1. Provider’s issuer URL points to discovery endpoint
  2. System fetches .well-known/openid-configuration
  3. Authorization, token, and userinfo endpoints discovered automatically
  4. No need to configure individual endpoint URLs

SAM Access Tokens

Overview

SAM Access Tokens are locally signed JWTs that enable efficient authentication:
  • Minted at Login: Created during OAuth callback after role resolution
  • Embedded Roles: Contains user roles and scopes
  • Local Validation: No network call to OAuth2 service
  • ES256 Signed: Using gateway’s ephemeral EC key
  • Configurable TTL: Default 3600 seconds (1 hour)

Token Structure

{
  "header": {
    "alg": "ES256",
    "typ": "JWT",
    "kid": "gateway-key-id"
  },
  "payload": {
    "sub": "[email protected]",
    "sam_user_id": "[email protected]",
    "email": "[email protected]",
    "name": "John Doe",
    "roles": ["data_analyst", "viewer"],
    "provider": "azure",
    "jti": "unique-token-id",
    "iat": 1234567890,
    "exp": 1234571490
  }
}

Configuration

Enable SAM tokens in your gateway configuration:
app_config:
  sam_access_token:
    enabled: true
    ttl_seconds: 3600
    clock_skew_tolerance: 300

Trust Manager

The Trust Manager enables distributed token validation:
  1. Key Generation: Gateway creates ephemeral EC key pair at startup
  2. Trust Card Publication: Public key published to broker in JWKS format
  3. Registry Subscription: Components subscribe and store keys
  4. Local Validation: Tokens validated using registry keys
  5. Periodic Refresh: Keys re-published every 60 seconds
Security Features:
  • Only gateways can sign user identity JWTs
  • Task ID binding prevents cross-task replay
  • Clock skew tolerance for distributed systems
  • Automatic key rotation support

Role-Based Access Control (RBAC)

RBAC Concepts

Users represent identities (typically email addresses):
  • Normalized to lowercase for emails
  • Case-sensitive for non-email identifiers
  • Mapped to roles via configuration
Roles are collections of permissions:
  • Represent job functions or responsibilities
  • Can inherit from other roles
  • Assigned to users in configuration
Scopes are granular permissions:
  • Pattern-based (e.g., tool:data:read)
  • Support wildcards (e.g., tool:*:*)
  • Hierarchical structure

Configuration Files

Role Definitions

Create role-to-scope-definitions.yaml:
roles:
  admin:
    description: "Full system access"
    scopes:
      - "*"

  data_analyst:
    description: "Data analysis specialist"
    scopes:
      - "tool:data:*"
      - "tool:artifact:load"
      - "tool:artifact:create"
      - "agent:data_analysis_agent:delegate"
      - "monitor/namespace/*:a2a_messages:subscribe"

  viewer:
    description: "Read-only access"
    scopes:
      - "tool:basic:read"
      - "tool:artifact:load"
      - "agent:*:delegate"

  developer:
    description: "Developer with tool access"
    inherits:
      - "viewer"
    scopes:
      - "tool:basic:*"
      - "tool:advanced:read"
      - "tool:artifact:create"

User Assignments

Create user-to-role-assignments.yaml:
users:
  [email protected]:
    roles: ["admin"]
    description: "System Administrator"

  [email protected]:
    roles: ["data_analyst"]
    description: "Senior Data Analyst"

  [email protected]:
    roles: ["viewer"]
    description: "Read-only User"

  [email protected]:
    roles: ["developer", "viewer"]  # Multiple roles
    description: "Software Developer"

# Gateway-specific identities (optional)
gateway_specific_identities:
  "_default_enterprise_gateway:[email protected]":
    roles: ["admin"]
    description: "Gateway-specific admin"

Enterprise Configuration

Create enterprise_config.yaml:
authorization_service:
  type: "default_rbac"
  role_to_scope_definitions_path: "config/auth/role-to-scope-definitions.yaml"
  user_to_role_assignments_path: "config/auth/user-to-role-assignments.yaml"

namespace: "enterprise_prod"

Scope Types

Tool Scopes

Control access to tools:
scopes:
  - "tool:basic:read"      # View basic tools
  - "tool:basic:*"         # All basic tool operations
  - "tool:data:*"          # All data tools
  - "tool:advanced:read"   # View advanced tools

Custom Tool Scopes

Define custom scopes in tool configuration:
components:
  - component_name: query_customer_database
    component_module: my_custom_tools
    component_config:
      tool_name: "customer_database_query"
      required_scopes:
        - "tool:database:query"
      database_connection:
        host: "db.example.com"
Grant access via roles:
roles:
  database_admin:
    scopes:
      - "tool:database:*"  # All database tools

Agent Scopes

Control access to specific agents:
scopes:
  - "agent:customer_support_agent:delegate"   # Specific agent
  - "agent:data_*:delegate"                   # All data agents
  - "agent:*:delegate"                        # All agents
Agent scopes are automatically derived from agent_name configuration.

Artifact Scopes

Control access to files and data:
scopes:
  - "tool:artifact:list"    # List artifacts
  - "tool:artifact:load"    # View/download
  - "tool:artifact:create"  # Create new
  - "tool:artifact:append"  # Append to existing
  - "tool:artifact:delete"  # Delete artifacts
  - "tool:artifact:*"       # All artifact operations

Monitoring Scopes

Control access to system monitoring:
scopes:
  - "monitor/namespace/production:a2a_messages:subscribe"  # Specific namespace
  - "monitor/namespace/*:a2a_messages:subscribe"          # All namespaces

Authorization Types

TypeBehaviorUse Case
deny_allReject all accessDefault when no config exists
default_rbacFile-based RBACProduction deployments
customExternal systemIntegration with existing IAM
noneGrant wildcard *Development only
Using type: none grants full access to all users and should never be used in production.

User Identity Resolution

The system extracts user identifiers from token claims in priority order:
sub → client_id → username → oid → preferred_username → upn → 
unique_name → email → name → azp → user_id
Normalization Rules:
  • Email addresses: Converted to lowercase
  • Non-email identifiers: Case-sensitive exact match
  • Fallback: sam_dev_user for development
Display Name Resolution:
name → (given_name + family_name) → preferred_username → user_id

Shared Authentication

WebUI Gateway + Platform Service

Both services use the same authentication stack:
  • Shared Middleware: create_oauth_middleware(component)
  • Shared Registry: MiddlewareRegistry singleton
  • Shared Resolver: EnterpriseConfigResolverImpl
  • Same Configuration: Via SAM_AUTHORIZATION_CONFIG
The Platform Service only inherits RBAC when SAM_AUTHORIZATION_CONFIG is set. Configuring authorization_service only in WebUI Gateway YAML will cause Platform Service to use deny_all.

Configuration Example

WebUI Gateway (webui.yaml):
app_config:
  frontend_use_authorization: ${FRONTEND_USE_AUTHORIZATION}
  external_auth_service_url: ${EXTERNAL_AUTH_SERVICE_URL}
  external_auth_provider: ${EXTERNAL_AUTH_PROVIDER}
  frontend_auth_login_url: ${FRONTEND_AUTH_LOGIN_URL}
  frontend_redirect_url: ${FRONTEND_REDIRECT_URL}
  external_auth_callback_uri: ${EXTERNAL_AUTH_CALLBACK}
Platform Service (platform.yaml):
app_config:
  frontend_use_authorization: ${FRONTEND_USE_AUTHORIZATION}
  external_auth_service_url: ${EXTERNAL_AUTH_SERVICE_URL}
  external_auth_provider: ${EXTERNAL_AUTH_PROVIDER}
Environment Variables:
export SAM_AUTHORIZATION_CONFIG="/app/config/enterprise_config.yaml"
export FRONTEND_USE_AUTHORIZATION="true"
export EXTERNAL_AUTH_SERVICE_URL="http://localhost:8080"
export EXTERNAL_AUTH_PROVIDER="azure"

Token Extraction

The middleware extracts tokens from three sources (in order):
  1. Authorization Header
    Authorization: Bearer <token>
    
  2. Session Cookie
    Cookie: session=<session-id>
    
    Token stored in session under access_token key.
  3. Query Parameter
    GET /api/endpoint?token=<token>
    

Development Mode

For local development, disable authentication:
app_config:
  frontend_use_authorization: false
This assigns a hardcoded development identity:
FieldValue
idsam_dev_user
nameSam Dev User
email[email protected]
auth_methoddevelopment
Development mode bypasses all authentication. Never use in production.

Exempt Paths

These paths bypass authentication for system operations:
PathPurpose
/health, /api/v1/platform/healthHealth checks
/api/v1/configFrontend configuration
/api/v1/auth/callbackOAuth callback
/api/v1/auth/loginLogin initiation
/api/v1/auth/refreshToken refresh
/api/v1/csrf-tokenCSRF protection
/api/v1/auth/tool/callbackTool OAuth
OPTIONS requests also bypass auth for CORS preflight.

Microsoft Graph Integration

For Azure-based environments, integrate with Microsoft Graph:
authorization_service:
  type: "default_rbac"
  role_to_scope_definitions_path: "config/auth/role-to-scope-definitions.yaml"
  user_to_role_provider: "ms_graph"
  
  ms_graph_config:
    ms_graph_tenant_id: ${MS_GRAPH_TENANT_ID}
    ms_graph_client_id: ${MS_GRAPH_CLIENT_ID}
    ms_graph_client_secret: ${MS_GRAPH_CLIENT_SECRET}
Roles are still defined in YAML, but user assignments come from Microsoft Graph.

Troubleshooting

401 Errors on Platform Service

Symptom: WebUI works but Platform Service returns 401. Solution: Set SAM_AUTHORIZATION_CONFIG environment variable.
export SAM_AUTHORIZATION_CONFIG="/app/config/enterprise_config.yaml"

All Requests Denied

Symptom: All requests return 403 after login. Cause: System using deny_all authorization. Solution: Verify enterprise_config.yaml exists and contains authorization_service block.

User Identity Mismatch

Symptom: User cannot access resources despite role assignment. Solution: Check extracted identifier in logs. Email addresses are lowercased automatically.
users:
  [email protected]:  # Matches [email protected]
    roles: ["admin"]

SAM Token Validation Fails

Symptom: Log shows “Token is not a valid sam_access_token”. Normal Behavior: Middleware tries SAM validation first, then falls back to IdP. If Expected: Verify sam_access_token.enabled: true in gateway config.

Best Practices

Security

  1. Use HTTPS in Production
    ssl_cert: "/path/to/cert.pem"
    ssl_key: "/path/to/key.pem"
    
  2. Restrict CORS Origins
    cors:
      origins: "https://yourdomain.com"
    
  3. Configure Session Timeouts
    session:
      timeout: 1800  # 30 minutes
    
  4. Rotate Credentials Regularly
    • OAuth2 client secrets
    • Gateway signing keys
    • Database credentials
  5. Implement Least Privilege
    • Assign minimal scopes per role
    • Use specific scopes over wildcards
    • Regular access reviews

Role Design

  1. Align with Job Functions
    roles:
      customer_support:
        description: "Customer support representative"
        scopes:
          - "agent:customer_support_agent:delegate"
          - "tool:artifact:load"
    
  2. Use Inheritance
    roles:
      operator:
        inherits: ["viewer"]
        scopes:
          - "tool:basic:*"
    
  3. Document Roles
    roles:
      data_analyst:
        description: "Analyst with database and artifact access"
        scopes: [...]
    

Configuration Management

  1. Version Control
    • Store configs in Git
    • Use separate files per environment
    • Never commit secrets
  2. Environment Variables
    client_secret: ${OAUTH_CLIENT_SECRET}
    
  3. Regular Backups
    • Backup RBAC configuration files
    • Store securely off-host
    • Test restore procedures

Reference

Environment Variables

VariablePurposeDefault
SAM_AUTHORIZATION_CONFIGEnterprise config pathNone
FRONTEND_USE_AUTHORIZATIONEnable authenticationfalse
EXTERNAL_AUTH_SERVICE_URLOAuth2 service URLNone
EXTERNAL_AUTH_PROVIDERProvider namegeneric
EXTERNAL_AUTH_CALLBACKOAuth callback URINone
FRONTEND_AUTH_LOGIN_URLLogin endpointNone
FRONTEND_REDIRECT_URLPost-login redirectNone
OAUTH2_ENABLEDEnable OAuth2 servicefalse
OAUTH2_DEV_MODEAllow HTTP (dev only)false

Code Examples

Custom OAuth2 Integration

from solace_agent_mesh.common.oauth import OAuth2RetryClient

client = OAuth2RetryClient(
    max_retries=3,
    backoff_base=2.0,
    backoff_jitter=True
)

token_data = await client.fetch_client_credentials_token(
    token_url="https://auth.example.com/token",
    client_id="your-client-id",
    client_secret="your-client-secret",
    scope="read write",
    verify=True,  # or path to CA cert
    timeout=30.0
)

Building Auth Headers

from solace_agent_mesh.common.auth_headers import build_full_auth_headers

async def fetch_oauth_token(agent_name: str, auth_config: dict) -> str:
    # Your OAuth token fetching logic
    return "oauth_token_xyz"

headers = await build_full_auth_headers(
    agent_name="my-agent",
    agent_config={
        "authentication": {
            "type": "oauth2_client_credentials",
            "token_url": "https://auth.example.com/token",
            "client_id": "client123",
            "client_secret": "secret456"
        }
    },
    custom_headers_key="task_headers",
    oauth_token_fetcher=fetch_oauth_token
)
# Returns: {'Authorization': 'Bearer oauth_token_xyz'}

Next Steps

Security Best Practices

Implement defense-in-depth security

Enterprise Connectors

Connect to external data sources

Build docs developers (and LLMs) love