About Fastmcp Auth Entraid
Fastmcp Auth Entraid is an MCP server published by thiagorufino1 in the Security category.
Recent commit activity is the strongest signal that reported issues will get attention.
Install
git clone https://github.com/thiagorufino1/fastmcp-entra-id-authThis server has no ConduID identity, so agent calls to it are not receipted. Pin the version you install and review the source before granting it credentials.
Ask AI
Ask AI about Fastmcp Auth Entraid
Powered by Claude · Grounded in docs
Security checks
- ✓README presentThe repository ships documentation.
5 checks have not been run yet.
README
🤖 MCP Server com Autenticação Microsoft Entra ID
Implementação de referência de um servidor FastMCP protegido com Microsoft Entra ID (Azure AD) utilizando App Roles e controle de acesso baseado em grupos de segurança.
Este projeto demonstra como:
- 🔐 Proteger um servidor MCP com validação de JWT do Entra ID
- 🎭 Aplicar controle de acesso por ferramenta usando App Roles
- 👥 Gerenciar acesso via grupos de segurança do Azure AD sem alterações de código
- 🧠 Filtrar
tools/listpara que a LLM veja apenas as ferramentas que o usuário pode executar
🛡️ Visão geral de segurança
Esta seção destina-se a arquitetos de segurança que precisam avaliar a postura de segurança do projeto antes da aprovação para uso corporativo.
Modelo de ameaças e mitigações
| Ameaça | Mitigação implementada |
|---|---|
| Acesso sem autenticação | Toda requisição exige JWT assinado pelo Entra ID |
| Token forjado | Assinatura validada contra JWKS público do Entra (chaves rotacionadas pelo Azure) |
| Token v1.0 com issuer diferente | Verifier rejeita qualquer issuer diferente de login.microsoftonline.com (v2.0) |
| Usuário sem permissão conecta e enumera ferramentas | Conexão rejeitada com 401 antes de qualquer resposta MCP |
| Usuário com permissão parcial invoca ferramenta proibida | LLM não vê a ferramenta e execução bloqueada em segunda camada |
| Elevação de privilégio via claim manipulado | Claims vêm do JWT assinado pelo Azure, não são aceitos do cliente |
| Token roubado reutilizado | Janela de validade de 1 hora; revogação por remoção de grupo reflete no próximo token |
| Autoatribuição de roles pelo usuário | App Roles são atribuídas exclusivamente por administradores via Azure AD |
🏗️ Arquitetura
Cliente MCP (portal, CLI, App Service com OBO)
|
| Authorization: Bearer <JWT Entra ID v2.0>
v
+----------------------------------------------------+
| FastMCP Server |
| |
| Camada 1: RoleEnforcedJWTVerifier |
| - valida assinatura contra JWKS do Entra |
| - valida issuer, audience e versão do token |
| - rejeita 401 se nenhuma role válida presente |
| |
| Camada 2: tools/list filtrado por auth= |
| - LLM recebe apenas ferramentas permitidas |
| |
| Camada 3: tools/call verificado por auth= |
| - execução bloqueada se role insuficiente |
+----------------------------------------------------+
|
| valida JWT contra JWKS público
v
Microsoft Entra ID
(assinatura RS256, rotação automática de chaves)
Camadas de segurança em detalhe
| Camada | Mecanismo | Resultado em falha |
|---|---|---|
| Assinatura JWT | AzureJWTVerifier valida RS256 contra JWKS do Entra |
401 Unauthorized |
| Versão do token | Exige v2.0 (iss: login.microsoftonline.com) |
401 Unauthorized |
| Audience | Valida que aud = CLIENT_ID da aplicação |
401 Unauthorized |
| Presença de role | RoleEnforcedJWTVerifier verifica claim roles |
401 Unauthorized |
| Visibilidade de ferramentas | auth= por ferramenta filtra tools/list |
Ferramenta oculta da LLM |
| Execução de ferramentas | auth= por ferramenta bloqueia tools/call |
Erro de autorização MCP |
🎯 Modelo de controle de acesso
O acesso é controlado via App Roles do Azure AD atribuídas a grupos de segurança, sendo necessárias apenas mudanças na associação de grupos para conceder ou revogar acesso, sem alterações de código.
Grupos do Azure AD App Roles Ferramentas
mcp-trc-read (grupo) ──► mcp-trc-read ──► soma
mcp-trc-admin (grupo) ──► mcp-trc-admin ──► soma
──► health_check
Por que App Roles e não OAuth Scopes?
OAuth Scopes (scp) |
App Roles (roles) |
|
|---|---|---|
| Quem controla o acesso | App define, usuário consente | Admin (atribui roles aos grupos) |
| Revogação | Usuário revoga consentimento | Admin revoga via associação de grupo |
| Adequado para | Permissões delegadas pelo usuário | RBAC corporativo |
| Auditável centralmente | Parcialmente | Sim, via Azure AD |
As App Roles aparecem como array roles no JWT, sem necessidade de consentimento do usuário, controlado exclusivamente pelo administrador.
Por que o scope access_as_user?
O fluxo delegado exige pelo menos um OAuth scope para o cliente obter um token. O scope access_as_user funciona como porta de entrada, permitindo que clientes se autentiquem contra a API. A autorização real (quais ferramentas chamar) é aplicada pelas App Roles, não pelo scope.
🔄 Fluxo de autenticação por requisição
1. Cliente envia: POST /mcp
Authorization: Bearer <entra-jwt>
2. RoleEnforcedJWTVerifier executa em sequência:
a. Valida assinatura JWT contra endpoint JWKS do Entra
b. Valida issuer = https://login.microsoftonline.com/<tenant>/v2.0
c. Valida audience = <client-id> da aplicação
d. Valida que scp inclui "access_as_user"
e. Verifica que claim roles contém ao menos um de: mcp-trc-read, mcp-trc-admin
→ Qualquer falha retorna 401 Unauthorized e encerra a requisição
3. Cliente chama tools/list:
→ FastMCP avalia auth= de cada ferramenta contra o claim roles do token
→ Usuários com mcp-trc-read: recebem apenas [soma]
→ Usuários com mcp-trc-admin: recebem [soma, health_check]
4. Cliente chama tools/call:
→ auth= verificado novamente antes da execução
→ Chamada não autorizada retorna AuthorizationError
📊 Matriz de acesso por ferramenta
| Ferramenta | mcp-trc-read |
mcp-trc-admin |
Sem role |
|---|---|---|---|
soma |
✅ visível + executável | ✅ visível + executável | ❌ 401 na conexão |
health_check |
❌ oculta da LLM | ✅ visível + executável | ❌ 401 na conexão |
A LLM nunca vê ferramentas que o usuário não pode executar, eliminando tentativas de invocação não autorizada.
🔀 Modos de autenticação
O FastMCP suporta dois modos, alternados via AUTH_MODE no .env. O controle de acesso via App Roles e grupos funciona identicamente nos dois modos.
⚠️ Apenas um modo pode estar ativo por instância do servidor.
Modo A: JWT Verifier (AUTH_MODE=jwt) — padrão recomendado para ACA
Usuário → [obtém token Azure externamente] → Cliente MCP → FastMCP (valida JWT)
O cliente obtém token do Entra ID diretamente (via OBO, MSAL, Azure CLI) e envia como Bearer. O FastMCP apenas valida, sem redirecionar nem emitir tokens.
Indicado para: portais customizados, App Service com OBO, Azure Container Apps, automações.
Como obter token para testes:
az account get-access-token `
--scope "api://<CLIENT_ID>/access_as_user" `
--query accessToken -o tsv
Modo B: OAuth Proxy (AUTH_MODE=oauth)
Usuário → Cliente MCP → FastMCP (redireciona para login Azure) → Azure → FastMCP → Cliente
FastMCP atua como servidor de autorização OAuth, fazendo proxy do login para o Entra ID. Browser abre automaticamente para login. O cliente não precisa gerenciar tokens manualmente.
Indicado para: Claude Desktop, Cursor, extensões VS Code, qualquer cliente com suporte OAuth 2.0 nativo.
Requer registro de redirect URI no App Registration:
# Desenvolvimento local
az ad app update --id <CLIENT_ID> `
--web-redirect-uris "http://localhost:8000/auth/callback"
# Produção (ACA)
az ad app update --id <CLIENT_ID> `
--web-redirect-uris "https://<seu-app>.azurecontainerapps.io/auth/callback"
Endpoints publicados automaticamente pelo FastMCP no modo oauth:
/.well-known/oauth-authorization-server metadados do servidor OAuth
/.well-known/oauth-protected-resource/mcp metadados do recurso protegido
/auth/callback callback do fluxo de autorização
Comparativo dos modos
JWT Verifier (jwt) |
OAuth Proxy (oauth) |
|
|---|---|---|
| Emissor do token | Microsoft Entra ID | FastMCP via proxy do Entra |
| Requisito do cliente | Deve fornecer Bearer token | Deve suportar OAuth 2.0 |
| App Roles e grupos AD | ✅ | ✅ |
client_secret obrigatório |
❌ | ✅ |
| Redirect URI no App Registration | ❌ | ✅ |
| Fluxo de login via browser | ❌ externo | ✅ nativo |
| Azure Container Apps com MI | ✅ ideal | ⚠️ mais complexo |
| Aquisição de token | Cliente/orchestrador (automatizável via MSAL/MI/OBO) | Automática |
Para alternar entre modos, edite AUTH_MODE no .env e reinicie o servidor. Nenhuma alteração de código é necessária.
📁 Estrutura do projeto
mcp-container-apps/
├── .env # segredos (nunca commitar)
├── .env.example # template com documentação
├── requirements.txt
└── app/
├── __init__.py
├── __main__.py # ponto de entrada: python -m app
├── main.py # montagem do servidor
├── config.py # variáveis de ambiente + constantes de roles
├── auth/
│ ├── __init__.py
│ ├── verifier.py # RoleEnforcedJWTVerifier, build_auth_provider()
│ └── checks.py # fábrica de AuthCheck require_roles()
└── tools/
├── __init__.py # register_tools()
├── health.py # health_check, exige mcp-trc-admin
└── soma.py # soma, exige mcp-trc-read ou mcp-trc-admin
✅ Pré-requisitos
- Python 3.11+
- Azure CLI (
az): guia de instalação - Tenant Azure com permissão para criar App Registrations e grupos de segurança
🔧 Passo 1: App Registration no Azure
1.1 Login
az login --tenant "<SEU_TENANT_ID>"
1.2 Criar o App Registration
APP=$(az ad app create \
--display-name "mcp-lab" \
--sign-in-audience AzureADMyOrg \
--query "{appId:appId, objectId:id}" \
-o json)
CLIENT_ID=$(echo $APP | jq -r '.appId')
OBJECT_ID=$(echo $APP | jq -r '.objectId')
PowerShell:
$APP = az ad app create --display-name "mcp-lab" --sign-in-audience AzureADMyOrg --query "{appId:appId, objectId:id}" -o json | ConvertFrom-Json
$CLIENT_ID = $APP.appId
$OBJECT_ID = $APP.objectId
1.3 Definir URI identificador e versão do token
O URI identificador (api://<client-id>) é obrigatório para scopes customizados e App Roles. A versão do token deve ser 2 para que o issuer seja login.microsoftonline.com (v2.0). Tokens v1.0 usam sts.windows.net como issuer e são rejeitados pelo verifier.
az ad app update --id $CLIENT_ID --identifier-uris "api://$CLIENT_ID"
$body = '{"api":{"requestedAccessTokenVersion":2}}'
$body | Out-File "$env:TEMP\tokenver.json" -Encoding utf8 -NoNewline
az rest --method PATCH `
--uri "https://graph.microsoft.com/v1.0/applications/$OBJECT_ID" `
--headers "Content-Type=application/json" `
--body "@$env:TEMP\tokenver.json"
1.4 Adicionar o scope access_as_user
Este scope delegado permite que clientes solicitem tokens para esta API. Ele não concede acesso às ferramentas; isso é controlado exclusivamente pelas App Roles.
$SCOPE_ID = [System.Guid]::NewGuid().ToString()
$body = @"
{
"api": {
"oauth2PermissionScopes": [{
"adminConsentDescription": "Acessar mcp-lab como o usuário autenticado",
"adminConsentDisplayName": "Acessar mcp-lab",
"id": "$SCOPE_ID",
"isEnabled": true,
"type": "User",
"userConsentDescription": "Acessar mcp-lab em seu nome",
"userConsentDisplayName": "Acessar mcp-lab",
"value": "access_as_user"
}]
}
}
"@
$body | Out-File "$env:TEMP\scope.json" -Encoding utf8 -NoNewline
az rest --method PATCH `
--uri "https://graph.microsoft.com/v1.0/applications/$OBJECT_ID" `
--headers "Content-Type=application/json" `
--body "@$env:TEMP\scope.json"
1.5 Criar App Roles
As App Roles são atribuídas por administradores e aparecem no claim roles do JWT. Usuários não podem autoatribuí-las, sendo essa a diferença fundamental em relação a OAuth Scopes.
$ROLE_READ_ID = [System.Guid]::NewGuid().ToString()
$ROLE_ADMIN_ID = [System.Guid]::NewGuid().ToString()
$body = @"
{
"appRoles": [
{
"allowedMemberTypes": ["User", "Application"],
"description": "Acesso de leitura às ferramentas MCP",
"displayName": "MCP Read",
"id": "$ROLE_READ_ID",
"isEnabled": true,
"value": "mcp-trc-read"
},
{
"allowedMemberTypes": ["User", "Application"],
"description": "Acesso administrativo às ferramentas MCP",
"displayName": "MCP Admin",
"id": "$ROLE_ADMIN_ID",
"isEnabled": true,
"value": "mcp-trc-admin"
}
]
}
"@
$body | Out-File "$env:TEMP\approles.json" -Encoding utf8 -NoNewline
az rest --method PATCH `
--uri "https://graph.microsoft.com/v1.0/applications/$OBJECT_ID" `
--headers "Content-Type=application/json" `
--body "@$env:TEMP\approles.json"
1.6 Criar Service Principal e client secret
az ad sp create --id $CLIENT_ID
$SECRET = az ad app credential reset --id $CLIENT_ID --years 1 --query "password" -o tsv
$TENANT_ID = az account show --query tenantId -o tsv
⚠️ Em produção, o
client_secretdeve ser armazenado no Azure Key Vault e acessado via Managed Identity. Nunca persista em arquivos de configuração ou variáveis de ambiente em disco.
1.7 Conceder consentimento administrativo para access_as_user
$SP_ID = az ad sp show --id $CLIENT_ID --query id -o tsv
$body = @"
{
"clientId": "$SP_ID",
"consentType": "AllPrincipals",
"resourceId": "$SP_ID",
"scope": "access_as_user"
}
"@
$body | Out-File "$env:TEMP\consent.json" -Encoding utf8 -NoNewline
az rest --method POST `
--uri "https://graph.microsoft.com/v1.0/oauth2PermissionGrants" `
--headers "Content-Type=application/json" `
--body "@$env:TEMP\consent.json"
⚠️
consentType: AllPrincipalsconcede consentimento em nome de todos os usuários do tenant. Os usuários não verão tela de consentimento ao autenticar. Esta ação requer privilégios de Global Administrator ou Privileged Role Administrator.
👥 Passo 2: Grupos de Segurança no Azure AD
O acesso é gerenciado exclusivamente via grupos de segurança. Administradores adicionam ou removem usuários dos grupos sem qualquer alteração de código ou no App Registration.
2.1 Criar os grupos
$GROUP_READ_ID = (az rest --method POST --uri "https://graph.microsoft.com/v1.0/groups" `
--headers "Content-Type=application/json" `
--body '{"displayName":"mcp-trc-read","mailEnabled":false,"mailNickname":"mcp-trc-read","securityEnabled":true}' `
-o json | ConvertFrom-Json).id
$GROUP_ADMIN_ID = (az rest --method POST --uri "https://graph.microsoft.com/v1.0/groups" `
--headers "Content-Type=application/json" `
--body '{"displayName":"mcp-trc-admin","mailEnabled":false,"mailNickname":"mcp-trc-admin","securityEnabled":true}' `
-o json | ConvertFrom-Json).id
2.2 Atribuir App Roles aos grupos
$body = "{`"principalId`":`"$GROUP_READ_ID`",`"resourceId`":`"$SP_ID`",`"appRoleId`":`"$ROLE_READ_ID`"}"
az rest --method POST `
--uri "https://graph.microsoft.com/v1.0/groups/$GROUP_READ_ID/appRoleAssignments" `
--headers "Content-Type=application/json" --body $body
$body = "{`"principalId`":`"$GROUP_ADMIN_ID`",`"resourceId`":`"$SP_ID`",`"appRoleId`":`"$ROLE_ADMIN_ID`"}"
az rest --method POST `
--uri "https://graph.microsoft.com/v1.0/groups/$GROUP_ADMIN_ID/appRoleAssignments" `
--headers "Content-Type=application/json" --body $body
Para verificar e gerenciar atribuições via portal: Azure AD → Enterprise Applications → mcp-lab → Users and groups
2.3 Gerenciar membros dos grupos
# Adicionar usuário ao grupo
$USER_OID = "<object-id-do-usuario>"
$body = "{`"@odata.id`":`"https://graph.microsoft.com/v1.0/directoryObjects/$USER_OID`"}"
az rest --method POST `
--uri "https://graph.microsoft.com/v1.0/groups/$GROUP_ADMIN_ID/members/`$ref" `
--headers "Content-Type=application/json" --body $body
# Remover usuário do grupo
az rest --method DELETE `
--uri "https://graph.microsoft.com/v1.0/groups/$GROUP_ADMIN_ID/members/$USER_OID/`$ref"
⚠️ A revogação de acesso reflete no próximo token emitido, normalmente em até 1 hora. Para ambientes que exigem revogação imediata, avaliar Continuous Access Evaluation (CAE) do Entra ID.
⚙️ Passo 3: Configuração do projeto
3.1 Instalar dependências
python -m venv .venv
# Windows
.venv\Scripts\activate
# Linux/Mac
source .venv/bin/activate
pip install -r requirements.txt
3.2 Configurar variáveis de ambiente
cp .env.example .env
Edite o arquivo .env:
AZURE_CLIENT_ID=<client-id-do-app-registration>
AZURE_CLIENT_SECRET=<client-secret>
AZURE_TENANT_ID=<tenant-id>
MCP_BASE_URL=http://localhost:8000
# Modo de autenticação: jwt (padrão) | oauth
AUTH_MODE=jwt
⚠️ Nunca comite o arquivo
.envno controle de versão. Adicione ao.gitignore. Em produção, use variáveis de ambiente do Azure Container Apps ou referências ao Key Vault. OAZURE_CLIENT_SECRETé necessário apenas noAUTH_MODE=oauth.
3.3 Executar
python -m app
O servidor sobe em http://0.0.0.0:8000/mcp.
🔑 Passo 4: Obter token para testes
az login --tenant "<TENANT_ID>"
az account get-access-token `
--scope "api://<CLIENT_ID>/access_as_user" `
--query accessToken -o tsv
Utilize o token como header Bearer no cliente MCP:
Authorization: Bearer <token>
Os tokens expiram em aproximadamente 1 hora. Execute az account get-access-token novamente para renovar sem novo login.
➕ Adicionando novas ferramentas
Crie app/tools/minha_ferramenta.py:
from fastmcp.tools.base import Tool
from ..auth import require_roles
def _minha_ferramenta(parametro: str) -> dict:
"""Descrição do que a ferramenta faz."""
return {"resultado": parametro}
minha_ferramenta: Tool = Tool.from_function(
_minha_ferramenta,
name="minha_ferramenta",
auth=require_roles("mcp-trc-read"),
)
Registre em app/tools/__init__.py:
from .minha_ferramenta import minha_ferramenta
def register_tools(mcp: FastMCP) -> None:
mcp.add_tool(health_check)
mcp.add_tool(soma)
mcp.add_tool(minha_ferramenta)
Nenhuma alteração na infraestrutura de autenticação é necessária.
🏭 Considerações para produção (Azure Container Apps)
O Azure Container Apps resolve automaticamente parte dos requisitos de segurança corporativa:
| Requisito | Solução no ACA |
|---|---|
| TLS/HTTPS | Ingress com certificado gerenciado automaticamente |
| Segredos | ACA Secrets ou Key Vault reference com Managed Identity |
| Logs | stdout capturado automaticamente pelo Log Analytics |
| Scaling | Configurável via regras de escala do ACA |
Variáveis de ambiente recomendadas no ACA (sem .env):
AZURE_CLIENT_ID variável de ambiente do ACA
AZURE_TENANT_ID variável de ambiente do ACA
MCP_BASE_URL https://<seu-app>.azurecontainerapps.io
AUTH_MODE jwt
AZURE_CLIENT_SECRET referência ao Key Vault (apenas se AUTH_MODE=oauth)
Para AUTH_MODE=jwt com Container Apps, o AZURE_CLIENT_SECRET não é utilizado na validação de tokens. O servidor aceita tokens JWT do Entra ID diretamente, incluindo tokens OBO (On-Behalf-Of) emitidos por serviços upstream como App Service com Managed Identity.
⚠️ Limitações conhecidas
- Revogação de acesso não é imediata: tokens emitidos antes da remoção do grupo permanecem válidos por até 1 hora.
- Apenas um modo de autenticação por instância:
AUTH_MODE=jwteAUTH_MODE=oauthnão podem coexistir na mesma instância do servidor. - Ausência de audit log nativo no servidor: logs de acesso dependem da infraestrutura de hospedagem (Azure Monitor recomendado).
- Tokens v1.0 são rejeitados: clientes que não suportam tokens v2.0 do Entra ID não conseguirão autenticar.
✅ Checklist de segurança
Identidade e acesso
- App Registration com
signInAudience: AzureADMyOrg, restringindo ao tenant corporativo -
requestedAccessTokenVersion: 2, com tokens v1.0 rejeitados pelo verifier - App Roles atribuídas a grupos, não a indivíduos, com controle centralizado e auditável
- Consentimento administrativo concedido via
AllPrincipals, sem prompts para usuários
Validação de token
- Assinatura RS256 validada contra JWKS público do Entra
- Issuer validado:
login.microsoftonline.com/<tenant>/v2.0 - Audience validado:
CLIENT_IDda aplicação - Presença obrigatória de role válida verificada na conexão
Controle de acesso
-
RoleEnforcedJWTVerifierrejeita conexão (401) antes de qualquer resposta MCP -
auth=por ferramenta filtratools/list, impedindo que a LLM veja ferramentas não autorizadas -
auth=por ferramenta bloqueiatools/callem segunda camada de defesa
Secrets e configuração
- Arquivo
.envno.gitignore, com segredos nunca versionados - Em produção:
client_secretarmazenado no Key Vault, não em variáveis de ambiente em texto plano - Client secret com rotação definida (recomendado: anual ou semestral)
Infraestrutura
- Servidor exposto somente via HTTPS em produção
- Container App em VNet privada com acesso restrito por NSG
- Logs roteados para Azure Monitor / Log Analytics
- Alertas configurados para tentativas de acesso com 401 repetidas
📚 Referências
README copy captured when this server was indexed; it may lag the repository. The original is authoritative.