.NET

22 jan, 2020

Implementando OAuth JSON Web Token com OWIN no ASP.NET Web API

Publicidade

Hoje venho neste post fazer atualização de uns dos posts mais lido do meu blog. O post no qual me refiro é este aqui Implementando OAuth JSON Web Token com OWIN no ASP.NET Web API.

Por isso, resolvi atualizá-lo já que ele foi feito em .NET Framework porém não vai ser uma simples atualização, vamos melhorar um pouco, além de usar o .NET Core, vamos também utilizar o Identity Server v4 integrado com ASP.NET Identity para nosso gestor de identidade.

Meu muito obrigado a todos que acompanham meu blog e que leem meus artigos.

O que é JSON Web Token (JWT) ?

Json Web Token é um Token de segurança que atua como container de informações sobre um determinado usuário. Ele pode ser transmitido facilmente entre o Servidor de Autorização (Token Issuer) e o Servidor de Recursos (Web Api).

Basicamente um JSON Web Token é uma cadeia de strings que é formado por 3 partes separadas por ponto (.) : Header, Payload, Signature

A parte do Header é formado por um objeto JSON com duas propriedades são elas:

  • tip: sempre tem o valor JWT
  • alg: determina qual o algoritmo que foi usado para assinar o Token

A parte do Payload é um objeto JSON que contém informações do usuário e quais são seus perfis, veja o exemplo abaixo

{
  "unique_name": "rcruz",
  "sub": "rcruz",
  "role": [
    "Administrator"
  ],
  "iss": "http://mytokenissuer.com.br",
  "aud": "379ee8430c2d421380a713458c23ef74",
  "exp": 14142345602,
  "nbf": 1434241802
}

Nem todas as propriedades são obrigatórias porém no nosso exemplo vamos utilizar as propriedades acima, veja o que cada uma representa:

  • sub (subject): Representa o nome de usuário para qual o Token foi expedido
  • role : Representa em quais perfis o usuário se encontra
  • iss (Token Issuer): Representa o servidor de autenticação que gerou o Token
  • aud (Audience): Representa o servidor de destino no qual este Token será usado
  • exp (Expiration): Representa o tempo de expiração do Token em formato UNIX
  • nbf (Not Before): Representa o tempo em formato UNIX que este Token não deve ser usado

A última parte do Token é uma junção do Header e do Payload em base 64 utilizando o algoritmo de criptografia definido no Header para gerar a assinatura do
Token no nosso caso vamos utilizar o HMAC-SHA256. 

O Fluxo de Autenticação do JWT

No fluxo abaixo detalha como é a criação e autenticação do JWT. Entendendo esse fluxo torna-se muito mais fácil utilizá-lo em nossas aplicações

JWT FLow

Utilizando o Identity Server v4 como Gestor de Identidade

Para começar vamos criar o projeto do gestor de identidade utilizando o Identity Server v4. Crie o projeto conforme imagens abaixo:

Com o nosso projeto criado, vamos adicionar os pacotes necessários para fazer nosso gestor de identidade.  Adicione os pacotes abaixo no projeto:

  • IdentityServer4
  • IdentityServer4.AspNetIdentity
  • IdentityServer4.EntityFramework
  • Microsoft.AspNetCore.Identity
  • Microsoft.AspNetCore.Identity.EntityFrameworkCore
  • Microsoft.EntityFrameworkCore
  • Microsoft.EntityFrameworkCore.SqlServer

Estamos neste ponto preparados para configurar o Identity Server v4

Configurando o ASP.NET Identity com o Entity Framework

Neste passo vamos configurar o ASP.NET Identity integrado com o Entity Framework, o ASP.NET Identity será responsável pela gerência de usuário de sua aplicação.

Vamos criar uma nova classe chamada ApplicationUser e coloca-lá na raiz do nosso projeto.

A classe ApplicationUser deve ter o código conforme exemplo abaixo:

public class ApplicationUser : IdentityUser
{

}

Com a classe ApplicationUser criada, vamos criar a classe que será responsável por gerir o banco do ASP.NET Identity. Crie uma classe chamada ApplicationDbContext e coloque na raiz do projeto.

A classe ApplicationDbContext deve ter o código conforme exemplo abaixo:

public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
     public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
          : base(options)
     {
     }
     protected override void OnModelCreating(ModelBuilder builder)
     {
         base.OnModelCreating(builder);
     }
 }

Agora que as classes estão criadas, vamos configura-las. Abra o Startup.cs e coloque o código conforme exemplo abaixo:

public void ConfigureServices(IServiceCollection services)
{
    services.AddDbContext<ApplicationDbContext>(options =>
                                                options.UseSqlServer(Configuration.GetConnectionString("IdentityConnection")));
    services.AddIdentity<ApplicationUser, IdentityRole>()
            .AddEntityFrameworkStores<ApplicationDbContext>()
            .AddDefaultTokenProviders();


}

Agora nossa aplicação está preparada para utilizar o ASP.NET Identity com o Entity Framework. Se você reparou estamos utilizando uma conexão com o banco de dados SQL SERVER e precisamos configurar o appsettings.json adicionando a chave de conexão com o banco de dados.

Abra o appsettings.json e adicione a seguinte estrutura conforme exemplo abaixo:

{
  "ConnectionStrings": {
    "IdentityConnection": "<SUA CHAVE DE CONEXÃO COM O BANCO DE DADOS">
  },
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft": "Warning",
      "Microsoft.Hosting.Lifetime": "Information"
    }
  },
  "AllowedHosts": "*"
}

Integrando o Identity Server v4 com o ASP.NET Identity

Com a integração do ASP.NET Identity concluída devemos integrar o Identity Server v4 no projeto. Abra o Startup.cs e adicione as configurações necessárias para o Identity Server v4 trabalhar junto com o ASP.NET Identity.

Adicione as linhas conforme código abaixo:

public void ConfigureServices(IServiceCollection services)
{
            services.AddDbContext<ApplicationDbContext>(options =>
                                                        options.UseSqlServer(Configuration.GetConnectionString("IdentityConnection")));

            services.AddIdentity<ApplicationUser, IdentityRole>()
                    .AddEntityFrameworkStores<ApplicationDbContext>()
                    .AddDefaultTokenProviders();

            var migrationsAssembly = typeof(Startup).GetTypeInfo().Assembly.GetName().Name;
            services.AddIdentityServer()

                    .AddDeveloperSigningCredential()
                    .AddAspNetIdentity<ApplicationUser>()
                    .AddConfigurationStore(options => { options.ConfigureDbContext = builder => builder.UseSqlServer(Configuration.GetConnectionString("IdentityConnection"), db => db.MigrationsAssembly(migrationsAssembly)); })
                    .AddOperationalStore(options => { options.ConfigureDbContext = builder => builder.UseSqlServer(Configuration.GetConnectionString("IdentityConnection"), db => db.MigrationsAssembly(migrationsAssembly)); });
}

Por fim devemos agora ativar o serviço, ative o serviço do Identity Server v4 conforme código abaixo:

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
      if (env.IsDevelopment())
      {
          app.UseDeveloperExceptionPage();
      }
      app.UseRouting();
      app.UseIdentityServer();
      app.UseEndpoints(endpoints =>
      {
         endpoints.MapGet("/", async context =>
         {
              await context.Response.WriteAsync("Hello World!");
          });
      });
}

Configurando o Identity Server v4

Com a integração concluída devemos configurar os Clients, API Resources, Identity Resouce que o Identity Server v4 irá utilizar.

Vamos criar uma nova classe chamada IdentityServerConfiguration, nela estará contida toda a configuração necessária do Identity Server.

Crie a classe na raiz do projeto e insira o código abaixo:

public class IdentityServerConfiguration
{
       public static IEnumerable<IdentityResource> GetIdentityResources()
       {
           return new List<IdentityResource>()
           {
               new IdentityResources.OpenId(),
               new IdentityResources.Profile()
           };
       }
       public static IEnumerable<ApiResource> GetApiResources()
       {
           return new List<ApiResource>()
           {
               new ApiResource("API", "Api Resources")
           };
       }
       public static IEnumerable<Client> GetClientScope()
       {
           return new List<Client>()
           {
               new Client()
               {
                   ClientId = "79E0C2E2-D750-45BC-8FA3-1A9D5F9F82B5",
                   ClientName = "Web API Acess Token",
                   AllowedGrantTypes = GrantTypes.ResourceOwnerPasswordAndClientCredentials,
                   AllowOfflineAccess = true,
                   ClientSecrets =
                   {
                       new Secret("1234567890".Sha256())
                   },
                   AllowedScopes =
                   {
                       IdentityServerConstants.StandardScopes.OpenId,
                       IdentityServerConstants.StandardScopes.Profile,
                       "API"
                   },
               }
           };
       }
}

Essa classe será usada para configuramos o Identity Server v4 no banco de dados. Vamos criar os Migrations necessários para criar a base de dados.

Criando os Migrations necessários do ASP.NET Identity & Identity Server v4

Estamos com tudo pronto para utilizar o Identity Server v4 integrado com o ASP.NET Identity, porémainda falta um detalhe, criar a base de dados e para isso usaremos os Migrations do EntityFrameworkCore nesta tarefa.

Iremos utilizar literalmente 3 contextos para criar nosso banco, o ApplicationDbContext que é do ASP.NET Identity, o ConfigurationDbContext e o PersistedGrantDbContext que são do Identity Server v4. Com eles temos todas a estrutura de banco de dados necessária para o Identity Server v4

No prompt de comando digite:

  • dotnet ef migrations add InitApplicationDbContext -c ApplicationDbContext -o Data/Migrations/Application
  • dotnet ef migrations add InitConfigurationDbContext -c ConfigurationDbContext -o Data/Migrations/Configuration
  • dotnet ef migrations add InitPersistedGrantDbContext -c PersistedGrantDbContext -o Data/Migrations/PersistedGrant

Veja a imagem para um exemplo melhor:

Aplique a migrações utilizando o comando “dotnet ef database update” para cada contexto e sua base de dados deverá está semelhante a imagem abaixo:

Configurando o Seed do Identity Server v4

Precisamos criar uma classe que será responsável pela a carga em nossas tabelas. Ela irá utilizar a classe IdentityServerConfiguration para as inserções necessárias para o Identity Server v4 funcionar corretamente.

Cria uma pasta chamada Seed e dentro desta pasta crie uma classe com o nome SeedData. 

Coloque o código abaixo dentro da classe:

public class SeedData
    {
        public static void EnsureSeedData(IServiceProvider serviceProvider)
        {
            PerformMigrations(serviceProvider);
            EnsureSeedData(serviceProvider.GetRequiredService<ConfigurationDbContext>());
        }
        private static void PerformMigrations(IServiceProvider serviceProvider)
        {
            serviceProvider.GetRequiredService<ApplicationDbContext>().Database.Migrate();
            serviceProvider.GetRequiredService<ConfigurationDbContext>().Database.Migrate();
            serviceProvider.GetRequiredService<PersistedGrantDbContext>().Database.Migrate();
        }
        private static void EnsureSeedData(ConfigurationDbContext context)
        {
            if (!context.Clients.Any())
            {
                foreach (var client in IdentityServerConfiguration.GetClientScope().ToList())
                {
                    context.Clients.Add(client.ToEntity());
                }
                context.SaveChanges();
            }
           
            if (!context.IdentityResources.Any())
            {
                foreach (var resource in IdentityServerConfiguration.GetIdentityResources().ToList())
                {
                    context.IdentityResources.Add(resource.ToEntity());
                }
                context.SaveChanges();
            }
           
            if (!context.ApiResources.Any())
            {
                foreach (var resource in IdentityServerConfiguration.GetApiResources().ToList())
                {
                    context.ApiResources.Add(resource.ToEntity());
                }
                context.SaveChanges();
            }
           
        }
    }

Feito isso, ainda temos mais um passo, vamos alterar o Program.cs para garantir que os dados estarão na base de dados.

Abra o Program.cs e adicione as linhas de código conforme exemplo abaixo:

static void Main(string[] args)
 {
     var host = CreateHostBuilder(args).Build();
      using (var scope = host.Services.CreateScope())
      {
           var services = scope.ServiceProvider;
           try
           {
               SeedData.EnsureSeedData(services);
           }
           catch (Exception ex)
           {
                var logger = services.GetRequiredService<ILogger<Program>>();
                logger.LogError(ex.ToString());
           }
       }
       host.Run();
 }

Rodando o Identity Server v4

Com tudo configurado, vamos executar o Identity Server v4, se tudo ocorreu bem, você irá conseguir fazer uma requisição e ver resultado da resposta do Identity Server v4 conforme imagem abaixo:

E se chamarmos a API de Token devemos ter uma resposta conforme imagem abaixo:

Nosso Identity Server v4 está pronto.

Criando um Client Web API e utilizando o Identity Server como Gerador de Token

Agora vamos criar uma aplicação cliente por exemplo um Web API que precisará ter uma API segura. Para isso crie um novo projeto  ASP.NET Core Web Application. No template padrão do ASP.NET Core Web Application é criada uma API chamada  WeatherForecastController. 

Vamos configurar nosso cliente para utilizar o Identity Server v4 como Autenticador/Autorizador para isso abra o arquivo Startup.cs e adicione o código abaixo:

public class Startup
    {
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }
        public IConfiguration Configuration { get; }
        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddControllers();
            services.AddAuthentication("Bearer")
                .AddJwtBearer("Bearer", o =>
                {
                    o.Authority = "https://localhost:44387";
                    o.RequireHttpsMetadata = true;
                    o.Audience = "API";
                });
        }
        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            app.UseHttpsRedirection();
            app.UseRouting();
            app.UseAuthentication();
            app.UseAuthorization();
            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllers();
            });
        }
    }

Adicione o pacote Microsoft.AspNetCore.Authentication.JwtBearer para que possamos utilizar JWT Bearer Token

Altere o código adicionando o atributo “Authorize” no controller WeatherForecastController e ao fazer uma requisição deverá retornar um 401 – Unauthorized conforme imagem abaixo:

Nossa API está segura, agora para podermos utiliza-lá devemos criar um token gerado pelo Identity Server v4 e passar no Header de Autenticação da nossa API.

Veja nas imagens abaixo:

Conclusão

JSON Web Token (JWT) facilita e padroniza a forma de separar o servidor de autorização e o servidor de recursos. O JWT é amplamente utilizado para garantir a segurança de uma API REST pois todo o token é assinado digitalmente.

Chegamos ao final deste post no qual é uma atualização deste post Implementando OAuth JSON Web Token com OWIN no ASP.NET Web API que fiz sobre JWT. Resolvi atualizar abordando tecnologias recentes como o ASP.NET Identity CoreEntityFramework Core e o Identity Server v4. 

Espero que gostem desta atualização também deixo meu muito obrigado a vocês que leem meus artigos, espero que eles estejam ajudando vocês =]

O código fonte deste artigo está no meu GitHub através deste link

Abs e até o próximo