Scenario: Anropa ett underordnat API

Använd Microsoft Entra ID Auth SDK (sidovagn) för att hantera både tokenförvärv och HTTP-kommunikation i en enda åtgärd. SDK:n byter ut din inkommande token mot en som är begränsad till det underordnade API:et och gör sedan HTTP-anropet och returnerar svaret. Den här guiden visar hur du konfigurerar underordnade API:er, implementerar anrop i TypeScript och Python, hanterar olika HTTP-metoder och hanterar fel med återförsök.

Förutsättningar

  • Ett Azure konto med en aktiv prenumeration. Skapa ett konto kostnadsfritt.
  • Microsoft Entra ID Auth SDK (sidecar) är distribuerad och körs i din miljö. Se Installationsguide för installationsinstruktioner.
  • Downstream API konfigureras i SDK med den grundläggande URL:en och de nödvändiga omfången för API:erna som du vill anropa.
  • Ägartoken från autentiserade klienter – Ditt program tar emot token från klientprogram som du vidarebefordrar till SDK: et.
  • Lämpliga behörigheter i Microsoft Entra ID – Ditt konto måste ha behörigheter för att registrera program och bevilja API-behörigheter.

Konfiguration

Konfigurera det underordnade API:et i miljöinställningarna för Microsoft Entra ID Auth SDK (sidovagn):

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"

Konfigurationen anger:

  • BaseUrl: Rotslutpunkten för ditt underordnade API
  • Omfång: De behörigheter som krävs för åtkomst till det underordnade API:et

TypeScript/Node.js

I följande exempel visas hur du anropar underordnade API:er från TypeScript och Node.js program. Koden visar både en återanvändbar funktion och integrering med 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 }
  );
}

I följande exempel visas hur du integrerar dessa funktioner i ett Express.js program med mellanprogram och routningshanterare:

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

I följande exempel visas hur du anropar underordnade API:er från Python program med hjälp av begärandebiblioteket och Flask för HTTP-hantering:

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 ID Auth SDK (sidecar)."""
    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}
    )

Om du vill integrera dessa funktioner i ett Flask-program kan du använda följande exempel:

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

POST/PUT/PATCH-begäranden

Slutpunkten /DownstreamApi stöder ändringsåtgärder genom att skicka HTTP-metoden och begärandetexten. Använd dessa mönster när du behöver skapa, uppdatera eller ta bort resurser i det underordnade API:et.

Skapa resurser

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

Uppdatera resurser

// 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"
});

Avancerade scenarier

Följande scenarier visar avancerade konfigurationer för specialiserade användningsfall.

Anpassade rubriker

Lägg till anpassade rubriker i den underordnade API-begäran:

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

Åsidosätt områden

Begär andra omfång än de standardkonfigurerade omfången:

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

Med agentidentitet

Använd agentidentitet för att anropa API:er med programbehörigheter:

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

Felhantering

Implementera logik för återförsök med exponentiell backoff för att hantera tillfälliga fel på ett korrekt sätt:

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

Jämförelse med AuthorizationHeader-metoden

Microsoft Entra ID Auth SDK (sidovagn) innehåller två metoder för att anropa underordnade API:er. Använd den här jämförelsen för att avgöra vilken metod som passar bäst för dina behov.

Jämförelse av funktioner

Capability /DownstreamApi /AuthorizationHeader
Tokenförvärv Hanteras av SDK Hanteras av SDK
HTTP-begäran Hanteras av SDK Ditt ansvar
Svarsparsning Omsluten i JSON Direkt HTTP-svar
Anpassade rubriker Via frågeparametrar Fullständig HTTP-kontroll
Begärandekropp Vidarebefordras automatiskt Fullständig kontroll
Felhantering Fel omslutna av SDK Standard-HTTP-fel

När du ska använda varje metod

Användningsfall Recommendation Bäst för
Standardiserade REST-API-anrop med konventionella mönster /DownstreamApi GET, POST, PUT, PATCH, DELETE-operationer och minska standardkoden
Komplexa HTTP-klienter som kräver anpassad konfiguration /AuthorizationHeader Specialiserad hantering av begäran/svar; detaljerad kontroll
Direktåtkomst till HTTP-felkoder och -huvuden som behövs /AuthorizationHeader Program som behöver http-beteendekontroll på låg nivå
Enkelhet och snabb integrering prioriteras /DownstreamApi Program som prioriterar enkelhet framför lågnivåkontroll

Metodtips

  1. Återanvända HTTP-klienter: Skapa en gång och återanvänd för att undvika anslutningskostnader
  2. Implementera felhantering: Lägg till logik för återförsök för tillfälliga fel med exponentiell backoff
  3. Kontrollera statuskoder: Kontrollera alltid statuskoden innan du parsar svarsinnehållet
  4. Ange tidsgränser: Konfigurera lämpliga tidsgränser för begäranden för att förhindra hängande begäranden
  5. Inkludera korrelations-ID: Logga alla begäranden med unika identifierare för spårning från slutpunkt till slutpunkt
  6. Verifiera indata: Sanera och verifiera data innan de skickas till underordnade API:er
  7. Övervaka prestanda: Spåra svarstid för API-anrop och felfrekvenser för observerbarhet