Escenario: Llamada a una API de bajada

Use el SDK de Microsoft Entra para el identificador del agente para controlar la adquisición de tokens y la comunicación HTTP en una sola operación. El SDK intercambia tu token entrante por uno que esté limitado al alcance de la API de destino, realiza la llamada HTTP y devuelve la respuesta. En esta guía se muestra cómo configurar las API de bajada, implementar llamadas en TypeScript y Python, controlar diferentes métodos HTTP y administrar errores con reintentos.

Prerrequisitos

  • Una cuenta de Azure con una suscripción activa. Cree una cuenta gratuita.
  • Microsoft Entra SDK para AgentID implementado y ejecutándose en su entorno. Consulte la Guía de instalación para obtener instrucciones de configuración.
  • API descendente configurada en el SDK con la dirección URL base y los permisos necesarios para las API que desea invocar.
  • Tokens portadores de clientes autenticados: su aplicación recibe tokens de las aplicaciones cliente que reenviará al SDK.
  • Permisos de Microsoft Entra ID: la cuenta debe tener permisos para registrar aplicaciones y conceder permisos de API.

Configuración

Configura la API descendente en el SDK de Microsoft Entra para la configuración del entorno de ID de Agente.

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"

La configuración especifica:

  • BaseUrl: el punto de conexión raíz de la API de bajada
  • Ámbitos: los permisos necesarios para acceder a la API de bajada

TypeScript/Node.js

En los ejemplos siguientes se muestra cómo llamar a las API descendentes desde aplicaciones de TypeScript y Node.js. El código muestra una función reutilizable e integración con 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 }
  );
}

En el ejemplo siguiente se muestra cómo integrar estas funciones en una aplicación de Express.js mediante middleware y controladores de rutas:

// 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

En los ejemplos siguientes se muestra cómo llamar a las API descendentes desde aplicaciones Python mediante la biblioteca requests y Flask para la gestión de 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}
    )

Si desea integrar estas funciones en una aplicación de Flask, puede usar el ejemplo siguiente:

# 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)

Solicitudes POST/PUT/PATCH

El /DownstreamApi punto de conexión admite operaciones de modificación pasando el método HTTP y el cuerpo de la solicitud. Use estos patrones cuando necesite crear, actualizar o eliminar recursos en la API de bajada.

Creación 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);

Actualización de 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"
});

Escenarios avanzados

En los escenarios siguientes se muestran configuraciones avanzadas para casos de uso especializados.

Encabezados personalizados

Agregue encabezados personalizados a la solicitud de API de bajada:

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);

Sobrescribir ámbitos

Solicite ámbitos diferentes a los ámbitos configurados predeterminados:

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');

Con la identidad del agente

Use la identidad del agente para llamar a las API con permisos de aplicación:

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');

Control de errores

Implemente la lógica de reintento con retroceso exponencial para controlar los errores transitorios correctamente:

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}`);
}

Comparación con el enfoque AuthorizationHeader

El SDK de Microsoft Entra para la ID de agente proporciona dos enfoques para llamar a las API downstream. Use esta comparación para determinar qué enfoque se adapta mejor a sus necesidades.

Comparación de funcionalidades

Capacidad /DownstreamApi /AuthorizationHeader
Adquisición de tokens Controlado por el SDK Controlado por el SDK
Solicitud HTTP Controlado por el SDK Su responsabilidad
Análisis de respuestas Encapsulado en JSON Respuesta HTTP directa
Encabezados personalizados Mediante parámetros de consulta Control total de HTTP
Cuerpo de la solicitud Reenviado automáticamente Control total
Tratamiento de errores Errores envueltos en el SDK Errores HTTP estándar

Cuándo usar cada enfoque

Caso de uso Recomendación Mejor para
Llamadas API REST estándar con patrones convencionales /DownstreamApi Operaciones GET, POST, PUT, PATCH, DELETE; reducción de código repetitivo
Clientes HTTP complejos que requieren configuración personalizada /AuthorizationHeader Tratamiento especializado de solicitudes y respuestas; Control específico
Acceso directo a los encabezados y códigos de error HTTP necesarios /AuthorizationHeader Aplicaciones que necesitan control de comportamiento HTTP de bajo nivel
Simplicidad y integración rápida prioritarios /DownstreamApi Las aplicaciones priorizan la simplicidad sobre el control de bajo nivel

procedimientos recomendados

  1. Reutilización de clientes HTTP: crear una vez y reutilizar para evitar la sobrecarga de conexión
  2. Implementación del control de errores: adición de lógica de reintento para errores transitorios con retroceso exponencial
  3. Comprobar códigos de estado: compruebe siempre el código de estado antes de analizar el contenido de la respuesta.
  4. Establecer tiempos de espera: configure los tiempos de espera de solicitud adecuados para evitar que las solicitudes se queden colgadas.
  5. Incluir identificadores de correlación: registre todas las solicitudes con identificadores únicos para el seguimiento de un extremo a otro
  6. Validar entrada: Sane y valide los datos antes de enviarlos a las API de bajada.
  7. Supervisión del rendimiento: seguimiento de las tasas de latencia y errores de las llamadas api para la observabilidad