Cenário: Chamar uma API downstream

Use o Microsoft Entra SDK para o ID do Agente para gerir tanto a aquisição de tokens como a comunicação HTTP numa única operação. O SDK troca seu token de entrada por um com escopo para a API downstream e, em seguida, faz a chamada HTTP e retorna a resposta. Este guia mostra-lhe como configurar APIs downstream, implementar chamadas em TypeScript e Python, lidar com diferentes métodos HTTP e gerir erros com retentações.

Pré-requisitos

  • Uma conta no Azure com uma subscrição ativa. Crie uma conta gratuitamente.
  • Microsoft Entra SDK para AgentID implementado e em execução no seu ambiente. Consulte o Guia de Instalação para obter instruções de configuração.
  • API downstream configurada no SDK com a URL base e os escopos necessários para as APIs que você deseja chamar.
  • Tokens de portador de clientes autenticados - Seu aplicativo recebe tokens de aplicativos cliente que você encaminhará para o SDK.
  • Permissões apropriadas em Microsoft Entra ID - A sua conta deve ter permissões para registar aplicações e conceder permissões da API.

Configuração

Configure a API downstream no seu Microsoft Entra SDK para as definições de ambiente do Agent ID:

env:
- name: DownstreamApis__Graph__BaseUrl
  value: "https://graph.microsoft.com/v1.0"
- name: DownstreamApis__Graph__Scopes__0
  value: "User.Read"
- name: DownstreamApis__Graph__Scopes__1
  value: "Mail.Read"

A configuração especifica:

  • BaseUrl: O ponto de extremidade raiz da sua API downstream
  • Escopos: As permissões necessárias para aceder à API downstream

TypeScript/Node.js

Os exemplos a seguir mostram como chamar APIs downstream de aplicativos TypeScript e Node.js. O código demonstra uma função reutilizável e integração com Express.js.

interface DownstreamApiResponse {
  statusCode: number;
  headers: Record<string, string>;
  content: string;
}

async function callDownstreamApi(
  incomingToken: string,
  serviceName: string,
  relativePath: string,
  method: string = 'GET',
  body?: any
): Promise<any> {
  const sdkUrl = process.env.ENTRA_SDK_URL || 'http://localhost:5000';
  
  const url = new URL(`${sdkUrl}/DownstreamApi/${serviceName}`);
  url.searchParams.append('optionsOverride.RelativePath', relativePath);
  if (method !== 'GET') {
    url.searchParams.append('optionsOverride.HttpMethod', method);
  }
  
  const requestOptions: any = {
    method: method,
    headers: {
      'Authorization': incomingToken
    }
  };
  
  if (body) {
    requestOptions.headers['Content-Type'] = 'application/json';
    requestOptions.body = JSON.stringify(body);
  }
  
  const response = await fetch(url.toString(), requestOptions);
  
  if (!response.ok) {
    throw new Error(`SDK error: ${response.statusText}`);
  }
  
  const data = await response.json() as DownstreamApiResponse;
  
  if (data.statusCode >= 400) {
    throw new Error(`API error ${data.statusCode}: ${data.content}`);
  }
  
  return JSON.parse(data.content);
}

// Usage examples
async function getUserProfile(incomingToken: string) {
  return await callDownstreamApi(incomingToken, 'Graph', 'me');
}

async function listEmails(incomingToken: string) {
  return await callDownstreamApi(
    incomingToken,
    'Graph',
    'me/messages?$top=10&$select=subject,from,receivedDateTime'
  );
}

async function sendEmail(incomingToken: string, message: any) {
  return await callDownstreamApi(
    incomingToken,
    'Graph',
    'me/sendMail',
    'POST',
    { message }
  );
}

O exemplo a seguir demonstra como integrar essas funções em um aplicativo Express.js usando middleware e manipuladores de rota:

// Express.js API example
import express from 'express';

const app = express();
app.use(express.json());

app.get('/api/profile', async (req, res) => {
  try {
    const incomingToken = req.headers.authorization;
    if (!incomingToken) {
      return res.status(401).json({ error: 'No authorization token' });
    }
    
    const profile = await getUserProfile(incomingToken);
    res.json(profile);
  } catch (error) {
    console.error('Error:', error);
    res.status(500).json({ error: 'Failed to fetch profile' });
  }
});

app.get('/api/messages', async (req, res) => {
  try {
    const incomingToken = req.headers.authorization;
    if (!incomingToken) {
      return res.status(401).json({ error: 'No authorization token' });
    }
    
    const messages = await listEmails(incomingToken);
    res.json(messages);
  } catch (error) {
    console.error('Error:', error);
    res.status(500).json({ error: 'Failed to fetch messages' });
  }
});

app.post('/api/messages/send', async (req, res) => {
  try {
    const incomingToken = req.headers.authorization;
    if (!incomingToken) {
      return res.status(401).json({ error: 'No authorization token' });
    }
    
    const message = req.body;
    await sendEmail(incomingToken, message);
    res.json({ success: true });
  } catch (error) {
    console.error('Error:', error);
    res.status(500).json({ error: 'Failed to send message' });
  }
});

app.listen(8080, () => {
  console.log('Server running on port 8080');
});

Python

Os exemplos seguintes mostram como chamar APIs a jusante a partir de aplicações Python usando a biblioteca de pedidos e o Flask para tratamento HTTP:

import os
import json
import requests
from typing import Dict, Any, Optional

def call_downstream_api(
    incoming_token: str,
    service_name: str,
    relative_path: str,
    method: str = 'GET',
    body: Optional[Dict[str, Any]] = None
) -> Any:
    """Call a downstream API via the Microsoft Entra SDK for AgentID."""
    sdk_url = os.getenv('ENTRA_SDK_URL', 'http://localhost:5000')
    
    params = {
        'optionsOverride.RelativePath': relative_path
    }
    
    if method != 'GET':
        params['optionsOverride.HttpMethod'] = method
    
    headers = {'Authorization': incoming_token}
    json_body = None
    
    if body:
        headers['Content-Type'] = 'application/json'
        json_body = body
    
    response = requests.request(
        method,
        f"{sdk_url}/DownstreamApi/{service_name}",
        params=params,
        headers=headers,
        json=json_body
    )
    
    if not response.ok:
        raise Exception(f"SDK error: {response.text}")
    
    data = response.json()
    
    if data['statusCode'] >= 400:
        raise Exception(f"API error {data['statusCode']}: {data['content']}")
    
    return json.loads(data['content'])

# Usage examples
def get_user_profile(incoming_token: str) -> Dict[str, Any]:
    return call_downstream_api(incoming_token, 'Graph', 'me')

def list_emails(incoming_token: str) -> Dict[str, Any]:
    return call_downstream_api(
        incoming_token,
        'Graph',
        'me/messages?$top=10&$select=subject,from,receivedDateTime'
    )

def send_email(incoming_token: str, message: Dict[str, Any]) -> None:
    call_downstream_api(
        incoming_token,
        'Graph',
        'me/sendMail',
        'POST',
        {'message': message}
    )

Se você quiser integrar essas funções em uma aplicação Flask, você pode usar o seguinte exemplo:

# Flask API example
from flask import Flask, request, jsonify

app = Flask(__name__)

@app.route('/api/profile')
def profile():
    incoming_token = request.headers.get('Authorization')
    if not incoming_token:
        return jsonify({'error': 'No authorization token'}), 401
    
    try:
        profile_data = get_user_profile(incoming_token)
        return jsonify(profile_data)
    except Exception as e:
        print(f"Error: {e}")
        return jsonify({'error': 'Failed to fetch profile'}), 500

@app.route('/api/messages')
def messages():
    incoming_token = request.headers.get('Authorization')
    if not incoming_token:
        return jsonify({'error': 'No authorization token'}), 401
    
    try:
        messages_data = list_emails(incoming_token)
        return jsonify(messages_data)
    except Exception as e:
        print(f"Error: {e}")
        return jsonify({'error': 'Failed to fetch messages'}), 500

@app.route('/api/messages/send', methods=['POST'])
def send_message():
    incoming_token = request.headers.get('Authorization')
    if not incoming_token:
        return jsonify({'error': 'No authorization token'}), 401
    
    try:
        message = request.json
        send_email(incoming_token, message)
        return jsonify({'success': True})
    except Exception as e:
        print(f"Error: {e}")
        return jsonify({'error': 'Failed to send message'}), 500

if __name__ == '__main__':
    app.run(port=8080)

Pedidos de POST/PUT/PATCH

O endpoint /DownstreamApi suporta operações de modificação através do envio do método HTTP e do corpo da solicitação. Use esses padrões quando precisar criar, atualizar ou excluir recursos na API downstream.

Criação de recursos

// POST example - Create a calendar event
async function createEvent(incomingToken: string, event: any) {
  return await callDownstreamApi(
    incomingToken,
    'Graph',
    'me/events',
    'POST',
    event
  );
}

// Usage
const newEvent = {
  subject: "Team Meeting",
  start: {
    dateTime: "2024-01-15T14:00:00",
    timeZone: "Pacific Standard Time"
  },
  end: {
    dateTime: "2024-01-15T15:00:00",
    timeZone: "Pacific Standard Time"
  }
};

const createdEvent = await createEvent(incomingToken, newEvent);

Atualizando recursos

// PATCH example - Update user profile
async function updateProfile(incomingToken: string, updates: any) {
  return await callDownstreamApi(
    incomingToken,
    'Graph',
    'me',
    'PATCH',
    updates
  );
}

// Usage
await updateProfile(incomingToken, {
  mobilePhone: "+1 555 0100",
  officeLocation: "Building 2, Room 201"
});

Cenários avançados

Os cenários a seguir demonstram configurações avançadas para casos de uso especializados.

Cabeçalhos personalizados

Adicione cabeçalhos personalizados à solicitação de API downstream:

const url = new URL(`${sdkUrl}/DownstreamApi/MyApi`);
url.searchParams.append('optionsOverride.RelativePath', 'items');
url.searchParams.append('optionsOverride.CustomHeader.X-Custom-Header', 'custom-value');
url.searchParams.append('optionsOverride.CustomHeader.X-Request-Id', requestId);

Substituir escopos

Solicite escopos diferentes dos escopos configurados padrão:

const url = new URL(`${sdkUrl}/DownstreamApi/Graph`);
url.searchParams.append('optionsOverride.RelativePath', 'me');
url.searchParams.append('optionsOverride.Scopes', 'User.ReadWrite');
url.searchParams.append('optionsOverride.Scopes', 'Mail.Send');

Com a identidade do agente

Use a identidade do agente para chamar APIs com permissões de aplicativo:

const url = new URL(`${sdkUrl}/DownstreamApi/Graph`);
url.searchParams.append('optionsOverride.RelativePath', 'users');
url.searchParams.append('AgentIdentity', agentClientId);
url.searchParams.append('AgentUsername', 'admin@contoso.com');

Tratamento de erros

Implemente a lógica de reintento com backoff exponencial para lidar com falhas transitórias de forma eficaz.

async function callDownstreamApiWithRetry(
  incomingToken: string,
  serviceName: string,
  relativePath: string,
  method: string = 'GET',
  body?: any,
  maxRetries: number = 3
): Promise<any> {
  let lastError: Error;
  
  for (let attempt = 1; attempt <= maxRetries; attempt++) {
    try {
      return await callDownstreamApi(
        incomingToken,
        serviceName,
        relativePath,
        method,
        body
      );
    } catch (error) {
      lastError = error as Error;
      
      // Don't retry on client errors (4xx)
      if (error.message.includes('API error 4')) {
        throw error;
      }
      
      // Retry on server errors (5xx) or network errors
      if (attempt < maxRetries) {
        const delay = Math.pow(2, attempt) * 100;
        await new Promise(resolve => setTimeout(resolve, delay));
      }
    }
  }
  
  throw new Error(`Failed after ${maxRetries} retries: ${lastError!.message}`);
}

Comparação com a abordagem AuthorizationHeader

O Microsoft Entra SDK para Agent ID fornece duas abordagens para chamar APIs downstream. Use esta comparação para determinar qual abordagem melhor atende às suas necessidades.

Comparação de capacidades

Capacidade /DownstreamApi /AuthorizationHeader
Aquisição de Tokens Tratado por SDK Tratado por SDK
Solicitação HTTP Tratado por SDK A sua responsabilidade
Análise de respostas Embrulhado em JSON Resposta HTTP Direta
Cabeçalhos personalizados Via parâmetros de consulta Controlo HTTP total
Órgão do Pedido Encaminhado automaticamente Controlo total
Tratamento de erros Erros encapsulados no SDK Erros HTTP padrão

Quando usar cada abordagem

Caso de uso Recommendation Melhor para
Chamadas de API REST padrão com padrões convencionais /DownstreamApi GET, POST, PUT, PATCH, DELETE operadores; redução de código padrão
Clientes HTTP complexos que requerem configuração personalizada /AuthorizationHeader Tratamento especializado de requisições/respostas; controlo detalhado
Acesso direto aos códigos de erro HTTP e cabeçalhos necessários /AuthorizationHeader Aplicativos que precisam de controle de comportamento HTTP de baixo nível
Simplicidade e integração rápida priorizadas /DownstreamApi Aplicativos que priorizam a simplicidade em detrimento do controle de baixo nível

Melhores práticas

  1. Reutilizar clientes HTTP: crie uma vez e reutilize para evitar sobrecarga de conexão
  2. Implementar tratamento de erros: adicionar lógica de tentativa para falhas transitórias com recuo exponencial
  3. Verifique os códigos de status: sempre verifique o código de status antes de analisar o conteúdo da resposta
  4. Definir tempos limites: configure tempos limite de solicitação apropriados para evitar solicitações suspensas
  5. Incluir IDs de correlação: registre todas as solicitações com identificadores exclusivos para rastreamento de ponta a ponta
  6. Validar entrada: limpe e valide dados antes de enviar para APIs downstream
  7. Monitore o desempenho: rastreie a latência de chamadas de API e as taxas de falha para observabilidade