Condividi tramite


Autorizzazione nelle API Web con Microsoft. Identity.Web

Questa guida illustra come implementare l'autorizzazione nelle API Web ASP.NET Core usando Microsoft. Identity.Web. L'autorizzazione garantisce che i chiamanti autenticati dispongano degli ambiti necessari (autorizzazioni delegate) o delle autorizzazioni dell'app (autorizzazioni dell'applicazione ) per accedere alle risorse protette.

Informazioni generali

Autenticazione e autorizzazione

Concetto Scopo Result
Autenticazione Verificare l'identità 401 Non autorizzato se ha esito negativo
Autorizzazione Verificare le autorizzazioni 403 Proibito se non sufficiente

Cosa viene convalidato?

Quando un'API Web riceve un token di accesso, Microsoft. Identity.Web convalida:

  1. Firma del token : proviene da un'autorità attendibile?
  2. Destinatari dei token : è destinato a questa API?
  3. Scadenza del token : è ancora valida?
  4. Ambiti/Ruoli - L'app client e il soggetto (utente) hanno le autorizzazioni corrette?

Questa guida è incentrata sul numero 4: convalida degli ambiti e delle autorizzazioni dell'app.


Concetti relativi all'autorizzazione

Ambiti (autorizzazioni delegate)

Usato quando: Un utente delega l'autorizzazione a un'app per agire per suo conto.

Attestazione token:scp o scope per l'app client Valori di esempio:"access_as_user", "User.Read", "Files.ReadWrite"

Attestazione token:rolesValori di esempio:"admin""SimpleUser" per l'utente.

Scenario: Web API per conto dell'utente autenticato.

Autorizzazioni per le app (autorizzazioni dell'applicazione)

Usato quando: API Web chiamata da un'app che funge da solo (nessun contesto utente), ad esempio un servizio daemon/in background.

Attestazione token:rolesValori di esempio:"Mail.Read.All", "User.Read.All"

Scenario: L'app daemon chiama l'API Web usando le credenziali client.


Convalida dell'ambito con RequiredScope

L'attributo RequiredScope convalida che il token di accesso contenga almeno uno degli ambiti specificati.

Avvio rapido

1. Abilitare l'autorizzazione nell'API:

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

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddMicrosoftIdentityWebApi(builder.Configuration.GetSection("AzureAd"));

builder.Services.AddAuthorization(); // Required for authorization

var app = builder.Build();

app.UseAuthentication();
app.UseAuthorization(); // Must be after UseAuthentication
app.MapControllers();

app.Run();

2. Proteggere controller o azioni:

using Microsoft.AspNetCore.Authorization;
using Microsoft.Identity.Web.Resource;

[Authorize]
[RequiredScope("access_as_user")]
public class TodoListController : ControllerBase
{
    [HttpGet]
    public IActionResult GetTodos()
    {
        // Only accessible if token has "access_as_user" scope
        return Ok(new[] { "Todo 1", "Todo 2" });
    }
}

Modelli di utilizzo

Modello 1: Ambiti codificati in modo rigido

Usare quando: Gli ambiti sono fissi e noti in fase di sviluppo.

[Authorize]
[RequiredScope("access_as_user")]
public class TodoListController : ControllerBase
{
    // All actions require "access_as_user" scope
}

Più ambiti (se uno è corrispondente):

[Authorize]
[RequiredScope("read", "write", "admin")]
public class TodoListController : ControllerBase
{
    // Token must have "read" OR "write" OR "admin"
}

Modello 2: Ambiti dalla configurazione

Usare quando: Gli ambiti devono essere configurabili per ogni ambiente.

appsettings.json:

{
  "AzureAd": {
    "Instance": "https://login.microsoftonline.com/",
    "TenantId": "your-tenant-id",
    "ClientId": "your-api-client-id",
    "Scopes": "access_as_user read write"
  }
}

Controller:

[Authorize]
[RequiredScope(RequiredScopesConfigurationKey = "AzureAd:Scopes")]
public class TodoListController : ControllerBase
{
    // Scopes read from configuration
}

** Vantaggio:** Modificare gli ambiti senza ricompilare.

Modello 3: ambiti di Action-Level

Usare quando: Diverse azioni richiedono autorizzazioni diverse.

[Authorize]
public class TodoListController : ControllerBase
{
    [HttpGet]
    [RequiredScope("read")]
    public IActionResult GetTodos()
    {
        return Ok(todos);
    }

    [HttpPost]
    [RequiredScope("write")]
    public IActionResult CreateTodo([FromBody] Todo todo)
    {
        // Only tokens with "write" scope can create
        return CreatedAtAction(nameof(GetTodos), todo);
    }

    [HttpDelete("{id}")]
    [RequiredScope("admin")]
    public IActionResult DeleteTodo(int id)
    {
        // Only tokens with "admin" scope can delete
        return NoContent();
    }
}

Come funziona

Quando arriva una richiesta:

  1. ASP.NET Core middleware di autenticazione convalida il token
  2. RequiredScopel'attributo verifica la presenza dell'attestazione scp o scope
  3. Se il token contiene almeno un ambito corrispondente → la richiesta procede
  4. Se non è stato trovato alcun ambito corrispondente → risposta 403 Accesso negato

Esempio di risposta di errore:

{
  "error": "insufficient_scope",
  "error_description": "The token does not have the required scope 'access_as_user'."
}

Autorizzazioni dell'app con RequiredScopeOrAppPermission

L'attributo RequiredScopeOrAppPermission convalida gli ambiti (delegati) o le autorizzazioni dell'app (applicazione).

Quando utilizzare

** Usa RequiredScopeOrAppPermission quando:**

  • La tua API supporta sia le applicazioni delegate dagli utenti che le applicazioni daemon/servizio
  • Lo stesso endpoint deve accettare token da applicazioni web (ambiti) o servizi in background (autorizzazioni delle app)

** Usa RequiredScope quando:**

  • L'API gestisce solo le richieste delegate dall'utente

Avvio rapido

using Microsoft.Identity.Web.Resource;

[Authorize]
[RequiredScopeOrAppPermission(
    AcceptedScope = new[] { "access_as_user" },
    AcceptedAppPermission = new[] { "TodoList.ReadWrite.All" }
)]
public class TodoListController : ControllerBase
{
    [HttpGet]
    public IActionResult GetTodos()
    {
        // Accessible with EITHER:
        // - User-delegated token with "access_as_user" scope, OR
        // - App-only token with "TodoList.ReadWrite.All" app permission
        return Ok(todos);
    }
}

Autorizzazioni delle app basate su configurazione

appsettings.json:

{
  "AzureAd": {
    "Instance": "https://login.microsoftonline.com/",
    "TenantId": "your-tenant-id",
    "ClientId": "your-api-client-id",
    "Scopes": "access_as_user",
    "AppPermissions": "TodoList.ReadWrite.All TodoList.Admin"
  }
}

Controller:

[Authorize]
[RequiredScopeOrAppPermission(
    RequiredScopesConfigurationKey = "AzureAd:Scopes",
    RequiredAppPermissionsConfigurationKey = "AzureAd:AppPermissions"
)]
public class TodoListController : ControllerBase
{
    // Scopes and app permissions from configuration
}

Differenze nelle richieste dei token

Tipo di token Richiesta di rimborso Valore di esempio
Delegata dall'utente scp oppure scope "access_as_user User.Read"
Solo app roles ["TodoList.ReadWrite.All"]

Esempio: Token delegato dall'utente:

{
  "aud": "api://your-api-client-id",
  "iss": "https://login.microsoftonline.com/.../v2.0",
  "scp": "access_as_user",
  "sub": "user-object-id",
  ...
}

Esempio: Token solo per app:

{
  "aud": "api://your-api-client-id",
  "iss": "https://login.microsoftonline.com/.../v2.0",
  "roles": ["TodoList.ReadWrite.All"],
  "sub": "app-object-id",
  ...
}

Criteri di autorizzazione

Per scenari di autorizzazione più complessi, usare ASP.NET Core criteri di autorizzazione.

Perché usare i criteri?

  • Logica centralizzata : definire le regole di autorizzazione una sola volta, riutilizzare ovunque
  • Componibile : combinare più requisiti (ambiti + attestazioni e logica personalizzata)
  • Testable : logica di autorizzazione di unit test più semplice
  • Flessibile - Requisiti personalizzati oltre la convalida dell'ambito

Modello 1: Politica con RequireScope

using Microsoft.Identity.Web;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddMicrosoftIdentityWebApi(builder.Configuration.GetSection("AzureAd"));

builder.Services.AddAuthorization(options =>
{
    options.AddPolicy("TodoReadPolicy", policyBuilder =>
    {
        policyBuilder.RequireScope("read", "access_as_user");
    });

    options.AddPolicy("TodoWritePolicy", policyBuilder =>
    {
        policyBuilder.RequireScope("write", "admin");
    });
});

var app = builder.Build();

Controller:

[Authorize]
public class TodoListController : ControllerBase
{
    [HttpGet]
    [Authorize(Policy = "TodoReadPolicy")]
    public IActionResult GetTodos()
    {
        return Ok(todos);
    }

    [HttpPost]
    [Authorize(Policy = "TodoWritePolicy")]
    public IActionResult CreateTodo([FromBody] Todo todo)
    {
        return CreatedAtAction(nameof(GetTodos), todo);
    }
}

Modello 2: Policy con ScopeAuthorizationRequirement

using Microsoft.Identity.Web;
using Microsoft.Identity.Web.Resource;

builder.Services.AddAuthorization(options =>
{
    options.AddPolicy("CustomPolicy", policyBuilder =>
    {
        policyBuilder.AddRequirements(
            new ScopeAuthorizationRequirement(new[] { "access_as_user" })
        );
    });
});

Modello 3: Criterio predefinito (si applica a tutti [autorizza])

builder.Services.AddAuthorization(options =>
{
    var defaultPolicy = new AuthorizationPolicyBuilder()
        .RequireScope("access_as_user")
        .Build();

    options.DefaultPolicy = defaultPolicy;
});

Ora ogni [Authorize] attributo richiede automaticamente l'ambito "access_as_user":

[Authorize] // Automatically requires "access_as_user" scope
public class TodoListController : ControllerBase
{
    // All actions protected by default policy
}

Modello 4: Combinazione di più requisiti

builder.Services.AddAuthorization(options =>
{
    options.AddPolicy("AdminPolicy", policyBuilder =>
    {
        policyBuilder.RequireScope("admin");
        policyBuilder.RequireRole("Admin"); // Also check role claim
        policyBuilder.RequireAuthenticatedUser();
    });
});

Modello 5: criteri basati su configurazione

var requiredScopes = builder.Configuration["AzureAd:Scopes"]?.Split(' ');

builder.Services.AddAuthorization(options =>
{
    options.AddPolicy("ApiAccessPolicy", policyBuilder =>
    {
        if (requiredScopes != null)
        {
            policyBuilder.RequireScope(requiredScopes);
        }
    });
});

Filtro dei tenant

Limitare l'accesso API agli utenti solo da tenant specifici.

caso d'uso

Scenario: L'API multi-tenant deve accettare token solo dai clienti approvati.

Implementation

builder.Services.AddAuthorization(options =>
{
    string[] allowedTenants =
    {
        "14c2f153-90a7-4689-9db7-9543bf084dad", // Contoso tenant
        "af8cc1a0-d2aa-4ca7-b829-00d361edb652", // Fabrikam tenant
        "979f4440-75dc-4664-b2e1-2cafa0ac67d1"  // Northwind tenant
    };

    options.AddPolicy("AllowedTenantsOnly", policyBuilder =>
    {
        policyBuilder.RequireClaim(
            "http://schemas.microsoft.com/identity/claims/tenantid",
            allowedTenants
        );
    });

    // Apply to all endpoints by default
    options.DefaultPolicy = options.GetPolicy("AllowedTenantsOnly");
});

Filtro dei tenant basato sulla configurazione

appsettings.json:

{
  "AzureAd": {
    "Instance": "https://login.microsoftonline.com/",
    "ClientId": "your-api-client-id",
    "AllowedTenants": [
      "14c2f153-90a7-4689-9db7-9543bf084dad",
      "af8cc1a0-d2aa-4ca7-b829-00d361edb652"
    ]
  }
}

Avvio:

var allowedTenants = builder.Configuration.GetSection("AzureAd:AllowedTenants")
    .Get<string[]>();

builder.Services.AddAuthorization(options =>
{
    options.AddPolicy("AllowedTenantsOnly", policyBuilder =>
    {
        policyBuilder.RequireClaim(
            "http://schemas.microsoft.com/identity/claims/tenantid",
            allowedTenants ?? Array.Empty<string>()
        );
    });
});

Combinato: Scope e filtro dei tenant

builder.Services.AddAuthorization(options =>
{
    options.AddPolicy("SecureApiAccess", policyBuilder =>
    {
        // Require specific scope
        policyBuilder.RequireScope("access_as_user");

        // AND require specific tenant
        policyBuilder.RequireClaim(
            "http://schemas.microsoft.com/identity/claims/tenantid",
            allowedTenants
        );
    });
});

Procedure consigliate

Cose da fare

1. Nelle API Web usare [Authorize] sempre con la convalida dell'ambito:

[Authorize] // Authentication
[RequiredScope("access_as_user")] // Authorization
public class MyController : ControllerBase { }

2. Usare la configurazione per ambiti specifici dell'ambiente:

[RequiredScope(RequiredScopesConfigurationKey = "AzureAd:Scopes")]

3. Applicare privilegi minimi:

[HttpGet]
[RequiredScope("read")] // Only read permission needed

[HttpPost]
[RequiredScope("write")] // Write permission for modifications

4. Usare i criteri per l'autorizzazione complessa:

builder.Services.AddAuthorization(options =>
{
    options.AddPolicy("AdminOnly", policy =>
    {
        policy.RequireScope("admin");
        policy.RequireClaim("department", "IT");
    });
});

5. Abilitare risposte di errore dettagliate nello sviluppo:

if (builder.Environment.IsDevelopment())
{
    Microsoft.IdentityModel.Logging.IdentityModelEventSource.ShowPII = true;
}

Cose da non fare

1. Non ignorare [Authorize] quando si usa RequiredScope:

//  Wrong - RequiredScope won't work without [Authorize]
[RequiredScope("access_as_user")]
public class MyController : ControllerBase { }

//  Correct
[Authorize]
[RequiredScope("access_as_user")]
public class MyController : ControllerBase { }

2. Non utilizzare gli ID tenant codificati in modo rigido nell'ambiente di produzione:

//  Wrong
policyBuilder.RequireClaim("tid", "14c2f153-90a7-4689-9db7-9543bf084dad");

//  Better - use configuration
var tenants = Configuration.GetSection("AllowedTenants").Get<string[]>();
policyBuilder.RequireClaim("tid", tenants);

3. Non confondere gli ambiti con i ruoli:

//  Wrong - This checks roles claim, not scopes
[RequiredScope("Admin")] // "Admin" is typically a role, not a scope

//  Correct
[RequiredScope("access_as_user")] // Scope
[Authorize(Roles = "Admin")] // Role

4. Non esporre informazioni sull'ambito sensibili nei messaggi di errore (produzione):

Configurare i livelli di registrazione e la gestione degli errori appropriati per gli ambienti di produzione.


Risoluzione dei problemi

403 Accesso vietato - Ambito mancante

Errore: L'API restituisce 403 anche con token valido.

Diagnosi:

  1. Decodificare il token in jwt.ms
  2. Verifica scp o scope attestazione
  3. Verificare che corrisponda all'attributo RequiredScope

Soluzione:

  • Assicurarsi che l'app client richieda l'ambito corretto durante l'acquisizione del token
  • Verificare che l'ambito sia esposto nella registrazione dell'app per le API
  • Concedere il consenso amministratore se necessario

RequiredScope non funziona

Sintomo: L'attributo sembra essere ignorato.

Controllare:

  1. Hai aggiunto l'attributo [Authorize] ?
  2. Viene app.UseAuthorization() chiamato dopo app.UseAuthentication()?
  3. È services.AddAuthorization() registrato?

Chiave di configurazione non trovata

Errore: La convalida dell'ambito fallisce silenziosamente.

Controllare:

{
  "AzureAd": {
    "Scopes": "access_as_user" // Matches RequiredScopesConfigurationKey
  }
}

Verificare che il percorso di configurazione corrisponda esattamente.