Scenario: Verifiera ett auktoriseringshuvud

Verifiera inkommande bärartoken genom att vidarebefordra dem till Microsoft Entra SDK för AgentID:s /Validate slutpunkt och sedan extrahera de returnerade anspråken för att fatta beslut om auktorisering. Den här guiden visar hur du implementerar mellanprogram för tokenvalidering och fattar auktoriseringsbeslut baserat på omfång eller roller.

Förutsättningar

  • Ett Azure konto med en aktiv prenumeration. Skapa ett konto kostnadsfritt.
  • Microsoft Entra SDK för AgentID distribueras och körs med nätverksåtkomst från ditt program. Se Installationsguide för installationsinstruktioner.
  • Registrerat program i Microsoft Entra ID – Registrera en ny app i Microsoft Entra administrationscenter, konfigurerad för Konton endast i den här organisationskatalogen. Mer information finns i Registrera ett program . Registrera följande värden från programöversiktssidan :
    • App-ID (klient-ID)
    • Katalog-ID (hyresgäst)
    • Konfigurera en app-ID-URI i avsnittet Exponera ett API (används som målgrupp för tokenverifiering)
  • Ägartoken från autentiserade klienter – Ditt program måste ta emot token från klientprogram via OAuth 2.0-flöden.
  • Tilldela behörigheter i Microsoft Entra ID – Ditt konto måste ha behörighet att registrera program och konfigurera autentiseringsinställningar.

Konfiguration

Om du vill verifiera token för ditt API konfigurerar du Microsoft Entra SDK för agent-ID med din Microsoft Entra ID klientinformation.

env:
- name: AzureAd__Instance
  value: "https://login.microsoftonline.com/"
- name: AzureAd__TenantId
  value: "your-tenant-id"
- name: AzureAd__ClientId
  value: "your-api-client-id"
- name: AzureAd__Audience
  value: "api://your-api-id"

TypeScript/Node.js

Följande implementering visar hur du skapar ett mellanprogram för tokenvalidering som integreras med Microsoft Entra SDK för agent-ID med hjälp av TypeScript eller JavaScript. Det här mellanprogrammet kontrollerar varje inkommande begäran efter en giltig ägartoken och extraherar anspråk för användning i routningshanterare:

import fetch from 'node-fetch';

interface ValidateResponse {
  protocol: string;
  token: string;
  claims: {
    aud: string;
    iss: string;
    oid: string;
    sub: string;
    tid: string;
    upn?: string;
    scp?: string;
    roles?: string[];
    [key: string]: any;
  };
}

async function validateToken(authorizationHeader: string): Promise<ValidateResponse> {
  const sidecarUrl = process.env.SIDECAR_URL || 'http://localhost:5000';
  
  const response = await fetch(`${sidecarUrl}/Validate`, {
    headers: {
      'Authorization': authorizationHeader
    }
  });
  
  if (!response.ok) {
    throw new Error(`Token validation failed: ${response.statusText}`);
  }
  
  return await response.json() as ValidateResponse;
}

Följande kodfragment visar hur du använder validateToken funktionen i ett Express.js mellanprogram för att skydda API-slutpunkter:

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

const app = express();

// Token validation middleware
async function requireAuth(req, res, next) {
  const authHeader = req.headers.authorization;
  
  if (!authHeader) {
    return res.status(401).json({ error: 'No authorization token provided' });
  }
  
  try {
    const validation = await validateToken(authHeader);
    
    // Attach claims to request object
    req.user = {
      id: validation.claims.oid,
      upn: validation.claims.upn,
      tenantId: validation.claims.tid,
      scopes: validation.claims.scp?.split(' ') || [],
      roles: validation.claims.roles || [],
      claims: validation.claims
    };
    
    next();
  } catch (error) {
    console.error('Token validation failed:', error);
    return res.status(401).json({ error: 'Invalid token' });
  }
}

// Protected endpoint
app.get('/api/protected', requireAuth, (req, res) => {
  res.json({
    message: 'Access granted',
    user: {
      id: req.user.id,
      upn: req.user.upn
    }
  });
});

// Scope-based authorization
app.get('/api/admin', requireAuth, (req, res) => {
  if (!req.user.roles.includes('Admin')) {
    return res.status(403).json({ error: 'Insufficient permissions' });
  }
  
  res.json({ message: 'Admin access granted' });
});

app.listen(8080);

Python

Följande Python kodfragment använder Flask-dekoratörer för att omsluta routningshanterare med tokenverifiering. Den här dekoratören extraherar bärare-token från auktoriseringshuvudet, validerar den med Microsoft Entra SDK för AgentID och gör kraven tillgängliga i din rutt.

import os
import requests
from flask import Flask, request, jsonify
from functools import wraps

app = Flask(__name__)

def validate_token(authorization_header: str) -> dict:
    """Validate token using the SDK."""
    sidecar_url = os.getenv('SIDECAR_URL', 'http://localhost:5000')
    
    response = requests.get(
        f"{sidecar_url}/Validate",
        headers={'Authorization': authorization_header}
    )
    
    if not response.ok:
        raise Exception(f"Token validation failed: {response.text}")
    
    return response.json()

# Token validation decorator
def require_auth(f):
    @wraps(f)
    def decorated_function(*args, **kwargs):
        auth_header = request.headers.get('Authorization')
        
        if not auth_header:
            return jsonify({'error': 'No authorization token provided'}), 401
        
        try:
            validation = validate_token(auth_header)
            
            # Attach user info to Flask's g object
            from flask import g
            g.user = {
                'id': validation['claims']['oid'],
                'upn': validation['claims'].get('upn'),
                'tenant_id': validation['claims']['tid'],
                'scopes': validation['claims'].get('scp', '').split(' '),
                'roles': validation['claims'].get('roles', []),
                'claims': validation['claims']
            }
            
            return f(*args, **kwargs)
        except Exception as e:
            print(f"Token validation failed: {e}")
            return jsonify({'error': 'Invalid token'}), 401
    
    return decorated_function

# Protected endpoint
@app.route('/api/protected')
@require_auth
def protected():
    from flask import g
    return jsonify({
        'message': 'Access granted',
        'user': {
            'id': g.user['id'],
            'upn': g.user['upn']
        }
    })

# Role-based authorization
@app.route('/api/admin')
@require_auth
def admin():
    from flask import g
    if 'Admin' not in g.user['roles']:
        return jsonify({'error': 'Insufficient permissions'}), 403
    
    return jsonify({'message': 'Admin access granted'})

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

Go

Följande Go-implementering visar tokenverifiering med hjälp av standardmönstret för HTTP-hanterare. Den här metoden med mellanprogram extraherar ägartoken från auktoriseringshuvudet, validerar dem med Microsoft Entra SDK för AgentID och lagrar användarinformation i begärandehuvuden för användning i underordnade hanterare:

package main

import (
    "encoding/json"
    "fmt"
    "net/http"
    "os"
    "strings"
)

type ValidateResponse struct {
    Protocol string                 `json:"protocol"`
    Token    string                 `json:"token"`
    Claims   map[string]interface{} `json:"claims"`
}

type User struct {
    ID       string
    UPN      string
    TenantID string
    Scopes   []string
    Roles    []string
    Claims   map[string]interface{}
}

func validateToken(authHeader string) (*ValidateResponse, error) {
    sidecarURL := os.Getenv("SIDECAR_URL")
    if sidecarURL == "" {
        sidecarURL = "http://localhost:5000"
    }
    
    req, err := http.NewRequest("GET", fmt.Sprintf("%s/Validate", sidecarURL), 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 {
        return nil, fmt.Errorf("token validation failed: %s", resp.Status)
    }
    
    var validation ValidateResponse
    if err := json.NewDecoder(resp.Body).Decode(&validation); err != nil {
        return nil, err
    }
    
    return &validation, nil
}

// Middleware for token validation
func requireAuth(next http.HandlerFunc) http.HandlerFunc {
    return func(w http.ResponseWriter, r *http.Request) {
        authHeader := r.Header.Get("Authorization")
        
        if authHeader == "" {
            http.Error(w, "No authorization token provided", http.StatusUnauthorized)
            return
        }
        
        validation, err := validateToken(authHeader)
        if err != nil {
            http.Error(w, "Invalid token", http.StatusUnauthorized)
            return
        }
        
        // Extract user information from claims
        user := &User{
            ID:       validation.Claims["oid"].(string),
            TenantID: validation.Claims["tid"].(string),
            Claims:   validation.Claims,
        }
        
        if upn, ok := validation.Claims["upn"].(string); ok {
            user.UPN = upn
        }
        
        if scp, ok := validation.Claims["scp"].(string); ok {
            user.Scopes = strings.Split(scp, " ")
        }
        
        if roles, ok := validation.Claims["roles"].([]interface{}); ok {
            for _, role := range roles {
                user.Roles = append(user.Roles, role.(string))
            }
        }
        
        // Store user in context (simplified - use context.Context in production)
        r.Header.Set("X-User-ID", user.ID)
        r.Header.Set("X-User-UPN", user.UPN)
        
        next(w, r)
    }
}

func protectedHandler(w http.ResponseWriter, r *http.Request) {
    w.Header().Set("Content-Type", "application/json")
    json.NewEncoder(w).Encode(map[string]interface{}{
        "message": "Access granted",
        "user": map[string]string{
            "id":  r.Header.Get("X-User-ID"),
            "upn": r.Header.Get("X-User-UPN"),
        },
    })
}

func main() {
    http.HandleFunc("/api/protected", requireAuth(protectedHandler))
    
    fmt.Println("Server starting on :8080")
    http.ListenAndServe(":8080", nil)
}

C#

Följande C#-implementering visar tokenverifiering med hjälp av ASP.NET Core mellanprogram. Den här metoden använder beroendeinmatning för att komma åt tokenverifieringstjänsten, extraherar ägartoken från auktoriseringshuvudet, validerar dem med Microsoft Entra SDK för AgentID och lagrar användaranspråk i HttpContext för användning i kontrollanter:

using Microsoft.AspNetCore.Mvc;
using System.Net.Http;
using System.Net.Http.Json;
using System.Text.Json;

public class ValidateResponse
{
    public string Protocol { get; set; }
    public string Token { get; set; }
    public JsonElement Claims { get; set; }
}

public class TokenValidationService
{
    private readonly HttpClient _httpClient;
    private readonly string _sidecarUrl;
    
    public TokenValidationService(IHttpClientFactory httpClientFactory, IConfiguration config)
    {
        _httpClient = httpClientFactory.CreateClient();
        _sidecarUrl = config["SIDECAR_URL"] ?? "http://localhost:5000";
    }
    
    public async Task<ValidateResponse> ValidateTokenAsync(string authorizationHeader)
    {
        var request = new HttpRequestMessage(HttpMethod.Get, $"{_sidecarUrl}/Validate");
        request.Headers.Add("Authorization", authorizationHeader);
        
        var response = await _httpClient.SendAsync(request);
        response.EnsureSuccessStatusCode();
        
        return await response.Content.ReadFromJsonAsync<ValidateResponse>();
    }
}

// Middleware example
public class TokenValidationMiddleware
{
    private readonly RequestDelegate _next;
    private readonly TokenValidationService _validationService;
    
    public TokenValidationMiddleware(RequestDelegate next, TokenValidationService validationService)
    {
        _next = next;
        _validationService = validationService;
    }
    
    public async Task InvokeAsync(HttpContext context)
    {
        var authHeader = context.Request.Headers["Authorization"].ToString();
        
        if (string.IsNullOrEmpty(authHeader))
        {
            context.Response.StatusCode = 401;
            await context.Response.WriteAsJsonAsync(new { error = "No authorization token" });
            return;
        }
        
        try
        {
            var validation = await _validationService.ValidateTokenAsync(authHeader);
            
            // Store claims in HttpContext.Items for use in controllers
            context.Items["UserClaims"] = validation.Claims;
            context.Items["UserId"] = validation.Claims.GetProperty("oid").GetString();
            
            await _next(context);
        }
        catch (Exception ex)
        {
            context.Response.StatusCode = 401;
            await context.Response.WriteAsJsonAsync(new { error = "Invalid token" });
        }
    }
}

// Controller example
[ApiController]
[Route("api")]
public class ProtectedController : ControllerBase
{
    [HttpGet("protected")]
    public IActionResult GetProtected()
    {
        var userId = HttpContext.Items["UserId"] as string;
        
        return Ok(new
        {
            message = "Access granted",
            user = new { id = userId }
        });
    }
}

Extrahera specifika anspråk

När du har verifierat en token kan du extrahera anspråken för att fatta auktoriseringsbeslut i ditt program. Slutpunkten /Validate returnerar ett anspråksobjekt med följande information:

{
  "protocol": "Bearer",
  "claims": {
    "oid": "user-object-id",
    "upn": "user@contoso.com",
    "tid": "tenant-id",
    "scp": "User.Read Mail.Read",
    "roles": ["Admin"]
  }
}

Vanliga anspråk är:

  • oid: Objektidentifierare (unikt användar-ID) i din Microsoft Entra ID klientorganisation
  • upn: Användarens huvudnamn (vanligtvis e-postformat)
  • tid: Hyresgäst-ID som användaren tillhör
  • scp: Delegerade omfång som användaren har beviljats till ditt program
  • roles: Programroller som tilldelats användaren

Följande exempel visar hur du extraherar specifika anspråk från valideringssvaret:

Användaridentitet:

// Extract user identity
const userId = validation.claims.oid;  // Object ID
const userPrincipalName = validation.claims.upn;  // User Principal Name
const tenantId = validation.claims.tid;  // Tenant ID

Behörigheter och roller:

// Extract scopes (delegated permissions)
const scopes = validation.claims.scp?.split(' ') || [];

// Check for specific scope
if (scopes.includes('User.Read')) {
  // Allow access
}

// Extract roles (application permissions)
const roles = validation.claims.roles || [];

// Check for specific role
if (roles.includes('Admin')) {
  // Allow admin access
}

Auktoriseringsmönster

När du har verifierat token kan du framtvinga auktorisering baserat på antingen delegerade omfång (behörigheter som beviljats av användaren) eller programroller (tilldelade av klientadministratören). Välj det mönster som matchar din auktoriseringsmodell:

Omfångsbaserad auktorisering

Kontrollera om användartoken innehåller nödvändiga omfång innan du beviljar åtkomst:

function requireScopes(requiredScopes: string[]) {
  return async (req, res, next) => {
    const validation = await validateToken(req.headers.authorization);
    const userScopes = validation.claims.scp?.split(' ') || [];
    const hasAllScopes = requiredScopes.every(s => userScopes.includes(s));
    
    if (!hasAllScopes) {
      return res.status(403).json({ error: 'Insufficient scopes' });
    }
    next();
  };
}

app.get('/api/mail', requireScopes(['Mail.Read']), (req, res) => {
  res.json({ message: 'Mail access granted' });
});

Rollbaserad auktorisering

Kontrollera om användaren har nödvändiga programroller:

function requireRoles(requiredRoles: string[]) {
  return async (req, res, next) => {
    const validation = await validateToken(req.headers.authorization);
    const userRoles = validation.claims.roles || [];
    const hasRole = requiredRoles.some(r => userRoles.includes(r));
    
    if (!hasRole) {
      return res.status(403).json({ error: 'Insufficient permissions' });
    }
    next();
  };
}

app.delete('/api/resource', requireRoles(['Admin']), (req, res) => {
  res.json({ message: 'Resource deleted' });
});

Felhantering

Tokenverifieringen kan misslyckas av flera orsaker: token kan vara förfallen, ogiltig eller saknar nödvändiga omfång. Implementera felhantering som skiljer mellan olika felscenarier så att du kan svara på rätt sätt:

async function validateTokenSafely(authHeader: string): Promise<ValidateResponse | null> {
  try {
    return await validateToken(authHeader);
  } catch (error) {
    if (error.message.includes('401')) {
      console.error('Token is invalid or expired');
    } else if (error.message.includes('403')) {
      console.error('Token missing required scopes');
    } else {
      console.error('Token validation error:', error.message);
    }
    return null;
  }
}

Vanliga valideringsfel

Error Orsak Lösning
401 Inte auktoriserad Ogiltig eller förfallen token Begär ny token från klienten
403 – Förbjuden Nödvändiga behörigheter saknas Uppdatera omfångskonfiguration eller tokenbegäran
400 Felaktig begäran Missbildat autentiseringshuvud Kontrollera rubrikformat: ******

Svarsstruktur

Slutpunkten /Validate returnerar:

{
  "protocol": "Bearer",
  "token": "******",
  "claims": {
    "aud": "api://your-api-id",
    "iss": "https://sts.windows.net/tenant-id/",
    "iat": 1234567890,
    "nbf": 1234567890,
    "exp": 1234571490,
    "oid": "user-object-id",
    "sub": "subject",
    "tid": "tenant-id",
    "upn": "user@contoso.com",
    "scp": "User.Read Mail.Read",
    "roles": ["Admin"]
  }
}

Metodtips

  1. Verifiera tidigt: Verifiera token vid API-gatewayen eller startpunkten
  2. Kontrollera omfång: Kontrollera alltid att token har nödvändiga omfång för operationen
  3. Loggfel: Loggvalideringsfel för säkerhetsövervakning
  4. Hantera fel: Ange tydliga felmeddelanden för felsökning
  5. Använd Mellanprogram: Implementera validering som mellanprogram för konsekvens
  6. Säker SDK: Se till att SDK:et endast är tillgängligt från ditt program