Como usar Identity para proteger um back-end da API Web para SPAs

Observação

Esta não é a versão mais recente deste artigo. Para informações sobre a versão vigente, confira a Versão do .NET 8 deste artigo.

Importante

Essas informações relacionam-se ao produto de pré-lançamento, que poderá ser substancialmente modificado antes do lançamento comercial. A Microsoft não oferece nenhuma garantia, explícita ou implícita, quanto às informações fornecidas aqui.

Para informações sobre a versão vigente, confira a Versão do .NET 8 deste artigo.

ASP.NET Core Identity fornece APIs que lidam com autenticação, autorização e gerenciamento de identidade. As APIs possibilitam proteger pontos de extremidade de um back-end da API Web com autenticação baseada em cookie. Há uma opção baseada em token para clientes que não podem usar cookies.

Este artigo mostra como usar Identity para proteger um back-end da API Web para SPAs, como aplicativos Angular, React e Vue. As mesmas APIs de back-end podem ser usadas para proteger Blazor WebAssembly aplicativos.

Pré-requisitos

As etapas mostradas neste artigo adicionam autenticação e autorização a um aplicativo de API Web do ASP.NET Core que:

  • Ainda não está configurado para autenticação.
  • Destina-se a net8.0 ou posteriores.
  • Pode ser a API mínima ou a API baseada em controlador.

Algumas das instruções de testes contidas neste artigo usam a interface do usuário do Swagger que está incluída no modelo de projeto. A interface do usuário do Swagger não é necessária para usar a Identity com um back-end da Web API.

Instalar os pacotes NuGet

Instale os seguintes pacotes NuGet:

Para começar de maneira mais rápida, use o banco de dados na memória.

Altere o banco de dados posteriormente para SQLite ou SQL Server para salvar dados do usuário entre sessões ao testar ou para uso em produção. Isso introduz alguma complexidade em comparação com a memória, pois exige que o banco de dados seja criado por meio de migrações, conforme mostrado no EF Core tutorial de introdução.

Instale esses pacotes usando o gerenciador de pacotes NuGet no Visual Studio ou o comando dotnet add package da CLI.

Criar uma IdentityDbContext

Adicione uma classe nomeada ApplicationDbContext que herda de IdentityDbContext<TUser>:

using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;

public class ApplicationDbContext : IdentityDbContext<IdentityUser>
{
    public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options) :
        base(options)
    { }
}

O código mostrado fornece um construtor especial que possibilita configurar o banco de dados para ambientes diferentes.

Adicione uma ou mais das seguintes using diretivas conforme necessário ao adicionar o código mostrado nestas etapas.

using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;

Configurar o contexto EF Core

Como observado anteriormente, a maneira mais simples de começar é usar o banco de dados na memória. Com a memória, cada execução começa com um banco de dados novo e não há necessidade de usar migrações. Após a chamada para WebApplication.CreateBuilder(args), adicione o seguinte código para configurar Identity a usar um banco de dados na memória:

builder.Services.AddDbContext<ApplicationDbContext>(
    options => options.UseInMemoryDatabase("AppDb"));

Para salvar dados do usuário entre sessões ao testar ou para uso em produção, altere o banco de dados posteriormente para SQLite ou SQL Server.

Adiciona serviços Identity ao contêiner

Após a chamada, WebApplication.CreateBuilder(args)chame AddAuthorization para adicionar serviços ao contêiner de DI (injeção de dependência):

builder.Services.AddAuthorization();

Ativar APIs Identity

Após a chamada para WebApplication.CreateBuilder(args), chame AddIdentityApiEndpoints<TUser>(IServiceCollection) e AddEntityFrameworkStores<TContext>(IdentityBuilder).

builder.Services.AddIdentityApiEndpoints<IdentityUser>()
    .AddEntityFrameworkStores<ApplicationDbContext>();

Por padrão, os cookie tokens e proprietários são ativados. Os Cookies e tokens serão emitidos no login se o parâmetro useCookies da cadeia de caracteres de consulta no ponto de extremidade de login for true.

Rotas de mapa Identity

Após a chamada para builder.Build(), chame MapIdentityApi<TUser>(IEndpointRouteBuilder) para mapear os pontos de extremidade Identity:

app.MapIdentityApi<IdentityUser>();

Proteja os pontos de extremidade selecionados

Para proteger um ponto de extremidade, use o método de extensão RequireAuthorization na chamada Map{Method} que define a rota. Por exemplo:

app.MapGet("/weatherforecast", (HttpContext httpContext) =>
{
    var forecast = Enumerable.Range(1, 5).Select(index =>
        new WeatherForecast
        {
            Date = DateOnly.FromDateTime(DateTime.Now.AddDays(index)),
            TemperatureC = Random.Shared.Next(-20, 55),
            Summary = summaries[Random.Shared.Next(summaries.Length)]
        })
        .ToArray();
    return forecast;
})
.WithName("GetWeatherForecast")
.WithOpenApi()
.RequireAuthorization();

O método RequireAuthorization também pode ser usado para:

  • Proteger os pontos de extremidade da interface do usuário do Swagger, conforme mostrado no exemplo a seguir:

    app.MapSwagger().RequireAuthorization();
    
  • Proteger com uma declaração ou permissão específicas, conforme mostrado no exemplo a seguir:

    .RequireAuthorization("Admin");
    

Em um projeto de API Web baseado em controlador, proteja os pontos de extremidade aplicando o atributo [Authorize] a um controlador ou ação.

Testar a API

Uma maneira rápida de testar a autenticação é usar o banco de dados na memória e a interface do usuário do Swagger incluída no modelo de projeto. As etapas a seguir mostram como testar a API com a interface do usuário do Swagger. Verifique se os pontos de extremidade da interface do usuário do Swagger não estão protegidos.

Tentar acessar um ponto de extremidade seguro

  • Execute o aplicativo e navegue até a interface do usuário do Swagger.
  • Expanda um ponto de extremidade protegido, como /weatherforecast em um projeto criado pelo modelo de API Web.
  • Selecione Vamos experimentar.
  • Selecione Executar. Observe que a resposta é 401 - not authorized.

Testar o registro

  • Expanda /register e selecione Experimentar.

  • Na seção Parâmetros da interface do usuário, um corpo de solicitação de exemplo é mostrado:

    {
      "email": "string",
      "password": "string"
    }
    
  • Substitua "cadeia de caracteres" por um endereço de email e senha válidos e selecione Executar.

    Para cumprir as regras de validação de senha padrão, a senha deve ter pelo menos seis caracteres e conter pelo menos um dos seguintes caracteres:

    • Letra maiúscula
    • Letra minúscula
    • Dígito numérico
    • Caractere não alfanumérico

    Se você inserir um endereço de email inválido ou uma senha inválida, o resultado incluirá os erros de validação. Aqui está um exemplo de um corpo de resposta com erros de validação:

    {
      "type": "https://tools.ietf.org/html/rfc9110#section-15.5.1",
      "title": "One or more validation errors occurred.",
      "status": 400,
      "errors": {
        "PasswordTooShort": [
          "Passwords must be at least 6 characters."
        ],
        "PasswordRequiresNonAlphanumeric": [
          "Passwords must have at least one non alphanumeric character."
        ],
        "PasswordRequiresDigit": [
          "Passwords must have at least one digit ('0'-'9')."
        ],
        "PasswordRequiresLower": [
          "Passwords must have at least one lowercase ('a'-'z')."
        ]
      }
    }
    

    Os erros são retornados no formato ProblemDetails para que o cliente possa analisá-los e exibir os erros de validação na medida do necessário.

    Um registro bem-sucedido resulta em uma resposta 200 - OK.

Testar o login

  • Expanda /login e selecione Experimentar. O corpo da solicitação de exemplo mostra dois parâmetros adicionais:

    {
      "email": "string",
      "password": "string",
      "twoFactorCode": "string",
      "twoFactorRecoveryCode": "string"
    }
    

    As propriedades JSON extras não são necessárias para este exemplo e podem ser excluídas. Definir useCookies para true.

  • Substitua "cadeia de caracteres" pelo endereço de email e senha que você usou para registrar e selecione Executar.

    Um logon bem-sucedido resulta em uma resposta 200 - OK com um cookie no cabeçalho de resposta.

Retestar o ponto de extremidade protegido

Após um logon bem-sucedido, execute novamente o ponto de extremidade protegido. A autenticação cookie é enviada automaticamente com a solicitação e o ponto de extremidade é autorizado. CookieA autenticação baseada em segurança é interna no navegador e "apenas funciona".

Testar com clientes que não sejam um navegador

Alguns clientes Web podem não incluir cookies no cabeçalho por padrão:

  • Se você estiver usando uma ferramenta para testar APIs, talvez seja necessário habilitar cookies nas configurações.

  • A API JavaScript fetch não inclui cookies por padrão. Habilite-os definindo credentials ao valor nas opções include.

  • Um HttpClient sendo executado em um aplicativo do Blazor WebAssembly precisa de uma HttpRequestMessage que inclua credenciais, como o exemplo a seguir:

    request.SetBrowserRequestCredential(BrowserRequestCredentials.Include);
    

Usar autenticação baseada em token

Para clientes que não dão suporte a cookies, a API de logon fornece um parâmetro para solicitar tokens. Um token personalizado (que é proprietário da plataforma de identidade do ASP.NET Core) é emitido e pode ser usado para autenticar solicitações subsequentes. O token é passado no cabeçalho Authorization como um token de portador. Um token de atualização também é fornecido. Esse token permite que o aplicativo solicite um novo token quando o antigo expirar sem forçar o usuário a fazer logon novamente.

Os tokens não são Tokens JSON Web padrão (JWTs). O uso de tokens personalizados é intencional, pois a API interna Identity destina-se principalmente a cenários simples. A opção de token não pretende ser um provedor de serviço de identidade ou servidor de token com todos os recursos, mas sim uma alternativa à opção cookie para clientes que não podem usar cookies.

Para usar a autenticação baseada em token, defina o parâmetro useCookies da cadeia de caracteres de consulta como falsequando chamar o ponto de extremidade de /login. Os tokens usam o esquema de autenticação token de portador. Com o uso do token retornado da chamada para /login, as chamadas subsequentes para pontos de extremidade protegidos devem adicionar o cabeçalho Authorization: Bearer <token>, onde <token> é o token de acesso. Para obter mais informações, confira Usar o ponto de extremidade POST /login mais à frente neste artigo.

Faça logoff

Para fornecer ao usuário uma maneira de se desconectar, defina um ponto de extremidade de /logout como o do exemplo a seguir:

app.MapPost("/logout", async (SignInManager<IdentityUser> signInManager,
    [FromBody] object empty) =>
{
    if (empty != null)
    {
        await signInManager.SignOutAsync();
        return Results.Ok();
    }
    return Results.Unauthorized();
})
.WithOpenApi()
.RequireAuthorization();

Forneça um objeto JSON vazio ({}) no corpo da solicitação quando chamar esse ponto de extremidade. O código a seguir é um exemplo de uma chamada para o ponto de extremidade de logout:

public signOut() {
  return this.http.post('/logout', {}, {
    withCredentials: true,
    observe: 'response',
    responseType: 'text'

Os pontos de extremidade de MapIdentityApi<TUser>

A chamada para MapIdentityApi<TUser> adiciona os seguintes pontos de extremidade ao aplicativo:

Usar o ponto de extremidade POST /register

O corpo da solicitação deve ter as propriedades Email e Password:

{
  "email": "string",
  "password": "string",
}

Para saber mais, veja:

Usar o ponto de extremidade POST /login

No corpo da solicitação, serão necessários Email e Password. Se a autenticação de dois fatores (2FA) estiver habilitada, serão necessários TwoFactorCode ou TwoFactorRecoveryCode. Se a 2FA não estiver habilitada, omita tanto twoFactorCode quanto twoFactorRecoveryCode. Para obter mais informações, confira Usar o ponto de extremidade POST /manage/2fa mais à frente neste artigo.

Abaixo um exemplo do corpo da solicitação com a 2FA não habilitada:

{
  "email": "string",
  "password": "string"
}

Abaixo um exemplo do corpo da solicitação com a 2FA habilitada:

  • {
      "email": "string",
      "password": "string",
      "twoFactorCode": "string",
    }
    
  • {
      "email": "string",
      "password": "string",
      "twoFactorRecoveryCode": "string"
    }
    

O ponto de extremidade espera um parâmetro de cadeia de caracteres de consulta:

  • useCookies — definido como true para a autenticação baseada em cookie. Defina como false ou omita a autenticação baseada em token.

Para obter mais informações sobre a autenticação baseada em cookie, confira Testar o login anteriormente neste artigo.

Autenticação baseada em token

Se useCookies for false ou estiver omitido, a autenticação baseada em token estará habilitada. O corpo da resposta inclui as seguintes propriedades:

{
  "tokenType": "string",
  "accessToken": "string",
  "expiresIn": 0,
  "refreshToken": "string"
}

Para obter mais informações sobre essas propriedades, confira AccessTokenResponse.

Coloque o token de acesso em um cabeçalho para fazer solicitações autenticadas, conforme mostrado no exemplo a seguir

Authorization: Bearer {access token}

Quando o token de acesso estiver prestes a expirar, chame o ponto de extremidade /refresh.

Usar o ponto de extremidade POST /refresh

Para uso somente com a autenticação baseada em token. Obtém um novo token de acesso sem forçar o usuário a fazer login novamente. Chame esse ponto de extremidade quando o token de acesso estiver prestes a expirar.

O corpo da solicitação contém apenas o RefreshToken. Abaixo um exemplo de corpo da solicitação:

{
  "refreshToken": "string"
}

Se a chamada for bem-sucedida, o corpo da resposta será uma AccessTokenResponse nova, conforme mostrado no exemplo a seguir:

{
  "tokenType": "string",
  "accessToken": "string",
  "expiresIn": 0,
  "refreshToken": "string"
}

Usar o ponto de extremidade GET /confirmEmail

Se Identity estiver configurado para confirmação por email, uma chamada bem-sucedida para o ponto de extremidade /register enviará um email contendo um link para o ponto de extremidade /confirmEmail. O link contém os seguintes parâmetros de cadeia de caracteres de consulta:

  • userId
  • code
  • changedEmail — incluído somente se o usuário tiver alterado o endereço de email durante o registro.

Identity fornece texto padrão para o email de confirmação. Por padrão, o assunto do email será "Confirme seu email" e o corpo do email será mais ou menos como o seguinte exemplo:

 Please confirm your account by <a href='https://contoso.com/confirmEmail?userId={user ID}&code={generated code}&changedEmail={new email address}'>clicking here</a>.

Se a propriedade RequireConfirmedEmail estiver definida como true, o usuário não poderá fazer login antes de confirmar seu endereço de email clicando no link dentro do email. O ponto de extremidade /confirmEmail:

  • Confirma o endereço de email e permite que o usuário faça login.
  • Retorna o texto "Obrigado por confirmar seu email" no corpo da resposta.

Para configurar Identity para a confirmação por email, adicione o código em Program.cs para configurar RequireConfirmedEmail como true e adicione uma classe que implemente IEmailSender ao contêiner de DI. Por exemplo:

builder.Services.Configure<IdentityOptions>(options =>
{
    options.SignIn.RequireConfirmedEmail = true;
});

builder.Services.AddTransient<IEmailSender, EmailSender>();

Para obter mais informações, veja Confirmação de conta e recuperação de senha no ASP.NET Core.

Identity fornece texto padrão para outros emails que também precisam ser enviados, como 2FA e redefinição de senha. Para personalizar esses emails, forneça uma implementação personalizada da interface IEmailSender. No exemplo anterior, EmailSender é uma classe que implementa IEmailSender. Para obter mais informações, incluindo um exemplo de uma classe que implemente IEmailSender, confira Confirmação de conta e recuperação de senha no ASP.NET Core.

Usar o ponto de extremidade POST /resendConfirmationEmail

Envia um email somente se o endereço for válido para um usuário registrado.

O corpo da solicitação contém apenas o Email. Abaixo um exemplo de corpo da solicitação:

{
  "email": "string"
}

Para obter mais informações, confira Usar o ponto de extremidade GET /confirmEmail anteriormente neste artigo.

Usar o ponto de extremidade POST /forgotPassword

Gera um email contendo um código de redefinição de senha. Envie esse código para /resetPassword com uma nova senha.

O corpo da solicitação contém apenas o Email. Aqui está um exemplo:

{
  "email": "string"
}

Para obter informações sobre como habilitar a Identity para enviar emails, confira Usar o ponto de extremidade GET /confirmEmail.

Usar o ponto de extremidade POST /resetPassword

Chame esse ponto de extremidade após obter um código de redefinição ao chamar o ponto de extremidade /forgotPassword.

O corpo da solicitação requer Email, ResetCode e NewPassword. Aqui está um exemplo:

{
  "email": "string",
  "resetCode": "string",
  "newPassword": "string"
}

Usar o ponto de extremidade POST /manage/2fa

Configura a autenticação de dois fatores (2FA) para o usuário. Quando a 2FA está habilitada, um login bem-sucedido requer um código produzido por um aplicativo autenticador além do endereço de email e senha.

Habilitar 2FA

Para habilitar a 2FA para o usuário autenticado no momento:

  • Chame o ponto de extremidade /manage/2fa enviando um objeto JSON vazio ({}) no corpo da solicitação.

  • O corpo da resposta fornece SharedKey junto com algumas outras propriedades que não são necessárias nesse momento. A chave compartilhada é usada para configurar o aplicativo autenticador. Exemplo de corpo da resposta:

    {
      "sharedKey": "string",
      "recoveryCodesLeft": 0,
      "recoveryCodes": null,
      "isTwoFactorEnabled": false,
      "isMachineRemembered": false
    }
    
  • Use a chave compartilhada para obter uma senha única baseada em tempo (TOTP). Para obter mais informações, confira Habilitar a geração de um código QR para aplicativos autenticadores de TOTP no ASP.NET Core.

  • Chame o ponto de extremidade /manage/2fa enviando a TOTP e "enable": true no corpo da solicitação. Por exemplo:

    {
      "enable": true,
      "twoFactorCode": "string"
    }
    
  • O corpo da resposta confirma que IsTwoFactorEnabled é verdadeiro e fornece RecoveryCodes. Os códigos de recuperação são usados para fazer login quando o aplicativo autenticador não estiver disponível. Exemplo de um corpo de resposta após habilitar a 2FA com sucesso:

    {
      "sharedKey": "string",
      "recoveryCodesLeft": 10,
      "recoveryCodes": [
        "string",
        "string",
        "string",
        "string",
        "string",
        "string",
        "string",
        "string",
        "string",
        "string"
      ],
      "isTwoFactorEnabled": true,
      "isMachineRemembered": false
    }
    

Fazer login com 2FA

Chame o ponto de extremidade /login enviando o endereço de email, a senha e a TOTP no corpo da solicitação. Por exemplo:

{
  "email": "string",
  "password": "string",
  "twoFactorCode": "string"
}

Se o usuário não tiver acesso ao aplicativo autenticador, deve fazer login chamando o ponto de extremidade /login com um dos códigos de recuperação que foram fornecidos quando a 2FA foi habilitada. O corpo da solicitação é parecido com o seguinte exemplo:

{
  "email": "string",
  "password": "string",
  "twoFactorRecoveryCode": "string"
}

Redefinir os códigos de recuperação

Para obter uma nova coleção de códigos de recuperação, chame esse ponto de extremidade com ResetRecoveryCodes definido como true. Abaixo um exemplo de corpo da solicitação:

{
  "resetRecoveryCodes": true
}

Redefinir a chave compartilhada

Para obter uma nova chave compartilhada aleatória, chame esse ponto de extremidade com ResetSharedKey definido como true. Abaixo um exemplo de corpo da solicitação:

{
  "resetSharedKey": true
}

Redefinir a chave desabilita automaticamente a exigência de um login com dois fatores para o usuário autenticado até que este seja habilitado novamente por uma solicitação posterior.

Esqueça o computador

Para limpar o cookie "sinalizador lembre-se de mim", caso esteja presente, chame esse ponto de extremidade com ForgetMachine definido como true. Abaixo um exemplo de corpo da solicitação:

{
  "forgetMachine": true
}

Esse ponto de extremidade não afeta a autenticação baseada em token.

Usar o ponto de extremidade GET /manage/info

Obtém o endereço de email e o status de confirmação de email do usuário conectado. As declarações foram omitidas desse ponto de extremidade por motivos de segurança. Se as declarações forem necessárias, use as APIs do lado do servidor para configurar um ponto de extremidade para declarações. Ou, em vez de compartilhar todas as declarações de usuários, forneça um ponto de extremidade de validação que aceite uma declaração e responda se o usuário a tem.

A solicitação não requer nenhum parâmetro. O corpo da resposta inclui as propriedades e Email e IsEmailConfirmed, como no seguinte exemplo:

{
  "email": "string",
  "isEmailConfirmed": true
}

Usar o ponto de extremidade POST /manage/info

Atualiza o endereço de email e a senha do usuário conectado. Envie NewEmail, NewPassword e OldPassword no corpo da solicitação, conforme mostrado no exemplo a seguir:

{
  "newEmail": "string",
  "newPassword": "string",
  "oldPassword": "string"
}

Aqui está um exemplo do corpo da resposta:

{
  "email": "string",
  "isEmailConfirmed": false
}

Confira também

Para saber mais, consulte os recursos a seguir:

Os modelos ASP.NET Core oferecem autenticação em SPAs (Aplicativos de Página Única) usando o suporte para autorização de API. O ASP.NET Core Identity para autenticar e armazenar usuários é combinado com o Duende IdentityServer para implementar o OpenID Connect.

Importante

O Software Duende pode exigir que você pague uma taxa de licença pelo uso de produção do Duende Identity Server. Para obter mais informações, consulte Migrar do ASP.NET Core 5.0 para o 6.0.

Foi adicionado um parâmetro de autenticação aos modelos de projeto Angular e React semelhante ao parâmetro de autenticação nos modelos de projeto Aplicativo Web (Model-View-Controller) (MVC) e Aplicativo Web (Páginas Razor). Os valores de parâmetro permitidos são Nenhum e Individual. No momento, o modelo de projeto React.js e Redux não dá suporte ao parâmetro de autenticação.

Criar um aplicativo com suporte à autorização de API

A autenticação e a autorização do usuário podem ser usadas com SPAs Angular e React. Abra um shell de comando e execute o seguinte comando:

Angular:

dotnet new angular -au Individual

React:

dotnet new react -au Individual

O comando anterior cria um aplicativo ASP.NET Core com um diretório ClientApp que contém o SPA.

Descrição geral dos componentes de ASP.NET Core do aplicativo

As seções a seguir descrevem adições ao projeto quando o suporte à autenticação está incluído:

Program.cs

Os exemplos de código a seguir dependem do pacote NuGet Microsoft.AspNetCore.ApiAuthorization.IdentityServer. Os exemplos configuram a autenticação e a autorização da API usando os métodos de extensão AddApiAuthorization e AddIdentityServerJwt. Os projetos que usam os modelos de projeto SPA React ou Angular com autenticação incluem uma referência a esse pacote.

dotnet new angular -au Individual gera o seguinte arquivo Program.cs:

using Microsoft.AspNetCore.Authentication;
using Microsoft.EntityFrameworkCore;
using output_directory_name.Data;
using output_directory_name.Models;

var builder = WebApplication.CreateBuilder(args);

var connectionString = builder.Configuration.GetConnectionString("DefaultConnection") ?? throw new InvalidOperationException("Connection string 'DefaultConnection' not found.");
builder.Services.AddDbContext<ApplicationDbContext>(options =>
    options.UseSqlite(connectionString));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();

builder.Services.AddDefaultIdentity<ApplicationUser>(options => options.SignIn.RequireConfirmedAccount = true)
    .AddEntityFrameworkStores<ApplicationDbContext>();

builder.Services.AddIdentityServer()
    .AddApiAuthorization<ApplicationUser, ApplicationDbContext>();

builder.Services.AddAuthentication()
    .AddIdentityServerJwt();

builder.Services.AddControllersWithViews();
builder.Services.AddRazorPages();

var app = builder.Build();

if (app.Environment.IsDevelopment())
{
    app.UseMigrationsEndPoint();
}
else
{
    app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();

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

app.MapControllerRoute(
    name: "default",
    pattern: "{controller}/{action=Index}/{id?}");
app.MapRazorPages();

app.MapFallbackToFile("index.html");

app.Run();

O código anterior configura:

  • Identity com a interface do usuário padrão:

    builder.Services.AddDbContext<ApplicationDbContext>(options =>
        options.UseSqlite(connectionString));
    builder.Services.AddDatabaseDeveloperPageExceptionFilter();
    
    builder.Services.AddDefaultIdentity<ApplicationUser>(options => options.SignIn.RequireConfirmedAccount = true)
        .AddEntityFrameworkStores<ApplicationDbContext>();
    
  • IdentityServer com um método auxiliar adicional AddApiAuthorization que define algumas convenções padrão do ASP.NET Core no topo do IdentityServer:

    builder.Services.AddIdentityServer()
        .AddApiAuthorization<ApplicationUser, ApplicationDbContext>();
    
  • Autenticação com um método auxiliar adicional AddIdentityServerJwt que configura o aplicativo para validar tokens JWT produzidos pelo IdentityServer:

    builder.Services.AddAuthentication()
    .AddIdentityServerJwt();
    
  • O middleware de autenticação é responsável por validar as credenciais de solicitação e definir o usuário no contexto da solicitação:

    app.UseAuthentication();
    
  • O Middleware do IdentityServer expõe os pontos de extremidade do OpenID Connect:

    app.UseIdentityServer();
    

Serviço de Aplicativo do Azure no Linux

Para implantações Serviço de Aplicativo do Azure no Linux, especifique o emissor explicitamente:

builder.Services.Configure<JwtBearerOptions>(
    IdentityServerJwtConstants.IdentityServerJwtBearerScheme, 
    options =>
    {
        options.Authority = "{AUTHORITY}";
    });

No código anterior, o espaço reservado {AUTHORITY} é o Authority a ser usado ao fazer chamadas do OpenID Connect.

Exemplo:

options.Authority = "https://contoso-service.azurewebsites.net";

AddApiAuthorization

Esse método auxiliar configura o IdentityServer para usar nossa configuração com suporte. O IdentityServer é uma estrutura poderosa e extensível para lidar com questões de segurança do aplicativo. Ao mesmo tempo, isso expõe uma complexidade desnecessária para os cenários mais comuns. Portanto, é fornecido a você um conjunto de convenções e opções de configuração considerado um bom ponto de partida. Quando for necessário alterar a autenticação, o poder total do IdentityServer ainda estará disponível para personalizar a autenticação de acordo com suas necessidades.

AddIdentityServerJwt

O método auxiliar configura uma política para o aplicativo como o manipulador de autenticação padrão. A política está configurada para permitir que o Identity lide com todas as solicitações roteadas para qualquer subcaminho no espaço de URL do Identity "/Identity". Ele JwtBearerHandler manipula todas as outras solicitações. Além disso, este método registra um recurso de API <<ApplicationName>>API com o IdentityServer com um escopo padrão de <<ApplicationName>>API e configura o middleware do token de portador JWT para validar tokens emitidos pelo IdentityServer para o aplicativo.

WeatherForecastController

No arquivo, observe o atributo [Authorize] aplicado à classe que indica que o usuário precisa ser autorizado com base na política padrão para acessar o recurso. A política de autorização padrão está configurada para usar o esquema de autenticação padrão, que é configurado pelo AddIdentityServerJwt para o esquema de política mencionado acima, tornando o JwtBearerHandler configurado por esse método auxiliar, o manipulador padrão para solicitações para o aplicativo.

ApplicationDbContext

No arquivo, observe que o mesmo DbContext é usado no Identity com a exceção de que ele se estende ApiAuthorizationDbContext (uma classe mais derivada de IdentityDbContext) para incluir o esquema do IdentityServer.

Para obter controle total do esquema do banco de dados, herde de uma das classes IdentityDbContext disponíveis e configure o contexto para incluir o esquema do Identity chamando builder.ConfigurePersistedGrantContext(_operationalStoreOptions.Value) no método OnModelCreating.

OidcConfigurationController

No arquivo, observe o ponto de extremidade provisionado para atender aos parâmetros OIDC que o cliente precisa usar.

appsettings.json

No arquivo appsettings.json da raiz do projeto, há uma nova seção IdentityServer que descreve a lista de clientes configurados. No exemplo a seguir, há um único cliente. O nome do cliente corresponde ao nome do aplicativo e é mapeado por convenção para o parâmetro ClientId OAuth. O perfil indica o tipo de aplicativo que está sendo configurado. Ele é usado internamente para conduzir convenções que simplificam o processo de configuração do servidor. Há vários perfis disponíveis, conforme explicado na seção Perfis de aplicativo.

"IdentityServer": {
  "Clients": {
    "angularindividualpreview3final": {
      "Profile": "IdentityServerSPA"
    }
  }
}

appsettings.Development.json

No arquivo appsettings.Development.json da raiz do projeto, há uma seção do IdentityServer que descreve a chave usada para assinar tokens. Ao implantar na produção, uma chave precisa ser provisionada e implantada junto com o aplicativo, conforme explicado na seção Implantar na produção.

"IdentityServer": {
  "Key": {
    "Type": "Development"
  }
}

Descrição geral do aplicativo Angular

O suporte à autenticação e à autorização de API no modelo Angular reside em seu próprio módulo Angular no diretório ClientApp/src/api-authorization. O módulo é composto pelos seguintes elementos:

  • Três componentes:
    • login.component.ts: manipula o fluxo de logon do aplicativo.
    • logout.component.ts: manipula o fluxo de logoff do aplicativo.
    • login-menu.component.ts: um widget que exibe um dos seguintes conjuntos de links:
      • Gerenciamento do perfil do usuário e links de logoff quando o usuário é autenticado.
      • Links de registro e logon quando o usuário não é autenticado.
  • Uma proteção de rota AuthorizeGuard que pode ser adicionada às rotas e exige que um usuário seja autenticado antes de visitar a rota.
  • Um interceptador HTTP AuthorizeInterceptor que anexa o token de acesso a solicitações HTTP de saída direcionadas à API quando o usuário é autenticado.
  • Um serviço AuthorizeService que manipula os detalhes de nível inferior do processo de autenticação e expõe informações sobre o usuário autenticado para o restante do aplicativo para consumo.
  • Um módulo Angular que define rotas associadas às partes de autenticação do aplicativo. Ele expõe o componente do menu de logon, o interceptador, a proteção e o serviço para consumo do restante do aplicativo.

Descrição geral do aplicativo React

O suporte para autenticação e autorização de API no modelo React reside no diretório ClientApp/src/components/api-authorization. Ele é composto pelos seguintes elementos:

  • Quatro componentes:
    • Login.js: manipula o fluxo de logon do aplicativo.
    • Logout.js: manipula o fluxo de logoff do aplicativo.
    • LoginMenu.js: um widget que exibe um dos seguintes conjuntos de links:
      • Gerenciamento do perfil do usuário e links de logoff quando o usuário é autenticado.
      • Links de registro e logon quando o usuário não é autenticado.
    • AuthorizeRoute.js: um componente de rota que exige que um usuário seja autenticado antes de renderizar o componente indicado no parâmetro Component.
  • Uma instância authService exportada da classe AuthorizeService que manipula os detalhes de nível inferior do processo de autenticação e expõe informações sobre o usuário autenticado ao restante do aplicativo para consumo.

Agora que você já viu os principais componentes da solução, pode dar uma olhada mais profunda nos cenários individuais do aplicativo.

Exigir autorização em uma nova API

Por padrão, o sistema é configurado para exigir facilmente autorização para novas APIs. Para fazer isso, crie um novo controlador e adicione o atributo [Authorize] à classe do controlador ou a qualquer ação dentro do controlador.

Personalizar o manipulador de autenticação de API

Para personalizar a configuração do manipulador JWT da API, configure sua instância JwtBearerOptions:

builder.Services.AddAuthentication()
    .AddIdentityServerJwt();

builder.Services.Configure<JwtBearerOptions>(
    IdentityServerJwtConstants.IdentityServerJwtBearerScheme,
    options =>
    {
        ...
    });

O manipulador JWT da API gera eventos que permitem o controle sobre o processo de autenticação usando JwtBearerEvents. Para fornecer suporte para autorização de API, AddIdentityServerJwt registra seus próprios manipuladores de eventos.

Para personalizar a manipulação de um evento, encapsule o manipulador de eventos existente com lógica adicional, conforme necessário. Por exemplo:

builder.Services.Configure<JwtBearerOptions>(
    IdentityServerJwtConstants.IdentityServerJwtBearerScheme,
    options =>
    {
        var onTokenValidated = options.Events.OnTokenValidated;       

        options.Events.OnTokenValidated = async context =>
        {
            await onTokenValidated(context);
            ...
        }
    });

No código anterior, o manipulador de eventos OnTokenValidated é substituído por uma implementação personalizada. Essa implementação:

  1. Chama a implementação original fornecida pelo suporte à autorização da API.
  2. Execute sua própria lógica personalizada.

Proteger uma rota do lado do cliente (Angular)

A proteção de uma rota do lado do cliente é feita adicionando a proteção de autorização à lista de proteções a serem executadas ao configurar uma rota. Por exemplo, você pode ver como a rota fetch-data é configurada no módulo Angular do aplicativo principal:

RouterModule.forRoot([
  // ...
  { path: 'fetch-data', component: FetchDataComponent, canActivate: [AuthorizeGuard] },
])

É importante mencionar que a proteção de uma rota não protege o ponto de extremidade real (que ainda requer um atributo [Authorize] aplicado a ele), mas apenas impede que o usuário navegue para a rota do lado do cliente quando ela não estiver autenticada.

Autenticar solicitações de API (Angular)

A autenticação de solicitações para APIs hospedadas junto com o aplicativo é feita automaticamente por meio do uso do interceptador de cliente HTTP definido pelo aplicativo.

Proteger uma rota do lado do cliente (React)

Proteja uma rota do lado do cliente usando o componente AuthorizeRoute em vez do componente Route sem formatação. Por exemplo, observe como a rota fetch-data é configurada no componente App:

<AuthorizeRoute path='/fetch-data' component={FetchData} />

Proteção de uma rota:

  • Não protege o ponto de extremidade real (que ainda requer um atributo [Authorize] aplicado a ele).
  • Só impede que o usuário navegue até a rota fornecida do lado do cliente quando ela não é autenticada.

Autenticar solicitações de API (React)

A autenticação de solicitações com o React é feita importando primeiro a instância authService do AuthorizeService. O token de acesso é recuperado do authService e anexado à solicitação, conforme mostrado abaixo. Nos componentes React, esse trabalho geralmente é feito no método do ciclo de vida componentDidMount ou como resultado de alguma interação do usuário.

Importar o authService para um componente

import authService from './api-authorization/AuthorizeService'

Recuperar e anexar o token de acesso à resposta

async populateWeatherData() {
  const token = await authService.getAccessToken();
  const response = await fetch('api/SampleData/WeatherForecasts', {
    headers: !token ? {} : { 'Authorization': `Bearer ${token}` }
  });
  const data = await response.json();
  this.setState({ forecasts: data, loading: false });
}

Implantar para a produção

Para implantar o aplicativo na produção, os seguintes recursos precisam ser provisionados:

  • Um banco de dados para armazenar as contas de usuário do Identity e as concessões do IdentityServer.
  • Um certificado de produção a ser usado para assinar tokens.
    • Não há requisitos específicos para esse certificado. Ele pode ser um certificado autoassinado ou um certificado provisionado por meio de uma autoridade de AC.
    • Ele pode ser gerado por meio de ferramentas padrão, como o PowerShell ou o OpenSSL.
    • Ele pode ser instalado no repositório de certificados nos computadores de destino ou implantado como um arquivo .pfx com uma senha forte.

Exemplo: Implantar em um provedor de hospedagem Web não Azure

No painel de hospedagem da Web, crie ou carregue seu certificado. Em seguida, no arquivo appsettings.json do aplicativo, modifique a seção IdentityServer para incluir os principais detalhes. Por exemplo:

"IdentityServer": {
  "Key": {
    "Type": "Store",
    "StoreName": "WebHosting",
    "StoreLocation": "CurrentUser",
    "Name": "CN=MyApplication"
  }
}

No exemplo anterior:

  • StoreName representa o nome do repositório de certificados em que o certificado é armazenado. Nesse caso, ele aponta para o repositório de hospedagem da Web.
  • StoreLocation representa onde carregar o certificado (CurrentUser nesse caso).
  • Name corresponde à entidade diferenciada para o certificado.

Exemplo: implantar no Serviço de Aplicativo do Azure

Esta seção descreve a implantação do aplicativo no Serviço de Aplicativo do Azure usando um certificado armazenado no repositório de certificados. Para modificar o aplicativo para carregar um certificado do repositório de certificados, é necessário um plano de serviço de camada Standard ou superior ao configurar o aplicativo no portal do Azure em uma etapa posterior.

No arquivo appsettings.json do aplicativo, modifique a seção IdentityServer para incluir os principais detalhes:

"IdentityServer": {
  "Key": {
    "Type": "Store",
    "StoreName": "My",
    "StoreLocation": "CurrentUser",
    "Name": "CN=MyApplication"
  }
}
  • O nome do repositório representa o nome do repositório de certificados em que o certificado está armazenado. Nesse caso, ele aponta para o repositório de usuários pessoais.
  • O local do repositório representa onde carregar o certificado (CurrentUser ou LocalMachine).
  • A propriedade de nome no certificado corresponde ao assunto distinto do certificado.

Para implantar no Serviço de Aplicativo do Azure, siga as etapas em Implantar o aplicativo no Azure, que explica como criar os recursos necessários do Azure e implantar o aplicativo na produção.

Depois de seguir as instruções anteriores, o aplicativo é implantado no Azure, mas ainda não está funcional. O certificado usado pelo aplicativo deve ser configurado no portal do Azure. Localize a impressão digital do certificado e siga as etapas descritas em Carregar certificados.

Embora essas etapas mencionem o SSL, há uma seção de Certificados privados no portal do Azure, onde você pode carregar o certificado provisionado para usar com o aplicativo.

Depois de definir o aplicativo e as configurações do aplicativo no portal do Azure, reinicie o aplicativo no portal.

Outras opções de configuração

O suporte para autorização de API se baseia no IdentityServer com um conjunto de convenções, valores padrão e aprimoramentos para simplificar a experiência para SPAs. Todo o poder do IdentityServer está disponível nos bastidores se as integrações do ASP.NET Core não abrangerem seu cenário. O suporte do ASP.NET Core é focado em aplicativos "originais", nos quais todos os aplicativos são criados e implantados por nossa organização. Dessa forma, o suporte não é oferecido para coisas como consentimento ou federação. Para esses cenários, use o IdentityServer e siga sua documentação.

Perfis de aplicativo

Os perfis de aplicativo são configurações predefinidas para aplicativos que definem melhor seus parâmetros. Neste momento, há suporte para os seguintes perfis:

  • IdentityServerSPA: representa um SPA hospedado ao lado do IdentityServer como uma única unidade.
    • O /authentication/login-callback usa como padrão redirect_uri.
    • O /authentication/logout-callback usa como padrão post_logout_redirect_uri.
    • O conjunto de escopos inclui o openid, profile e todos os escopos definidos para as APIs no aplicativo.
    • O conjunto de tipos de resposta OIDC permitidos é id_token token ou cada um deles individualmente (id_token, token).
    • O modo de resposta permitido é fragment.
  • SPA: representa um SPA que não está hospedado com IdentityServer.
    • O conjunto de escopos inclui o openid, profile e todos os escopos definidos para as APIs no aplicativo.
    • O conjunto de tipos de resposta OIDC permitidos é id_token token ou cada um deles individualmente (id_token, token).
    • O modo de resposta permitido é fragment.
  • IdentityServerJwt: representa uma API hospedada junto com o IdentityServer.
    • O aplicativo está configurado para ter um único escopo que usa como padrão o nome do aplicativo.
  • API: representa uma API que não está hospedada com o IdentityServer.
    • O aplicativo está configurado para ter um único escopo que usa como padrão o nome do aplicativo.

Configuração por meio de AppSettings

Configure os aplicativos por meio do sistema de configuração adicionando-os à lista de Clients ou Resources.

Configure a propriedade redirect_uri e post_logout_redirect_uri de cada cliente, conforme mostrado no exemplo a seguir:

"IdentityServer": {
  "Clients": {
    "MySPA": {
      "Profile": "SPA",
      "RedirectUri": "https://www.example.com/authentication/login-callback",
      "LogoutUri": "https://www.example.com/authentication/logout-callback"
    }
  }
}

Ao configurar recursos, você pode configurar os escopos do recurso, conforme mostrado abaixo:

"IdentityServer": {
  "Resources": {
    "MyExternalApi": {
      "Profile": "API",
      "Scopes": "a b c"
    }
  }
}

Configuração por meio de código

Você também pode configurar os clientes e recursos por meio do código usando uma sobrecarga de AddApiAuthorization que executa uma ação para configurar opções.

AddApiAuthorization<ApplicationUser, ApplicationDbContext>(options =>
{
    options.Clients.AddSPA(
        "My SPA", spa =>
        spa.WithRedirectUri("http://www.example.com/authentication/login-callback")
           .WithLogoutRedirectUri(
               "http://www.example.com/authentication/logout-callback"));

    options.ApiResources.AddApiResource("MyExternalApi", resource =>
        resource.WithScopes("a", "b", "c"));
});

Recursos adicionais

Os modelos ASP.NET Core 3.1 e posteriores oferecem autenticação em SPAs (Aplicativos de Página Única) usando o suporte para autorização de API. O Identity do ASP.NET Core para autenticação e armazenamento de usuários é combinado com o IdentityServer para implementar o OpenID Connect.

Foi adicionado um parâmetro de autenticação aos modelos de projeto Angular e React semelhante ao parâmetro de autenticação nos modelos de projeto Aplicativo Web (Model-View-Controller) (MVC) e Aplicativo Web (Páginas Razor). Os valores de parâmetro permitidos são Nenhum e Individual. No momento, o modelo de projeto React.js e Redux não dá suporte ao parâmetro de autenticação.

Criar um aplicativo com suporte à autorização de API

A autenticação e a autorização do usuário podem ser usadas com SPAs Angular e React. Abra um shell de comando e execute o seguinte comando:

Angular:

dotnet new angular -o <output_directory_name> 

React:

dotnet new react -o <output_directory_name> -au Individual

O comando anterior cria um aplicativo ASP.NET Core com um diretório ClientApp que contém o SPA.

Descrição geral dos componentes de ASP.NET Core do aplicativo

As seções a seguir descrevem adições ao projeto quando o suporte à autenticação está incluído:

Classe Startup

Os exemplos de código a seguir dependem do pacote NuGet Microsoft.AspNetCore.ApiAuthorization.IdentityServer. Os exemplos configuram a autenticação e a autorização da API usando os métodos de extensão AddApiAuthorization e AddIdentityServerJwt. Os projetos que usam os modelos de projeto SPA React ou Angular com autenticação incluem uma referência a esse pacote.

A classe Startup tem as seguintes adições:

  • Dentro do método Startup.ConfigureServices:

    • Identity com a interface do usuário padrão:

      services.AddDbContext<ApplicationDbContext>(options =>
          options.UseSqlite(Configuration.GetConnectionString("DefaultConnection")));
      
      services.AddDefaultIdentity<ApplicationUser>()
          .AddEntityFrameworkStores<ApplicationDbContext>();
      
    • IdentityServer com um método auxiliar adicional AddApiAuthorization que define algumas convenções padrão do ASP.NET Core no topo do IdentityServer:

      services.AddIdentityServer()
          .AddApiAuthorization<ApplicationUser, ApplicationDbContext>();
      
    • Autenticação com um método auxiliar adicional AddIdentityServerJwt que configura o aplicativo para validar tokens JWT produzidos pelo IdentityServer:

      services.AddAuthentication()
          .AddIdentityServerJwt();
      
  • Dentro do método Startup.Configure:

    • O middleware de autenticação é responsável por validar as credenciais de solicitação e definir o usuário no contexto da solicitação:

      app.UseAuthentication();
      
    • O Middleware do IdentityServer expõe os pontos de extremidade do OpenID Connect:

      app.UseIdentityServer();
      

Serviço de Aplicativo do Azure no Linux

Para implantações do Serviço de Aplicativo do Azure no Linux, especifique o emissor explicitamente em Startup.ConfigureServices:

services.Configure<JwtBearerOptions>(
    IdentityServerJwtConstants.IdentityServerJwtBearerScheme, 
    options =>
    {
        options.Authority = "{AUTHORITY}";
    });

No código anterior, o espaço reservado {AUTHORITY} é o Authority a ser usado ao fazer chamadas do OpenID Connect.

Exemplo:

options.Authority = "https://contoso-service.azurewebsites.net";

AddApiAuthorization

Esse método auxiliar configura o IdentityServer para usar nossa configuração com suporte. O IdentityServer é uma estrutura poderosa e extensível para lidar com questões de segurança do aplicativo. Ao mesmo tempo, isso expõe uma complexidade desnecessária para os cenários mais comuns. Portanto, é fornecido a você um conjunto de convenções e opções de configuração considerado um bom ponto de partida. Quando for necessário alterar a autenticação, o poder total do IdentityServer ainda estará disponível para personalizar a autenticação de acordo com suas necessidades.

AddIdentityServerJwt

O método auxiliar configura uma política para o aplicativo como o manipulador de autenticação padrão. A política está configurada para permitir que o Identity lide com todas as solicitações roteadas para qualquer subcaminho no espaço de URL do Identity "/Identity". Ele JwtBearerHandler manipula todas as outras solicitações. Além disso, este método registra um recurso de API <<ApplicationName>>API com o IdentityServer com um escopo padrão de <<ApplicationName>>API e configura o middleware do token de portador JWT para validar tokens emitidos pelo IdentityServer para o aplicativo.

WeatherForecastController

No arquivo, observe o atributo [Authorize] aplicado à classe que indica que o usuário precisa ser autorizado com base na política padrão para acessar o recurso. A política de autorização padrão está configurada para usar o esquema de autenticação padrão, que é configurado pelo AddIdentityServerJwt para o esquema de política mencionado acima, tornando o JwtBearerHandler configurado por esse método auxiliar, o manipulador padrão para solicitações para o aplicativo.

ApplicationDbContext

No arquivo, observe que o mesmo DbContext é usado no Identity com a exceção de que ele se estende ApiAuthorizationDbContext (uma classe mais derivada de IdentityDbContext) para incluir o esquema do IdentityServer.

Para obter controle total do esquema do banco de dados, herde de uma das classes IdentityDbContext disponíveis e configure o contexto para incluir o esquema do Identity chamando builder.ConfigurePersistedGrantContext(_operationalStoreOptions.Value) no método OnModelCreating.

OidcConfigurationController

No arquivo, observe o ponto de extremidade provisionado para atender aos parâmetros OIDC que o cliente precisa usar.

appsettings.json

No arquivo appsettings.json da raiz do projeto, há uma nova seção IdentityServer que descreve a lista de clientes configurados. No exemplo a seguir, há um único cliente. O nome do cliente corresponde ao nome do aplicativo e é mapeado por convenção para o parâmetro ClientId OAuth. O perfil indica o tipo de aplicativo que está sendo configurado. Ele é usado internamente para conduzir convenções que simplificam o processo de configuração do servidor. Há vários perfis disponíveis, conforme explicado na seção Perfis de aplicativo.

"IdentityServer": {
  "Clients": {
    "angularindividualpreview3final": {
      "Profile": "IdentityServerSPA"
    }
  }
}

appsettings.Development.json

No arquivo appsettings.Development.json da raiz do projeto, há uma seção do IdentityServer que descreve a chave usada para assinar tokens. Ao implantar na produção, uma chave precisa ser provisionada e implantada junto com o aplicativo, conforme explicado na seção Implantar na produção.

"IdentityServer": {
  "Key": {
    "Type": "Development"
  }
}

Descrição geral do aplicativo Angular

O suporte à autenticação e à autorização de API no modelo Angular reside em seu próprio módulo Angular no diretório ClientApp/src/api-authorization. O módulo é composto pelos seguintes elementos:

  • Três componentes:
    • login.component.ts: manipula o fluxo de logon do aplicativo.
    • logout.component.ts: manipula o fluxo de logoff do aplicativo.
    • login-menu.component.ts: um widget que exibe um dos seguintes conjuntos de links:
      • Gerenciamento do perfil do usuário e links de logoff quando o usuário é autenticado.
      • Links de registro e logon quando o usuário não é autenticado.
  • Uma proteção de rota AuthorizeGuard que pode ser adicionada às rotas e exige que um usuário seja autenticado antes de visitar a rota.
  • Um interceptador HTTP AuthorizeInterceptor que anexa o token de acesso a solicitações HTTP de saída direcionadas à API quando o usuário é autenticado.
  • Um serviço AuthorizeService que manipula os detalhes de nível inferior do processo de autenticação e expõe informações sobre o usuário autenticado para o restante do aplicativo para consumo.
  • Um módulo Angular que define rotas associadas às partes de autenticação do aplicativo. Ele expõe o componente do menu de logon, o interceptador, a proteção e o serviço para consumo do restante do aplicativo.

Descrição geral do aplicativo React

O suporte para autenticação e autorização de API no modelo React reside no diretório ClientApp/src/components/api-authorization. Ele é composto pelos seguintes elementos:

  • Quatro componentes:
    • Login.js: manipula o fluxo de logon do aplicativo.
    • Logout.js: manipula o fluxo de logoff do aplicativo.
    • LoginMenu.js: um widget que exibe um dos seguintes conjuntos de links:
      • Gerenciamento do perfil do usuário e links de logoff quando o usuário é autenticado.
      • Links de registro e logon quando o usuário não é autenticado.
    • AuthorizeRoute.js: um componente de rota que exige que um usuário seja autenticado antes de renderizar o componente indicado no parâmetro Component.
  • Uma instância authService exportada da classe AuthorizeService que manipula os detalhes de nível inferior do processo de autenticação e expõe informações sobre o usuário autenticado ao restante do aplicativo para consumo.

Agora que você já viu os principais componentes da solução, pode dar uma olhada mais profunda nos cenários individuais do aplicativo.

Exigir autorização em uma nova API

Por padrão, o sistema é configurado para exigir facilmente autorização para novas APIs. Para fazer isso, crie um novo controlador e adicione o atributo [Authorize] à classe do controlador ou a qualquer ação dentro do controlador.

Personalizar o manipulador de autenticação de API

Para personalizar a configuração do manipulador JWT da API, configure sua instância JwtBearerOptions:

services.AddAuthentication()
    .AddIdentityServerJwt();

services.Configure<JwtBearerOptions>(
    IdentityServerJwtConstants.IdentityServerJwtBearerScheme,
    options =>
    {
        ...
    });

O manipulador JWT da API gera eventos que permitem o controle sobre o processo de autenticação usando JwtBearerEvents. Para fornecer suporte para autorização de API, AddIdentityServerJwt registra seus próprios manipuladores de eventos.

Para personalizar a manipulação de um evento, encapsule o manipulador de eventos existente com lógica adicional, conforme necessário. Por exemplo:

services.Configure<JwtBearerOptions>(
    IdentityServerJwtConstants.IdentityServerJwtBearerScheme,
    options =>
    {
        var onTokenValidated = options.Events.OnTokenValidated;       

        options.Events.OnTokenValidated = async context =>
        {
            await onTokenValidated(context);
            ...
        }
    });

No código anterior, o manipulador de eventos OnTokenValidated é substituído por uma implementação personalizada. Essa implementação:

  1. Chama a implementação original fornecida pelo suporte à autorização da API.
  2. Execute sua própria lógica personalizada.

Proteger uma rota do lado do cliente (Angular)

A proteção de uma rota do lado do cliente é feita adicionando a proteção de autorização à lista de proteções a serem executadas ao configurar uma rota. Por exemplo, você pode ver como a rota fetch-data é configurada no módulo Angular do aplicativo principal:

RouterModule.forRoot([
  // ...
  { path: 'fetch-data', component: FetchDataComponent, canActivate: [AuthorizeGuard] },
])

É importante mencionar que a proteção de uma rota não protege o ponto de extremidade real (que ainda requer um atributo [Authorize] aplicado a ele), mas apenas impede que o usuário navegue para a rota do lado do cliente quando ela não estiver autenticada.

Autenticar solicitações de API (Angular)

A autenticação de solicitações para APIs hospedadas junto com o aplicativo é feita automaticamente por meio do uso do interceptador de cliente HTTP definido pelo aplicativo.

Proteger uma rota do lado do cliente (React)

Proteja uma rota do lado do cliente usando o componente AuthorizeRoute em vez do componente Route sem formatação. Por exemplo, observe como a rota fetch-data é configurada no componente App:

<AuthorizeRoute path='/fetch-data' component={FetchData} />

Proteção de uma rota:

  • Não protege o ponto de extremidade real (que ainda requer um atributo [Authorize] aplicado a ele).
  • Só impede que o usuário navegue até a rota fornecida do lado do cliente quando ela não é autenticada.

Autenticar solicitações de API (React)

A autenticação de solicitações com o React é feita importando primeiro a instância authService do AuthorizeService. O token de acesso é recuperado do authService e anexado à solicitação, conforme mostrado abaixo. Nos componentes React, esse trabalho geralmente é feito no método do ciclo de vida componentDidMount ou como resultado de alguma interação do usuário.

Importar o authService para um componente

import authService from './api-authorization/AuthorizeService'

Recuperar e anexar o token de acesso à resposta

async populateWeatherData() {
  const token = await authService.getAccessToken();
  const response = await fetch('api/SampleData/WeatherForecasts', {
    headers: !token ? {} : { 'Authorization': `Bearer ${token}` }
  });
  const data = await response.json();
  this.setState({ forecasts: data, loading: false });
}

Implantar para a produção

Para implantar o aplicativo na produção, os seguintes recursos precisam ser provisionados:

  • Um banco de dados para armazenar as contas de usuário do Identity e as concessões do IdentityServer.
  • Um certificado de produção a ser usado para assinar tokens.
    • Não há requisitos específicos para esse certificado. Ele pode ser um certificado autoassinado ou um certificado provisionado por meio de uma autoridade de AC.
    • Ele pode ser gerado por meio de ferramentas padrão, como o PowerShell ou o OpenSSL.
    • Ele pode ser instalado no repositório de certificados nos computadores de destino ou implantado como um arquivo .pfx com uma senha forte.

Exemplo: Implantar em um provedor de hospedagem Web não Azure

No painel de hospedagem da Web, crie ou carregue seu certificado. Em seguida, no arquivo appsettings.json do aplicativo, modifique a seção IdentityServer para incluir os principais detalhes. Por exemplo:

"IdentityServer": {
  "Key": {
    "Type": "Store",
    "StoreName": "WebHosting",
    "StoreLocation": "CurrentUser",
    "Name": "CN=MyApplication"
  }
}

No exemplo anterior:

  • StoreName representa o nome do repositório de certificados em que o certificado é armazenado. Nesse caso, ele aponta para o repositório de hospedagem da Web.
  • StoreLocation representa onde carregar o certificado (CurrentUser nesse caso).
  • Name corresponde à entidade diferenciada para o certificado.

Exemplo: implantar no Serviço de Aplicativo do Azure

Esta seção descreve a implantação do aplicativo no Serviço de Aplicativo do Azure usando um certificado armazenado no repositório de certificados. Para modificar o aplicativo para carregar um certificado do repositório de certificados, é necessário um plano de serviço de camada Standard ou superior ao configurar o aplicativo no portal do Azure em uma etapa posterior.

No arquivo appsettings.json do aplicativo, modifique a seção IdentityServer para incluir os principais detalhes:

"IdentityServer": {
  "Key": {
    "Type": "Store",
    "StoreName": "My",
    "StoreLocation": "CurrentUser",
    "Name": "CN=MyApplication"
  }
}
  • O nome do repositório representa o nome do repositório de certificados em que o certificado está armazenado. Nesse caso, ele aponta para o repositório de usuários pessoais.
  • O local do repositório representa onde carregar o certificado (CurrentUser ou LocalMachine).
  • A propriedade de nome no certificado corresponde ao assunto distinto do certificado.

Para implantar no Serviço de Aplicativo do Azure, siga as etapas em Implantar o aplicativo no Azure, que explica como criar os recursos necessários do Azure e implantar o aplicativo na produção.

Depois de seguir as instruções anteriores, o aplicativo é implantado no Azure, mas ainda não está funcional. O certificado usado pelo aplicativo deve ser configurado no portal do Azure. Localize a impressão digital do certificado e siga as etapas descritas em Carregar certificados.

Embora essas etapas mencionem o SSL, há uma seção de Certificados privados no portal do Azure, onde você pode carregar o certificado provisionado para usar com o aplicativo.

Depois de definir o aplicativo e as configurações do aplicativo no portal do Azure, reinicie o aplicativo no portal.

Outras opções de configuração

O suporte para autorização de API se baseia no IdentityServer com um conjunto de convenções, valores padrão e aprimoramentos para simplificar a experiência para SPAs. Todo o poder do IdentityServer está disponível nos bastidores se as integrações do ASP.NET Core não abrangerem seu cenário. O suporte do ASP.NET Core é focado em aplicativos "originais", nos quais todos os aplicativos são criados e implantados por nossa organização. Dessa forma, o suporte não é oferecido para coisas como consentimento ou federação. Para esses cenários, use o IdentityServer e siga sua documentação.

Perfis de aplicativo

Os perfis de aplicativo são configurações predefinidas para aplicativos que definem melhor seus parâmetros. Neste momento, há suporte para os seguintes perfis:

  • IdentityServerSPA: representa um SPA hospedado ao lado do IdentityServer como uma única unidade.
    • O /authentication/login-callback usa como padrão redirect_uri.
    • O /authentication/logout-callback usa como padrão post_logout_redirect_uri.
    • O conjunto de escopos inclui o openid, profile e todos os escopos definidos para as APIs no aplicativo.
    • O conjunto de tipos de resposta OIDC permitidos é id_token token ou cada um deles individualmente (id_token, token).
    • O modo de resposta permitido é fragment.
  • SPA: representa um SPA que não está hospedado com IdentityServer.
    • O conjunto de escopos inclui o openid, profile e todos os escopos definidos para as APIs no aplicativo.
    • O conjunto de tipos de resposta OIDC permitidos é id_token token ou cada um deles individualmente (id_token, token).
    • O modo de resposta permitido é fragment.
  • IdentityServerJwt: representa uma API hospedada junto com o IdentityServer.
    • O aplicativo está configurado para ter um único escopo que usa como padrão o nome do aplicativo.
  • API: representa uma API que não está hospedada com o IdentityServer.
    • O aplicativo está configurado para ter um único escopo que usa como padrão o nome do aplicativo.

Configuração por meio de AppSettings

Configure os aplicativos por meio do sistema de configuração adicionando-os à lista de Clients ou Resources.

Configure a propriedade redirect_uri e post_logout_redirect_uri de cada cliente, conforme mostrado no exemplo a seguir:

"IdentityServer": {
  "Clients": {
    "MySPA": {
      "Profile": "SPA",
      "RedirectUri": "https://www.example.com/authentication/login-callback",
      "LogoutUri": "https://www.example.com/authentication/logout-callback"
    }
  }
}

Ao configurar recursos, você pode configurar os escopos do recurso, conforme mostrado abaixo:

"IdentityServer": {
  "Resources": {
    "MyExternalApi": {
      "Profile": "API",
      "Scopes": "a b c"
    }
  }
}

Configuração por meio de código

Você também pode configurar os clientes e recursos por meio do código usando uma sobrecarga de AddApiAuthorization que executa uma ação para configurar opções.

AddApiAuthorization<ApplicationUser, ApplicationDbContext>(options =>
{
    options.Clients.AddSPA(
        "My SPA", spa =>
        spa.WithRedirectUri("http://www.example.com/authentication/login-callback")
           .WithLogoutRedirectUri(
               "http://www.example.com/authentication/logout-callback"));

    options.ApiResources.AddApiResource("MyExternalApi", resource =>
        resource.WithScopes("a", "b", "c"));
});

Recursos adicionais