Chamadas de APIs downstream desde aplicações web

Este guia explica como chamar APIs subsequentes a partir de aplicações web ASP.NET Core e OWIN usando Microsoft.Identity.Web. Nas aplicações web, adquires tokens em representação do utilizador autenticado para chamar APIs com permissões delegadas.

Compreender o fluxo de tokens

Quando um utilizador inicia sessão na sua aplicação web, pode chamar APIs downstream (Microsoft Graph, serviços Azure ou APIs personalizadas) em nome deles. Microsoft. O Identity.Web gere a aquisição de tokens, cache e atualização automática.

Revise o fluxo dos tokens de utilizador

sequenceDiagram
    participant User as User Browser
    participant WebApp as Your Web App
    participant AzureAD as Microsoft Entra ID
    participant API as Downstream API

    User->>WebApp: 1. Access page requiring API data
    Note over WebApp: User already signed in
    WebApp->>AzureAD: 2. Request access token for API<br/>(using user's refresh token)
    AzureAD->>AzureAD: 3. Validate & check consent
    AzureAD->>WebApp: 4. Return access token
    Note over WebApp: Cache token
    WebApp->>API: 5. Call API with token
    API->>WebApp: 6. Return data
    WebApp->>User: 7. Render page with data

Rever pré-requisitos

Verifique se o seu ambiente cumpre os seguintes requisitos antes de começar.

  • Aplicação web configurada com autenticação OpenID Connect
  • Funcionamento do login do utilizador
  • Registo de aplicações com permissões da API configuradas
  • Consentimento do utilizador obtido (ou consentimento do administrador concedido)

Implementar ASP.NET Core

1. Configurar autenticação e aquisição de tokens

Adicione serviços de autenticação e ative a aquisição de tokens no seu Program.cs ficheiro.

using Microsoft.AspNetCore.Authentication.OpenIdConnect;
using Microsoft.Identity.Web;

var builder = WebApplication.CreateBuilder(args);

// Add authentication with explicit scheme
builder.Services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
    .AddMicrosoftIdentityWebApp(builder.Configuration.GetSection("AzureAd"))
    .EnableTokenAcquisitionToCallDownstreamApi()
    .AddInMemoryTokenCaches();

builder.Services.AddRazorPages()
    .AddMicrosoftIdentityUI();

builder.Services.AddAuthorization(options =>
{
    options.FallbackPolicy = options.DefaultPolicy;
});

var app = builder.Build();

app.UseAuthentication();
app.UseAuthorization();

app.MapRazorPages();
app.Run();

2. Configurar appsettings.json

Defina o registo da aplicação e as definições da API downstream no Microsoft Entra ID em appsettings.json.

{
  "AzureAd": {
    "Instance": "https://login.microsoftonline.com/",
    "TenantId": "your-tenant-id",
    "ClientId": "your-client-id",
    "CallbackPath": "/signin-oidc",
    "SignedOutCallbackPath": "/signout-callback-oidc",
    "ClientCredentials": [
      {
        "SourceType": "ClientSecret",
        "ClientSecret": "your-client-secret"
      }
    ]
  },
  "DownstreamApis": {
    "GraphAPI": {
      "BaseUrl": "https://graph.microsoft.com/v1.0",
      "Scopes": ["user.read", "mail.read"]
    },
    "MyAPI": {
      "BaseUrl": "https://myapi.example.com",
      "Scopes": ["api://my-api-id/access_as_user"]
    }
  }
}

Importante: Para aplicações web que chamam APIs a jusante, precisas de credenciais de cliente (certificado ou secreto) além da configuração de início de sessão.

3. Adicionar suporte a API a jusante

Escolha uma das seguintes opções para registar as suas APIs a jusante.

Opção A: Registo de APIs nomeadas

O código seguinte regista múltiplas APIs de destino a partir das configurações.

using Microsoft.Identity.Web;

// Register multiple downstream APIs
builder.Services.AddDownstreamApis(
    builder.Configuration.GetSection("DownstreamApis"));

Opção B: Usar Microsoft Graph Helper

O código seguinte regista o cliente Microsoft Graph SDK a partir da configuração.

// Install: Microsoft.Identity.Web.GraphServiceClient
builder.Services.AddMicrosoftGraph(builder.Configuration.GetSection("DownstreamApis:GraphAPI"));

4. Chamar a API downstream a partir do controlador

Injeta IDownstreamApi no teu controlador e chama a API em nome do utilizador iniciado.

using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Identity.Web;
using Microsoft.Identity.Abstractions;

[Authorize]
public class ProfileController : Controller
{
    private readonly IDownstreamApi _downstreamApi;
    private readonly ILogger<ProfileController> _logger;

    public ProfileController(
        IDownstreamApi downstreamApi,
        ILogger<ProfileController> logger)
    {
        _downstreamApi = downstreamApi;
        _logger = logger;
    }

    public async Task<IActionResult> Index()
    {
        try
        {
            // Call downstream API on behalf of user
            var userData = await _downstreamApi.GetForUserAsync<UserData>(
                "MyAPI",
                options => options.RelativePath = "api/profile");

            return View(userData);
        }
        catch (MicrosoftIdentityWebChallengeUserException ex)
        {
            // Incremental consent required
            // Redirect user to consent page
            return Challenge(
                new AuthenticationProperties
                {
                    RedirectUri = "/Profile"
                },
                OpenIdConnectDefaults.AuthenticationScheme);
        }
        catch (HttpRequestException ex)
        {
            _logger.LogError(ex, "Failed to call downstream API");
            return View("Error");
        }
    }
}

5. Chamar a API downstream a partir do Razor Page

Injete IDownstreamApi no seu modelo Razor Page e chame a API.

using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.Identity.Web;
using Microsoft.Identity.Abstractions;

[Authorize]
public class ProfileModel : PageModel
{
    private readonly IDownstreamApi _downstreamApi;

    public UserData UserData { get; set; }

    public ProfileModel(IDownstreamApi downstreamApi)
    {
        _downstreamApi = downstreamApi;
    }

    public async Task OnGetAsync()
    {
        try
        {
            UserData = await _downstreamApi.GetForUserAsync<UserData>(
                "MyAPI",
                options => options.RelativePath = "api/profile");
        }
        catch (MicrosoftIdentityWebChallengeUserException)
        {
            // Handle incremental consent
            // User will be redirected to consent page
            throw;
        }
    }
}

Faça uma chamada ao Microsoft Graph

Para as chamadas à API do Microsoft Graph, utilize a GraphServiceClient dedicada.

Instalar pacotes

Instale o pacote Microsoft Graph para a Microsoft. Identidade.Web.

dotnet add package Microsoft.Identity.Web.GraphServiceClient

Configure o cliente Graph no seu código inicial.

// Startup configuration
builder.Services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
    .AddMicrosoftIdentityWebApp(builder.Configuration.GetSection("AzureAd"))
    .EnableTokenAcquisitionToCallDownstreamApi()
    .AddMicrosoftGraph(options =>
    {
        options.Scopes = "user.read mail.read";
    })
    .AddInMemoryTokenCaches();

Chame a API do Graph

Injeta GraphServiceClient no teu controlador para chamar Microsoft Graph endpoints.

using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Graph;

[Authorize]
{
    private readonly GraphServiceClient _graphClient;

    public HomeController(GraphServiceClient graphClient)
    {
        _graphClient = graphClient;
    }

    public async Task<IActionResult> Index()
    {
        // Get current user's profile
        var user = await _graphClient.Me.GetAsync();

        // Get user's emails
        var messages = await _graphClient.Me.Messages
            .GetAsync(config => config.QueryParameters.Top = 10);

        return View(new { User = user, Messages = messages });
    }
}

Saiba mais sobre integração Microsoft Graph


Chame os clientes do SDK do Azure

Para chamar serviços do Azure, use MicrosoftIdentityTokenCredential.

Instalar pacotes

Instale os pacotes SDK do Azure necessários.

dotnet add package Microsoft.Identity.Web.Azure
dotnet add package Azure.Storage.Blobs

Registe a credencial do token Microsoft Entra no seu código de startup.

using Microsoft.Identity.Web;

builder.Services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
    .AddMicrosoftIdentityWebApp(builder.Configuration.GetSection("AzureAd"))
    .EnableTokenAcquisitionToCallDownstreamApi()
    .AddInMemoryTokenCaches();

// Add Azure token credential
builder.Services.AddMicrosoftIdentityAzureTokenCredential();

Aceder aos serviços Azure

Injete a credencial do token e use-a com os clientes do SDK do Azure.

using Azure.Storage.Blobs;
using Microsoft.Identity.Web;

public class StorageController : Controller
{
    private readonly MicrosoftIdentityTokenCredential _credential;

    public StorageController(MicrosoftIdentityTokenCredential credential)
    {
        _credential = credential;
    }

    [Authorize]
    public async Task<IActionResult> ListBlobs()
    {
        var blobClient = new BlobServiceClient(
            new Uri("https://myaccount.blob.core.windows.net"),
            _credential);

        var container = blobClient.GetBlobContainerClient("mycontainer");
        var blobs = new List<string>();

        await foreach (var blob in container.GetBlobsAsync())
        {
            blobs.Add(blob.Name);
        }

        return View(blobs);
    }
}

Saiba mais sobre integração SDK do Azure


Invocar APIs personalizadas com IDownstreamApi

Para as suas próprias APIs REST, IDownstreamApi fornece uma abordagem simples orientada por configuração.

Configurar a API

Defina as definições da API a jusante em appsettings.json.

{
  "DownstreamApis": {
    "MyAPI": {
      "BaseUrl": "https://myapi.example.com",
      "Scopes": ["api://my-api-id/access_as_user"],
      "RequestAppToken": false
    }
  }
}

Enviar pedidos GET

Recuperar dados da downstream API com parâmetros opcionais de consulta.

// Simple GET
var data = await _downstreamApi.GetForUserAsync<MyData>(
    "MyAPI",
    options => options.RelativePath = "api/resource");

// GET with query parameters
var results = await _downstreamApi.GetForUserAsync<SearchResults>(
    "MyAPI",
    options =>
    {
        options.RelativePath = "api/search";
        options.QueryParameters = new Dictionary<string, string>
        {
            ["query"] = "test",
            ["limit"] = "10"
        };
    });

Enviar pedidos POST

Crie um novo recurso na API a jusante publicando um corpo de pedido.

var newItem = new CreateItemRequest
{
    Name = "New Item",
    Description = "Item description"
};

var created = await _downstreamApi.PostForUserAsync<CreateItemRequest, CreatedItem>(
    "MyAPI",
    newItem,
    options => options.RelativePath = "api/items");

Enviar pedidos de PUT e DELETE

Atualize ou elimine recursos na API downstream.

// PUT request
var updated = await _downstreamApi.PutForUserAsync<UpdateRequest, UpdatedItem>(
    "MyAPI",
    updateData,
    options => options.RelativePath = "api/items/123");

// DELETE request
await _downstreamApi.DeleteForUserAsync(
    "MyAPI",
    null,
    options => options.RelativePath = "api/items/123");

Saiba mais sobre chamadas API personalizadas


Use IAuthorizationHeaderProvider (advanced)

Para controlo máximo sobre pedidos HTTP, use IAuthorizationHeaderProvider.

Registar o cliente HTTP

Regista um cliente HTTP nomeado para a tua API downstream.

builder.Services.AddHttpClient("MyAPI", client =>
{
    client.BaseAddress = new Uri("https://myapi.example.com");
});

Criar pedidos HTTP personalizados

Construir e enviar pedidos HTTP com cabeçalhos personalizados e autorização.

using Microsoft.Identity.Abstractions;

public class CustomApiService
{
    private readonly IAuthorizationHeaderProvider _authProvider;
    private readonly IHttpClientFactory _httpClientFactory;

    public CustomApiService(
        IAuthorizationHeaderProvider authProvider,
        IHttpClientFactory httpClientFactory)
    {
        _authProvider = authProvider;
        _httpClientFactory = httpClientFactory;
    }

    public async Task<MyData> GetDataAsync()
    {
        // Get authorization header
        var authHeader = await _authProvider.CreateAuthorizationHeaderForUserAsync(
            new[] { "api://my-api-id/access_as_user" });

        // Create HTTP request with custom logic
        var client = _httpClientFactory.CreateClient("MyAPI");
        var request = new HttpRequestMessage(HttpMethod.Get, "api/resource");
        request.Headers.Add("Authorization", authHeader);
        request.Headers.Add("X-Custom-Header", "custom-value");

        var response = await client.SendAsync(request);
        response.EnsureSuccessStatusCode();

        return await response.Content.ReadFromJsonAsync<MyData>();
    }
}

Saiba mais sobre lógica HTTP personalizada


Ao chamar APIs a jusante, a sua aplicação pode precisar de lidar com cenários em que a interação do utilizador é necessária. Isto acontece em três cenários principais:

  1. Consentimento Incremental - Solicitar permissões adicionais para além do inicialmente concedido
  2. Acesso Condicional - Cumprir requisitos de segurança como MFA, conformidade de dispositivos ou políticas de localização
  3. Expulsão da Cache de Token - Repopular a cache de token após o reinício da aplicação ou a expiração da cache

Microsoft. O Identity.Web fornece tratamento automático destes cenários com código mínimo necessário.

Compreende o fluxo

Quando o Microsoft.Identity.Web deteta que é necessária a interação do utilizador, lança um MicrosoftIdentityWebChallengeUserException. O framework gere isto automaticamente através do [AuthorizeForScopes] atributo ou do MicrosoftIdentityConsentAndConditionalAccessHandler serviço (para Blazor), que:

  1. Redireciona o utilizador para o Microsoft Entra ID para consentimento/autenticação
  2. Preserva a URL original do pedido
  3. Devolve o utilizador ao destino pretendido após completar o fluxo
  4. Armazena em cache os tokens recém-adquiridos

Rever pré-requisitos

Para ativar o processamento automático de consentimento, certifique-se Program.cs de que inclui a seguinte configuração.

builder.Services.AddMicrosoftIdentityWebAppAuthentication(builder.Configuration, "AzureAd")
    .EnableTokenAcquisitionToCallDownstreamApi()
    .AddDownstreamApi("MyAPI", builder.Configuration.GetSection("MyAPI"))
    .AddInMemoryTokenCaches();

// For MVC applications - enables the account controller
builder.Services.AddControllersWithViews()
    .AddMicrosoftIdentityUI();

// Ensure routes are mapped
app.UseAuthentication();
app.UseAuthorization();

app.MapControllers(); // Required for AccountController

Aplicar [AuthorizeForScopes] em controladores MVC

O atributo [AuthorizeForScopes], definido em controladores ou ações de controlador, trata automaticamente de MicrosoftIdentityWebChallengeUserException desafiando o utilizador quando são necessárias permissões adicionais.

Declarar âmbitos em linha

Especifique os escopos necessários diretamente no atributo.

using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Identity.Web;
using Microsoft.Identity.Abstractions;

[Authorize]
[AuthorizeForScopes(Scopes = new[] { "user.read" })]
public class ProfileController : Controller
{
    private readonly IDownstreamApi _downstreamApi;

    public ProfileController(IDownstreamApi downstreamApi)
    {
        _downstreamApi = downstreamApi;
    }

    public async Task<IActionResult> Index()
    {
        // AuthorizeForScopes automatically handles consent challenges
        var userData = await _downstreamApi.GetForUserAsync<UserData>(
            "MyAPI",
            options => options.RelativePath = "api/profile");

        return View(userData);
    }

    // Different action requires additional scopes
    [AuthorizeForScopes(Scopes = new[] { "user.read", "mail.read" })]
    public async Task<IActionResult> Emails()
    {
        var emails = await _downstreamApi.GetForUserAsync<EmailList>(
            "GraphAPI",
            options => options.RelativePath = "me/messages");

        return View(emails);
    }
}

Configurar os telescópios a partir das configurações da aplicação

Armazene os escopos em appsettings.json para melhor manutenção.

appsettings.json:

{
  "AzureAd": {
    "Instance": "https://login.microsoftonline.com/",
    "TenantId": "common",
    "ClientId": "[Your-Client-ID]",
    "ClientCredentials": [
      {
        "SourceType": "ClientSecret",
        "ClientSecret": "[Your-Client-Secret]"
      }
    ]
  },
  "DownstreamApis": {
    "TodoList": {
      "BaseUrl": "https://localhost:5001",
      "Scopes": [ "api://[API-Client-ID]/access_as_user" ]
    },
    "GraphAPI": {
      "BaseUrl": "https://graph.microsoft.com/v1.0",
      "Scopes": [ "https://graph.microsoft.com/Mail.Read", "https://graph.microsoft.com/Mail.Send" ]
    }
  }
}

Controlador:

[Authorize]
[AuthorizeForScopes(ScopeKeySection = "DownstreamApis:TodoList:Scopes:0")]
public class TodoListController : Controller
{
    private readonly IDownstreamApi _downstreamApi;

    public TodoListController(IDownstreamApi downstreamApi)
    {
        _downstreamApi = downstreamApi;
    }

    public async Task<IActionResult> Index()
    {
        var todos = await _downstreamApi.GetForUserAsync<IEnumerable<TodoItem>>(
            "TodoList",
            options => options.RelativePath = "api/todolist");

        return View(todos);
    }

    [AuthorizeForScopes(ScopeKeySection = "DownstreamApis:GraphAPI:Scopes:0")]
    public async Task<IActionResult> EmailTodos()
    {
        // If user hasn't consented to Mail.Send, they'll be prompted
        await _downstreamApi.PostForUserAsync<EmailMessage, object>(
            "GraphAPI",
            new EmailMessage { /* ... */ },
            options => options.RelativePath = "me/sendMail");

        return RedirectToAction("Index");
    }
}

Configurar o ID externo Microsoft Entra com fluxos de utilizador

Para aplicações External ID (B2C) com múltiplos fluxos de utilizador, especifique o fluxo de utilizador no atributo.

[Authorize]
public class AccountController : Controller
{
    private const string SignUpSignInFlow = "b2c_1_susi";
    private const string EditProfileFlow = "b2c_1_edit_profile";
    private const string ResetPasswordFlow = "b2c_1_reset";

    [AuthorizeForScopes(
        ScopeKeySection = "DownstreamApis:TodoList:Scopes:0",
        UserFlow = SignUpSignInFlow)]
    public async Task<IActionResult> Index()
    {
        var data = await _downstreamApi.GetForUserAsync<UserData>(
            "TodoList",
            options => options.RelativePath = "api/data");

        return View(data);
    }

    [AuthorizeForScopes(
        Scopes = new[] { "openid", "offline_access" },
        UserFlow = EditProfileFlow)]
    public async Task<IActionResult> EditProfile()
    {
        // This triggers the B2C edit profile flow
        return RedirectToAction("Index");
    }
}

Aplicar [AuthorizeForScopes] nas páginas do Razor

Aplicar [AuthorizeForScopes] à classe do modelo de página:

using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.Identity.Web;
using Microsoft.Identity.Abstractions;

[Authorize]
[AuthorizeForScopes(ScopeKeySection = "DownstreamApis:MyAPI:Scopes:0")]
public class IndexModel : PageModel
{
    private readonly IDownstreamApi _downstreamApi;

    public UserData UserData { get; set; }

    public IndexModel(IDownstreamApi downstreamApi)
    {
        _downstreamApi = downstreamApi;
    }

    public async Task OnGetAsync()
    {
        // Automatically handles consent challenges
        UserData = await _downstreamApi.GetForUserAsync<UserData>(
            "MyAPI",
            options => options.RelativePath = "api/profile");
    }
}

As aplicações Blazor Server requerem um tratamento explícito de exceções através do MicrosoftIdentityConsentAndConditionalAccessHandler serviço.

Configurar Program.cs

Regista o gestor de consentimento do Blazor Server no teu código de arranque.

builder.Services.AddMicrosoftIdentityWebAppAuthentication(builder.Configuration, "AzureAd")
    .EnableTokenAcquisitionToCallDownstreamApi()
    .AddDownstreamApis("TodoList", builder.Configuration.GetSection("DownstreamApis"))
    .AddInMemoryTokenCaches();

// Register the consent handler for Blazor
builder.Services.AddServerSideBlazor()
    .AddMicrosoftIdentityConsentHandler();

Criar o componente Blazor

Envolver chamadas de API em blocos try-catch e usar ConsentHandler.HandleException() para lidar com desafios de consentimento.

@page "/todolist"
@using Microsoft.Identity.Web
@using Microsoft.Identity.Abstractions
@using MyApp.Models

@inject MicrosoftIdentityConsentAndConditionalAccessHandler ConsentHandler
@inject IDownstreamApi DownstreamApi

<h3>My Todo List</h3>

@if (todos == null)
{
    <p><em>Loading...</em></p>
}
else
{
    <ul>
        @foreach (var todo in todos)
        {
            <li>@todo.Title</li>
        }
    </ul>
}

@code {
    private IEnumerable<TodoItem> todos;

    protected override async Task OnInitializedAsync()
    {
        await LoadTodosAsync();
    }

    [AuthorizeForScopes(ScopeKeySection = "DownstreamApis:TodoList:Scopes:0")]
    private async Task LoadTodosAsync()
    {
        try
        {
            todos = await DownstreamApi.GetForUserAsync<IEnumerable<TodoItem>>(
                "TodoList",
                options => options.RelativePath = "api/todolist");
        }
        catch (Exception ex)
        {
            // Handles MicrosoftIdentityWebChallengeUserException
            // and initiates user consent/authentication flow
            ConsentHandler.HandleException(ex);
        }
    }

    private async Task AddTodoAsync(string title)
    {
        try
        {
            await DownstreamApi.PostForUserAsync<TodoItem, TodoItem>(
                "TodoList",
                new TodoItem { Title = title },
                options => options.RelativePath = "api/todolist");

            await LoadTodosAsync();
        }
        catch (Exception ex)
        {
            ConsentHandler.HandleException(ex);
        }
    }
}

Tratar exceções manualmente (avançado)

Se precisar de lógica de fluxo de consentimento personalizada, trate MicrosoftIdentityWebChallengeUserException explicitamente:

[Authorize]
public class AdvancedController : Controller
{
    private readonly IDownstreamApi _downstreamApi;
    private readonly ILogger<AdvancedController> _logger;

    public AdvancedController(
        IDownstreamApi downstreamApi,
        ILogger<AdvancedController> logger)
    {
        _downstreamApi = downstreamApi;
        _logger = logger;
    }

    public async Task<IActionResult> SendEmail()
    {
        try
        {
            await _downstreamApi.PostForUserAsync<EmailMessage, object>(
                "GraphAPI",
                new EmailMessage
                {
                    Subject = "Test",
                    Body = "Test message"
                },
                options => options.RelativePath = "me/sendMail");

            return RedirectToAction("Success");
        }
        catch (MicrosoftIdentityWebChallengeUserException ex)
        {
            // Log the consent requirement
            _logger.LogWarning(
                "Consent required for scopes: {Scopes}. Challenging user.",
                string.Join(", ", ex.Scopes));

            // Custom properties for redirect
            var properties = new AuthenticationProperties
            {
                RedirectUri = Url.Action("SendEmail", "Advanced"),
            };

            // Add custom state if needed
            properties.Items["consent_attempt"] = "1";

            return Challenge(properties, OpenIdConnectDefaults.AuthenticationScheme);
        }
        catch (HttpRequestException ex)
        {
            _logger.LogError(ex, "Failed to send email");
            return View("Error");
        }
    }
}

Lidar com cenários de acesso condicional

As políticas de acesso condicional podem exigir fatores adicionais de autenticação. O tratamento é idêntico ao consentimento incremental:

[Authorize]
[AuthorizeForScopes(ScopeKeySection = "DownstreamApis:SecureAPI:Scopes:0")]
public class SecureDataController : Controller
{
    private readonly IDownstreamApi _downstreamApi;

    public SecureDataController(IDownstreamApi downstreamApi)
    {
        _downstreamApi = downstreamApi;
    }

    public async Task<IActionResult> Index()
    {
        // If conditional access requires MFA, AuthorizeForScopes
        // automatically challenges the user
        var sensitiveData = await _downstreamApi.GetForUserAsync<SensitiveData>(
            "SecureAPI",
            options => options.RelativePath = "api/sensitive");

        return View(sensitiveData);
    }
}

Gatilhos comuns de acesso condicional:

  • Autenticação multifator (AMF)
  • Requisito de dispositivo compatível
  • Localização confiável da rede
  • Aceitação dos termos de utilização
  • Requisito de alteração de palavra-passe

Siga as melhores práticas

Aplique estas recomendações ao implementar o consentimento e o tratamento do acesso condicional.

Utilização [AuthorizeForScopes] - Abordagem mais simples para controladores MVC e Razor Pages

Armazenar âmbitos em configuração - Usar ScopeKeySection = "DownstreamApis:ApiName:Scopes:0" para referenciar os âmbitos em appsettings.json

Aplicar ao nível do controlador - Definir os escopos predefinidos no controlador, anular ações específicas

Tratar exceções no Blazor - Sempre envolver chamadas API com try-catch e usar ConsentHandler.HandleException()

Permitir relançar exceções - Se apanhar , volte a lançá-lo para que MicrosoftIdentityWebChallengeUserException possa processar

Teste o acesso condicional - Verifique se a sua aplicação gere corretamente a MFA e outras políticas da CA

Não suprima exceções – Apanhar sem voltar a lançar quebra o fluxo de consentimento

Não guarde respostas em cache indefinidamente – os tokens expiram; Design para Reautenticação


Permissões Estáticas (Consentimento do Administrador)

Todas as permissões são solicitadas durante o registo da aplicação e consentidas por um administrador de inquilino:

Prós:

  • Os utilizadores nunca veem os pedidos de consentimento
  • Obrigatório para aplicações Microsoft de primeira parte
  • Experiência de utilizador mais simples

Contras:

  • Exige envolvimento da administração dos inquilinos
  • Excessivamente privilegiado desde o início
  • Menos flexível para cenários multi-inquilino

Configuration:

// Request all pre-approved scopes for Microsoft Graph
var scopes = new[] { "https://graph.microsoft.com/.default" };

var userData = await _downstreamApi.GetForUserAsync<UserData>(
    "GraphAPI",
    options =>
    {
        options.RelativePath = "me";
        options.Scopes = scopes; // Use .default scope
    });

Consentimento Incremental (Dinâmico)

As permissões são solicitadas conforme necessário durante o tempo de execução:

Prós:

  • Melhor segurança (princípio do menor privilégio)
  • Os utilizadores consentem àquilo que realmente utilizam
  • Funciona para aplicações multi-inquilino

Contras:

  • Os utilizadores podem ser interrompidos por pedidos de consentimento
  • Requer manipulação MicrosoftIdentityWebChallengeUserException

Recomendação: Use consentimento incremental para aplicações multi-inquilinos; Use permissões estáticas para aplicações empresariais de primeira mão onde o consentimento do administrador é garantido


Configurar cache de tokens

Microsoft.Identity.Web armazena tokens em cache para melhorar o desempenho e reduzir chamadas para o serviço Microsoft Entra.

Usar cache em memória (por defeito)

Adicione uma cache de tokens em memória para desenvolvimento ou cenários de servidor único.

builder.Services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
    .AddMicrosoftIdentityWebApp(builder.Configuration.GetSection("AzureAd"))
    .EnableTokenAcquisitionToCallDownstreamApi()
    .AddInMemoryTokenCaches(); // In-memory cache

Utilização para:

  • Desenvolvimento
  • Implementações de servidor único
  • Pequena base de utilizadores

Limitations:

  • Não partilhado entre instâncias
  • Perdi no reinício da aplicação
  • O consumo de memória cresce com os utilizadores

Configure uma cache distribuída, como Redis ou SQL Server, para implementações em produção.

// Install: Microsoft.Identity.Web.TokenCache

// Redis
builder.Services.AddStackExchangeRedisCache(options =>
{
    options.Configuration = builder.Configuration["Redis:ConnectionString"];
    options.InstanceName = "MyApp_";
});

builder.Services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
    .AddMicrosoftIdentityWebApp(builder.Configuration.GetSection("AzureAd"))
    .EnableTokenAcquisitionToCallDownstreamApi()
    .AddDistributedTokenCaches();

// SQL Server
builder.Services.AddDistributedSqlServerCache(options =>
{
    options.ConnectionString = builder.Configuration["SqlCache:ConnectionString"];
    options.SchemaName = "dbo";
    options.TableName = "TokenCache";
});

builder.Services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
    .AddMicrosoftIdentityWebApp(builder.Configuration.GetSection("AzureAd"))
    .EnableTokenAcquisitionToCallDownstreamApi()
    .AddDistributedTokenCaches();

Utilização para:

  • Implementações multi-servidor (com balanceamento de carga)
  • Cenários de elevada disponibilidade
  • Grande base de utilizadores
  • Cache persistente ao longo dos reinícios

Lidar com falhas na aquisição de tokens

Apanhe exceções comuns

O código seguinte demonstra como detetar e lidar com as exceções mais comuns na aquisição de tokens.

try
{
    var data = await _downstreamApi.GetForUserAsync<MyData>(
        "MyAPI",
        options => options.RelativePath = "api/resource");
}
catch (MicrosoftIdentityWebChallengeUserException ex)
{
    // User needs to consent or reauthenticate
    _logger.LogWarning($"User consent required: {ex.Message}");
    return Challenge(new AuthenticationProperties { RedirectUri = Request.Path });
}
catch (MsalUiRequiredException ex)
{
    // User interaction required (sign-in again, MFA, etc.)
    _logger.LogWarning($"User interaction required: {ex.Message}");
    return Challenge(OpenIdConnectDefaults.AuthenticationScheme);
}
catch (MsalServiceException ex)
{
    // Service error (Microsoft Entra ID unavailable, etc.)
    _logger.LogError(ex, "Microsoft Entra ID service error");
    return StatusCode(503, "Authentication service temporarily unavailable");
}
catch (HttpRequestException ex)
{
    // Downstream API unreachable
    _logger.LogError(ex, "Downstream API call failed");
    return StatusCode(503, "Downstream service unavailable");
}

Implementar degradação suave

Carregue dados opcionais das APIs posteriores e volte aos valores predefinidos quando as chamadas falham.

public async Task<IActionResult> Dashboard()
{
    var model = new DashboardModel();

    // Try to load optional data from downstream API
    try
    {
        model.EnrichedData = await _downstreamApi.GetForUserAsync<EnrichedData>(
            "MyAPI",
            options => options.RelativePath = "api/enriched");
    }
    catch (Exception ex)
    {
        _logger.LogWarning(ex, "Failed to load enriched data, using defaults");
        model.EnrichedData = new EnrichedData { /* defaults */ };
    }

    return View(model);
}

** Implementar OWIN (Framework .NET)

Para aplicações web baseadas em OWIN no .NET Framework, siga estes passos.

1. Instalar pacotes

Instala os pacotes NuGet necessários.

Install-Package Microsoft.Identity.Web.OWIN
Install-Package Microsoft.Owin.Host.SystemWeb

2. Configurar o arranque

Configure a autenticação do Microsoft Entra e a aquisição de tokens na classe de inicialização OWIN.

using Microsoft.Identity.Web;
using Microsoft.Owin.Security;
using Microsoft.Owin.Security.Cookies;
using Owin;

public class Startup
{
    public void Configuration(IAppBuilder app)
    {
        app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);

        app.UseCookieAuthentication(new CookieAuthenticationOptions());

        app.AddMicrosoftIdentityWebApp(
            Configuration,
            configSectionName: "AzureAd",
            openIdConnectScheme: "OpenIdConnect",
            cookieScheme: CookieAuthenticationDefaults.AuthenticationType,
            subscribeToOpenIdConnectMiddlewareDiagnosticsEvents: true);

        app.EnableTokenAcquisitionToCallDownstreamApi();
        app.AddDistributedTokenCaches();
    }
}

3. Chamar a API a jusante

Adquira um token e invoque a API downstream a partir de um controlador MVC.

using Microsoft.Identity.Web;
using System.Threading.Tasks;
using System.Web.Mvc;

[Authorize]
public class ProfileController : Controller
{
    public async Task<ActionResult> Index()
    {
        var downstreamApi = TokenAcquirerFactory.GetDefaultInstance()
            .GetTokenAcquirer()
            .GetDownstreamApi();

        var userData = await downstreamApi.GetForUserAsync<UserData>(
            "MyAPI",
            options => options.RelativePath = "api/profile");

        return View(userData);
    }
}

Nota: o suporte OWIN tem algumas diferenças em relação ao ASP.NET Core. Consulte a documentação do OWIN para mais detalhes.


Siga as melhores práticas de segurança

Gerir os âmbitos

Aplicar o princípio do privilégio mínimo ao solicitar permissões de API.

Do:

  • Solicite apenas os escopos de acesso que precisar
  • Use consentimento incremental para funcionalidades avançadas
  • Documente os âmbitos necessários na sua aplicação

Don't:

  • Solicite permissões desnecessárias desde o início
  • Solicite escopos apenas para administradores sem justificação
  • Assumir que todos os âmbitos serão concedidos

Gerir tokens de forma segura

Siga estas orientações para proteger os tokens de acesso na sua aplicação.

Do:

  • Deixe a Microsoft. Identity.Web gerir tokens
  • Usar cache distribuído em produção
  • Lidar com falhas na aquisição de tokens de maneira elegante

Don't:

  • Armazena os tokens tu próprio
  • Tokens de acesso de registo
  • Enviar tokens para o código do lado do cliente

Lidar com erros

Implemente um tratamento robusto de erros para falhas de autenticação e chamadas de API.

Do:

  • Capturar e gerir exceções de consentimento
  • Fornecer mensagens de erro claras aos utilizadores
  • Registar erros para depuração

Don't:

  • Expor erros de token aos utilizadores
  • Falhar silenciosamente as chamadas de API
  • Ignorar exceções de autenticação

Resolver problemas comuns

Revise estas soluções para encontrar erros de autenticação frequentemente encontrados.

Problema: "AADSTS65001: O utilizador ou administrador não consentiu"

Causa: O utilizador não consentiu nas permissões obrigatórias.

Solution:

catch (MicrosoftIdentityWebChallengeUserException ex)
{
    // Redirect to consent page
    return Challenge(
        new AuthenticationProperties { RedirectUri = Request.Path },
        OpenIdConnectDefaults.AuthenticationScheme);
}

Problema: "AADSTS50076: É necessária autenticação multifator"

Causa: O utilizador precisa de completar MFA.

Solution:

catch (MsalUiRequiredException)
{
    // Redirect user to sign in with MFA
    return Challenge(OpenIdConnectDefaults.AuthenticationScheme);
}

Problema: Tokens que não persistem ao longo das reinicializações da aplicação

Causa: Usar cache em memória.

Solution: Mudar para cache distribuída (Redis, SQL Server ou Cosmos DB).

Problema: 401 Não autorizado na API downstream

Causas possíveis:

  • Pediram telescópios errados
  • Permissão API não concedida no registo da aplicação
  • Token expirado

Solution:

  1. Verificar se os escopos em appsettings.json correspondem aos requisitos da API
  2. Verifique se o registo da aplicação tem permissões da API
  3. Garantir que os tokens estão a ser armazenados em cache e atualizados

Para diagnósticos detalhados: Consulte o Guia de Registo e Diagnóstico para IDs de correlação, depuração de cache de tokens e padrões abrangentes de resolução de problemas.


Otimize o desempenho

Planejar estratégia de cache de tokens

Selecione uma estratégia de cache que corresponda à sua topologia de implementação.

  • Use cache distribuído para implantações multi-servidor
  • Configurar a expiração adequada da cache
  • Monitorizar o desempenho da cache

Minimizar pedidos de token

Microsoft. O Identity.Web armazena tokens em cache automaticamente. Ambas as chamadas no exemplo seguinte reutilizam o mesmo token em cache.

// Bad: Multiple token acquisitions
var profile = await _downstreamApi.GetForUserAsync<Profile>(
    "API",
    options => options.RelativePath = "profile");
var settings = await _downstreamApi.GetForUserAsync<Settings>(
    "API",
    options => options.RelativePath = "settings");

// Good: Single token, multiple calls (token is cached)
// Both calls use the same cached token
var profile = await _downstreamApi.GetForUserAsync<Profile>(
    "API",
    options => options.RelativePath = "profile");
var settings = await _downstreamApi.GetForUserAsync<Settings>(
    "API",
    options => options.RelativePath = "settings");

Fazer chamadas paralelas de API

Chamar várias APIs downstream em simultâneo para reduzir a latência global.

// Call multiple APIs in parallel
var profileTask = _downstreamApi.GetForUserAsync<Profile>(
    "API1",
    options => options.RelativePath = "profile");
var settingsTask = _downstreamApi.GetForUserAsync<Settings>(
    "API2",
    options => options.RelativePath = "settings");

await Task.WhenAll(profileTask, settingsTask);

var profile = profileTask.Result;
var settings = settingsTask.Result;

Encontre orientações adicionais para cenários relacionados.