Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
78 changes: 61 additions & 17 deletions .env.example
Original file line number Diff line number Diff line change
@@ -1,9 +1,16 @@
# =============================================================================
# MCP Gateway Registry - Environment Configuration Sample
# MCP Gateway Registry - Environment Configuration Sample
# =============================================================================
# Copy this file to .env and update with your actual values
# Never commit real credentials to version control

# =============================================================================
# DOCKER CONFIGURATION
# =============================================================================
#different options for the home directory e.g. Ubuntu set to /home/ubuntu, set to /opt for mac would require sudo during installation
APP_HOME=/opt
# APP_HOME=/home/ubuntu

# =============================================================================
# REGISTRY CONFIGURATION
# =============================================================================
Expand Down Expand Up @@ -117,24 +124,30 @@ COGNITO_CLIENT_ID=your_cognito_client_id_here
# Get this from Amazon Cognito console > User Pools > App Integration > App clients
COGNITO_CLIENT_SECRET=your_cognito_client_secret_here


# =============================================================================
# MICROSOFT ENTRA ID CONFIGURATION (if AUTH_PROVIDER=entra)
# MICROSOFT ENTRA ID (AZURE AD) OAUTH2 CONFIGURATION (if AUTH_PROVIDER=entra)
# =============================================================================

# Azure AD Tenant ID (Directory/tenant ID from Azure Portal)
# Format: GUID (e.g., 12345678-1234-1234-1234-123456789012)
# Get from: Azure Portal → Azure Active Directory → Overview → Tenant ID
ENTRA_TENANT_ID=your-tenant-id-here

# Entra ID Application (client) ID
# Format: GUID (e.g., 87654321-4321-4321-4321-210987654321)
# Get from: Azure Portal → App registrations → Your App → Application (client) ID
ENTRA_CLIENT_ID=your-client-id-here

# Entra ID Client Secret (Application secret value)
# Get from: Azure Portal → App registrations → Your App → Certificates & secrets
# NOTE: Copy the secret VALUE immediately after creation (not the secret ID)
ENTRA_CLIENT_SECRET=your-client-secret-here
# Microsoft Entra ID Tenant ID
# Get this from Azure Portal > Azure Active Directory > Overview > Tenant ID
# Format: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
# Or use special values:
# - 'common': Multi-tenant (any organizational or personal Microsoft account)
# - 'organizations': Multi-tenant (organizational accounts only)
# - 'consumers': Personal Microsoft accounts only
ENTRA_TENANT_ID=your_tenant_id_here

# Azure AD Application (Client) ID
# Get this from Azure Portal > App Registrations > Your App > Overview > Application (client) ID
# Format: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
ENTRA_CLIENT_ID=your_application_client_id_here

# Azure AD Client Secret
# Get this from Azure Portal > App Registrations > Your App > Certificates & secrets > Client secrets
# IMPORTANT: Copy the SECRET VALUE, not the Secret ID
# Format: xxx~xxxxxxxxxxxxxxxxxxxxxxxxxxxx
ENTRA_CLIENT_SECRET=your_client_secret_value_here

# Enable Entra ID in OAuth2 providers (set to true when using Entra ID)
ENTRA_ENABLED=false
Expand All @@ -145,6 +158,37 @@ ENTRA_GROUP_ADMIN_ID=your-admin-group-object-id-here
# Users Group Example
ENTRA_GROUP_USERS_ID=your-users-group-object-id-here


# Entra ID specific claim mapping
# Set to determine which user info property returned from OpenID Provider to store as the User's username
ENTRA_USERNAME_CLAIM=preferred_username

# Set to determine which group attribute returned from OpenID Provider to filter for group permission
ENTRA_GROUPS_CLAIM=groups

# Set to determine which claim from Entra ID token to use as the User's email address
# - 'email': Standard email claim (requires email scope)
# - 'upn': User Principal Name, format: user@domain.com (recommended for hybrid/on-prem AD sync)
# - 'preferred_username': Preferred username, often same as UPN
# - 'unique_name': Legacy claim for backward compatibility
# Note: Not all Azure AD configurations return all claims. Choose based on your tenant settings.
ENTRA_EMAIL_CLAIM=upn

# Set to determine which user info property returned from OpenID Provider to store as the User's name
ENTRA_NAME_CLAIM=name

# Microsoft Graph API Base URL
# Used for accessing Microsoft Graph API endpoints (user info, groups, etc.)
#ENTRA_GRAPH_URL=https://graph.microsoft.com

# M2M (Machine-to-Machine) Default Scope
#ENTRA_M2M_SCOPE=https://graph.microsoft.com/.default

# Entra offers id,access token kinds
# id token: OIDC get user info, JWT
# access token: OAuth 2.0, Graph API
ENTRA_ROLE_TOKEN_KIND=id

# =============================================================================
# APPLICATION SECURITY
# =============================================================================
Expand Down Expand Up @@ -221,4 +265,4 @@ EXTERNAL_REGISTRY_TAGS=anthropic-registry,workday-asor
# COGNITO_DOMAIN=your-custom-domain.auth.{region}.amazoncognito.com

# Optional: Additional service-specific environment variables
# Add any additional configuration variables your deployment requires
# Add any additional configuration variables your deployment requires
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,9 @@ celerybeat.pid
.env.user
.env.docker

# Docker artifacts
docker-compose.override.yml

# Configuration files with sensitive data
credentials-provider/agentcore-auth/config.yaml
credentials-provider/oauth/config.yaml
Expand Down Expand Up @@ -178,7 +181,7 @@ cython_debug/
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/
.idea/

# Ruff stuff:
.ruff_cache/
Expand Down
Empty file added auth_server/__init__.py
Empty file.
44 changes: 25 additions & 19 deletions auth_server/oauth2_providers.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,25 +36,6 @@ providers:
name_claim: "name"
enabled: true

entra:
display_name: "Microsoft Entra ID"
client_id: "${ENTRA_CLIENT_ID}"
client_secret: "${ENTRA_CLIENT_SECRET}"
auth_url: "https://login.microsoftonline.com/${ENTRA_TENANT_ID}/oauth2/v2.0/authorize"
token_url: "https://login.microsoftonline.com/${ENTRA_TENANT_ID}/oauth2/v2.0/token"
user_info_url: "https://graph.microsoft.com/oidc/userinfo"
logout_url: "https://login.microsoftonline.com/${ENTRA_TENANT_ID}/oauth2/v2.0/logout"
# Request basic OIDC scopes - email and groups require optional claims configuration in Azure Portal
scopes: ["openid", "email", "profile"]
response_type: "code"
grant_type: "authorization_code"
# Claims mapping for user info
username_claim: "preferred_username"
groups_claim: "groups"
email_claim: "email"
name_claim: "name"
enabled: true

github:
display_name: "GitHub"
client_id: "${GITHUB_CLIENT_ID}"
Expand Down Expand Up @@ -89,6 +70,31 @@ providers:
name_claim: "name"
enabled: false # Disabled by default

entra:
display_name: "Microsoft Entra ID"
client_id: "${ENTRA_CLIENT_ID}"
client_secret: "${ENTRA_CLIENT_SECRET}"
# Tenant ID can be specific tenant or 'common' for multi-tenant
tenant_id: "${ENTRA_TENANT_ID}"
auth_url: "https://login.microsoftonline.com/${ENTRA_TENANT_ID}/oauth2/v2.0/authorize"
token_url: "https://login.microsoftonline.com/${ENTRA_TENANT_ID}/oauth2/v2.0/token"
jwks_url: "https://login.microsoftonline.com/${ENTRA_TENANT_ID}/discovery/v2.0/keys"
user_info_url: "https://graph.microsoft.com/v1.0/me"
logout_url: "https://login.microsoftonline.com/${ENTRA_TENANT_ID}/oauth2/v2.0/logout"
scopes: ["openid", "profile", "email", "User.Read"]
response_type: "code"
grant_type: "authorization_code"
# Entra ID specific claim mapping (with sensible defaults)
username_claim: "${ENTRA_USERNAME_CLAIM:-preferred_username}"
groups_claim: "${ENTRA_GROUPS_CLAIM:-groups}"
email_claim: "${ENTRA_EMAIL_CLAIM:-email}"
name_claim: "${ENTRA_NAME_CLAIM:-name}"
# Microsoft Graph API base URL (for sovereign clouds)
graph_url: "${ENTRA_GRAPH_URL:-https://graph.microsoft.com}"
# M2M (Machine-to-Machine) default scope
m2m_scope: "${ENTRA_M2M_SCOPE:-https://graph.microsoft.com/.default}"
enabled: true

# Default session settings
session:
max_age_seconds: 28800 # 8 hours
Expand Down
11 changes: 10 additions & 1 deletion auth_server/providers/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,15 @@
"""Authentication provider package for MCP Gateway Registry."""

from .base import AuthProvider
from .cognito import CognitoProvider
from .entra import EntraIdProvider
from .factory import get_auth_provider
from .keycloak import KeycloakProvider

__all__ = ["AuthProvider", "get_auth_provider"]
__all__ = [
"AuthProvider",
"CognitoProvider",
"EntraIdProvider",
"KeycloakProvider",
"get_auth_provider",
]
70 changes: 36 additions & 34 deletions auth_server/providers/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,12 @@

class AuthProvider(ABC):
"""Abstract base class for authentication providers."""

@abstractmethod
def validate_token(
self,
token: str,
**kwargs: Any
self,
token: str,
**kwargs: Any
) -> Dict[str, Any]:
"""Validate an access token and return user info.

Expand All @@ -42,7 +42,7 @@ def validate_token(
ValueError: If token validation fails
"""
pass

@abstractmethod
def get_jwks(self) -> Dict[str, Any]:
"""Get JSON Web Key Set for token validation.
Expand All @@ -54,12 +54,12 @@ def get_jwks(self) -> Dict[str, Any]:
ValueError: If JWKS cannot be retrieved
"""
pass

@abstractmethod
def exchange_code_for_token(
self,
code: str,
redirect_uri: str
self,
code: str,
redirect_uri: str
) -> Dict[str, Any]:
"""Exchange authorization code for access token.

Expand All @@ -79,17 +79,19 @@ def exchange_code_for_token(
ValueError: If code exchange fails
"""
pass

@abstractmethod
def get_user_info(
self,
access_token: str
self,
access_token: str,
id_token: Optional[str] = None
) -> Dict[str, Any]:
"""Get user information from access token.

Args:
access_token: Valid access token

access_token: OAuth2 access token (required for Graph API calls)
id_token: Optional ID token (preferred for user identity extraction)

Returns:
Dictionary containing user information:
- username: User's username
Expand All @@ -101,13 +103,13 @@ def get_user_info(
ValueError: If user info cannot be retrieved
"""
pass

@abstractmethod
def get_auth_url(
self,
redirect_uri: str,
state: str,
scope: Optional[str] = None
self,
redirect_uri: str,
state: str,
scope: Optional[str] = None
) -> str:
"""Get authorization URL for OAuth2 flow.

Expand All @@ -120,11 +122,11 @@ def get_auth_url(
Full authorization URL
"""
pass

@abstractmethod
def get_logout_url(
self,
redirect_uri: str
self,
redirect_uri: str
) -> str:
"""Get logout URL.

Expand All @@ -135,11 +137,11 @@ def get_logout_url(
Full logout URL
"""
pass

@abstractmethod
def refresh_token(
self,
refresh_token: str
self,
refresh_token: str
) -> Dict[str, Any]:
"""Refresh an access token using a refresh token.

Expand All @@ -153,11 +155,11 @@ def refresh_token(
ValueError: If token refresh fails
"""
pass

@abstractmethod
def validate_m2m_token(
self,
token: str
self,
token: str
) -> Dict[str, Any]:
"""Validate a machine-to-machine token.

Expand All @@ -171,13 +173,13 @@ def validate_m2m_token(
ValueError: If token validation fails
"""
pass

@abstractmethod
def get_m2m_token(
self,
client_id: Optional[str] = None,
client_secret: Optional[str] = None,
scope: Optional[str] = None
self,
client_id: Optional[str] = None,
client_secret: Optional[str] = None,
scope: Optional[str] = None
) -> Dict[str, Any]:
"""Get a machine-to-machine token using client credentials.

Expand All @@ -192,4 +194,4 @@ def get_m2m_token(
Raises:
ValueError: If token generation fails
"""
pass
pass
Loading
Loading