Note
Access to this page requires authorization. You can try signing in or changing directories.
Access to this page requires authorization. You can try changing directories.
Exchange incoming bearer tokens for authorization headers scoped to downstream APIs using the Microsoft Entra SDK for AgentID's /AuthorizationHeader endpoint. This approach gives you full control over HTTP requests while delegating token acquisition to the SDK.
Prerequisites
- An Azure account with an active subscription. Create an account for free.
- Microsoft Entra SDK for AgentID deployed and running in your environment. See Installation Guide for setup instructions.
- Downstream API configured in the SDK with the base URL and required scopes for token exchange.
- Bearer tokens from authenticated clients - Your application receives tokens from client applications that you'll exchange for downstream API tokens.
- Appropriate permissions in Microsoft Entra ID - Your account must have permissions to register applications and grant API permissions.
Configuration
Configure the downstream API in your Microsoft Entra SDK for AgentID with the base URL, required scopes, and optional relative path:
env:
- name: DownstreamApis__Graph__BaseUrl
value: "https://graph.microsoft.com/v1.0"
- name: DownstreamApis__Graph__Scopes
value: "User.Read Mail.Read"
TypeScript/Node.js
Create a TypeScript function to call the Microsoft Entra SDK for AgentID and obtain an authorization header. You can then use this header with your HTTP client to call downstream APIs:
import fetch from 'node-fetch';
interface AuthHeaderResponse {
authorizationHeader: string;
}
async function getAuthorizationHeader(
incomingToken: string,
serviceName: string
): Promise<string> {
const sidecarUrl = process.env.SIDECAR_URL || 'http://localhost:5000';
const response = await fetch(
`${sidecarUrl}/AuthorizationHeader/${serviceName}`,
{
headers: {
'Authorization': incomingToken
}
}
);
if (!response.ok) {
throw new Error(`Failed to get authorization header: ${response.statusText}`);
}
const data = await response.json() as AuthHeaderResponse;
return data.authorizationHeader;
}
// Usage example
async function getUserProfile(incomingToken: string) {
// Get authorization header for Microsoft Graph
const authHeader = await getAuthorizationHeader(incomingToken, 'Graph');
// Use the authorization header to call Microsoft Graph
const graphResponse = await fetch(
'https://graph.microsoft.com/v1.0/me',
{
headers: {
'Authorization': authHeader
}
}
);
return await graphResponse.json();
}
The following example demonstrates how to integrate this function into an Express.js application using middleware and route handlers:
// Express.js middleware example
import express from 'express';
const app = express();
app.get('/api/profile', async (req, res) => {
try {
const incomingToken = req.headers.authorization;
if (!incomingToken) {
return res.status(401).json({ error: 'No authorization token provided' });
}
const profile = await getUserProfile(incomingToken);
res.json(profile);
} catch (error) {
console.error('Error fetching profile:', error);
res.status(500).json({ error: 'Failed to fetch profile' });
}
});
Python
The following snippet demonstrates a Python function that calls the Microsoft Entra SDK for AgentID and obtains an authorization header.
import os
import requests
from typing import Dict, Any
def get_authorization_header(incoming_token: str, service_name: str) -> str:
"""Get an authorization header from the SDK."""
sidecar_url = os.getenv('SIDECAR_URL', 'http://localhost:5000')
response = requests.get(
f"{sidecar_url}/AuthorizationHeader/{service_name}",
headers={'Authorization': incoming_token}
)
if not response.ok:
raise Exception(f"Failed to get authorization header: {response.text}")
data = response.json()
return data['authorizationHeader']
def get_user_profile(incoming_token: str) -> Dict[str, Any]:
"""Get user profile from Microsoft Graph."""
# Get authorization header for Microsoft Graph
auth_header = get_authorization_header(incoming_token, 'Graph')
# Use the authorization header to call Microsoft Graph
graph_response = requests.get(
'https://graph.microsoft.com/v1.0/me',
headers={'Authorization': auth_header}
)
if not graph_response.ok:
raise Exception(f"Graph API error: {graph_response.text}")
return graph_response.json()
If you want to integrate this function into a Flask application, you can use the following 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 provided'}), 401
try:
profile_data = get_user_profile(incoming_token)
return jsonify(profile_data)
except Exception as e:
print(f"Error fetching profile: {e}")
return jsonify({'error': 'Failed to fetch profile'}), 500
if __name__ == '__main__':
app.run(port=8080)
Go
The following demonstrates a Go function to call the Microsoft Entra SDK for AgentID and obtain an authorization header. This implementation shows how to parse JSON responses and use the header:
package main
import (
"encoding/json"
"fmt"
"io"
"net/http"
"os"
)
type AuthHeaderResponse struct {
AuthorizationHeader string `json:"authorizationHeader"`
}
type UserProfile struct {
DisplayName string `json:"displayName"`
Mail string `json:"mail"`
UserPrincipalName string `json:"userPrincipalName"`
}
func getAuthorizationHeader(incomingToken, serviceName string) (string, error) {
sidecarURL := os.Getenv("SIDECAR_URL")
if sidecarURL == "" {
sidecarURL = "http://localhost:5000"
}
url := fmt.Sprintf("%s/AuthorizationHeader/%s", sidecarURL, serviceName)
req, err := http.NewRequest("GET", url, nil)
if err != nil {
return "", err
}
req.Header.Set("Authorization", incomingToken)
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
return "", err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
body, _ := io.ReadAll(resp.Body)
return "", fmt.Errorf("failed to get authorization header: %s", string(body))
}
var authResp AuthHeaderResponse
if err := json.NewDecoder(resp.Body).Decode(&authResp); err != nil {
return "", err
}
return authResp.AuthorizationHeader, nil
}
func getUserProfile(incomingToken string) (*UserProfile, error) {
// Get authorization header for Microsoft Graph
authHeader, err := getAuthorizationHeader(incomingToken, "Graph")
if err != nil {
return nil, err
}
// Use the authorization header to call Microsoft Graph
req, err := http.NewRequest("GET", "https://graph.microsoft.com/v1.0/me", nil)
if err != nil {
return nil, err
}
req.Header.Set("Authorization", authHeader)
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
body, _ := io.ReadAll(resp.Body)
return nil, fmt.Errorf("Graph API error: %s", string(body))
}
var profile UserProfile
if err := json.NewDecoder(resp.Body).Decode(&profile); err != nil {
return nil, err
}
return &profile, nil
}
// HTTP handler example
func profileHandler(w http.ResponseWriter, r *http.Request) {
incomingToken := r.Header.Get("Authorization")
if incomingToken == "" {
http.Error(w, "No authorization token provided", http.StatusUnauthorized)
return
}
profile, err := getUserProfile(incomingToken)
if err != nil {
fmt.Printf("Error fetching profile: %v\n", err)
http.Error(w, "Failed to fetch profile", http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(profile)
}
func main() {
http.HandleFunc("/api/profile", profileHandler)
fmt.Println("Server starting on :8080")
http.ListenAndServe(":8080", nil)
}
C# implementation
Create a C# class to call the Microsoft Entra SDK for AgentID and obtain an authorization header. This implementation uses ASP.NET Core's dependency injection:
using System;
using System.Net.Http;
using System.Net.Http.Json;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
public class SidecarClient
{
private readonly HttpClient _httpClient;
private readonly string _sidecarUrl;
public SidecarClient(IHttpClientFactory httpClientFactory, IConfiguration config)
{
_httpClient = httpClientFactory.CreateClient();
_sidecarUrl = config["SIDECAR_URL"] ?? "http://localhost:5000";
}
public async Task<string> GetAuthorizationHeaderAsync(
string incomingAuthorizationHeader,
string serviceName)
{
var request = new HttpRequestMessage(
HttpMethod.Get,
$"{_sidecarUrl}/AuthorizationHeader/{serviceName}"
);
request.Headers.Add("Authorization", incomingAuthorizationHeader);
var response = await _httpClient.SendAsync(request);
response.EnsureSuccessStatusCode();
var result = await response.Content.ReadFromJsonAsync<AuthHeaderResponse>();
return result.AuthorizationHeader;
}
}
public record AuthHeaderResponse(string AuthorizationHeader);
public record UserProfile(string DisplayName, string Mail, string UserPrincipalName);
// Controller example
[ApiController]
[Route("api/[controller]")]
public class ProfileController : ControllerBase
{
private readonly SidecarClient _sidecarClient;
private readonly HttpClient _httpClient;
public ProfileController(SidecarClient sidecarClient, IHttpClientFactory httpClientFactory)
{
_sidecarClient = sidecarClient;
_httpClient = httpClientFactory.CreateClient();
}
[HttpGet]
public async Task<ActionResult<UserProfile>> GetProfile()
{
var incomingToken = Request.Headers["Authorization"].ToString();
if (string.IsNullOrEmpty(incomingToken))
{
return Unauthorized("No authorization token provided");
}
try
{
// Get authorization header for Microsoft Graph
var authHeader = await _sidecarClient.GetAuthorizationHeaderAsync(
incomingToken,
"Graph"
);
// Use the authorization header to call Microsoft Graph
var request = new HttpRequestMessage(
HttpMethod.Get,
"https://graph.microsoft.com/v1.0/me"
);
request.Headers.Add("Authorization", authHeader);
var response = await _httpClient.SendAsync(request);
response.EnsureSuccessStatusCode();
var profile = await response.Content.ReadFromJsonAsync<UserProfile>();
return Ok(profile);
}
catch (Exception ex)
{
return StatusCode(500, $"Failed to fetch profile: {ex.Message}");
}
}
}
Advanced scenarios
The Microsoft Entra SDK for AgentID supports several advanced patterns through query parameters:
Override scopes
Request specific scopes different from configuration:
const response = await fetch(
`${sidecarUrl}/AuthorizationHeader/Graph?` +
`optionsOverride.Scopes=User.Read&` +
`optionsOverride.Scopes=Mail.Send`,
{
headers: { 'Authorization': incomingToken }
}
);
Multi-tenant support
Override tenant for specific user:
const response = await fetch(
`${sidecarUrl}/AuthorizationHeader/Graph?` +
`optionsOverride.AcquireTokenOptions.Tenant=${userTenantId}`,
{
headers: { 'Authorization': incomingToken }
}
);
Request application token
Request an application token instead of OBO:
const response = await fetch(
`${sidecarUrl}/AuthorizationHeader/Graph?` +
`optionsOverride.RequestAppToken=true`,
{
headers: { 'Authorization': incomingToken }
}
);
With agent identity
Use agent identity for delegation:
const response = await fetch(
`${sidecarUrl}/AuthorizationHeader/Graph?` +
`AgentIdentity=${agentClientId}&` +
`AgentUsername=${encodeURIComponent(userPrincipalName)}`,
{
headers: { 'Authorization': incomingToken }
}
);
Error handling
Implement proper error handling when calling the Microsoft Entra SDK for AgentID to distinguish between transient and permanent failures:
Handle transient errors
Implement retry logic with exponential backoff for transient failures:
async function getAuthorizationHeaderWithRetry(
incomingToken: string,
serviceName: string,
maxRetries = 3
): Promise<string> {
let lastError: Error;
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
const response = await fetch(
`${sidecarUrl}/AuthorizationHeader/${serviceName}`,
{
headers: { 'Authorization': incomingToken }
}
);
if (response.ok) {
const data = await response.json();
return data.authorizationHeader;
}
// Don't retry on 4xx errors (client errors)
if (response.status >= 400 && response.status < 500) {
const error = await response.json();
throw new Error(`Client error: ${error.detail || response.statusText}`);
}
// Retry on 5xx errors (server errors)
lastError = new Error(`Server error: ${response.statusText}`);
if (attempt < maxRetries) {
// Exponential backoff
await new Promise(resolve =>
setTimeout(resolve, Math.pow(2, attempt) * 100)
);
}
} catch (error) {
lastError = error as Error;
if (attempt < maxRetries) {
await new Promise(resolve =>
setTimeout(resolve, Math.pow(2, attempt) * 100)
);
}
}
}
throw new Error(`Failed after ${maxRetries} retries: ${lastError.message}`);
}
Best practices
When obtaining authorization headers from the Microsoft Entra SDK for AgentID, follow these practices:
- Reuse HTTP Clients: Create a single HTTP client instance and reuse it across requests rather than creating new clients per call. This improves performance and enables connection pooling.
- Handle Errors Gracefully: Implement retry logic for transient failures (5xx errors) but fail immediately on client errors (4xx responses) that indicate configuration problems.
- Set Appropriate Timeouts: Configure timeouts for SDK calls based on expected latency. This prevents your application from hanging if the SDK is unresponsive.
- Cache Authorization Headers: Cache returned headers for their lifetime to reduce unnecessary calls to the SDK. Respect the token's expiration time when caching.
- Log Correlation IDs: Include correlation IDs from SDK responses in your logs to enable request tracing across system boundaries.
- Validate Responses: Always check response status codes and validate that required fields are present before using the authorization header.