Na Parte 4 eu mostrei como configurar o IdentityServer4 utilizando o EntityFramework e SQL Server como banco de dados, porém até agora não vimos nenhum usuário informando o login e senha para se logar em um sistema baseado em MVC (Model View Controller) por exemplo, para isso vamos seguir um caminho simples e você pode evoluir tudo isso depois, pois o foco não está na elaboração da interface, mas como fazer um login e senha funcionar de forma segura e independente.
Identity
O Identity nada mais é do que uma API que possui uma interface e permite o usuário realizar o processo de login, além disso o Identity gerencia usuários, senhas, perfis, roles, claims (vamos ver isso mais adiante), tokens (olha aqui, já falamos sobre ele em posts anteriores), e-mail, confirmação de cadastro de usuários e outras funções. Cada uma delas permite o suporte ao OIDC (Open ID Connect). Caso você queira mais informações, eu sugiro dar uma olhada na documentação da Microsoft.
Vou utilizar o Identity para facilitar a realização do login do usuário.
Scaffold
Nada adianta ter o Identity sem as telas e para facilitar nossa vida, vamos utilizar o Scaffold para criar a estrutura do Identity dentro do nosso projeto, seguindo as instruções a seguir.
Você vai encontrar maiores informações sobre o Scaffold aqui.
Ajustes de código
- Se você seguiu os passos anteriores, procure pelo arquivo IdentityHostingStartup.cs, ele geralmente fica em Areas > Identity, você pode apagá-lo ou comentar todo o código dele.
- Localize o arquivo ApplicationDbContext.cs e mova-o para a raiz do projeto na pasta Data, vai ficar mais organizado neste momento por conta da namespace criada.
- Dentro da pasta Models na raiz do projeto, crie o arquivo ApplicationUser.cs, ele vai gerar a classe com o mesmo nome, e você apenas acrescente a herança da classe IdentityUser.
Statups.cs
Vou fazer algumas considerações sobre este arquivo:
Temos 2 métodos que configuram o Identity: AddIdentity e AddDefaultIdentity:
- AddDefaultIdentity: Este método já inicializa alguns serviços automaticamente, como o DefaultUI e o DefaultTokenProviders.
- AddIdentity: Com este método você pode adicionar os serviços necessários conforme sua necessidade.
O restante das configurações você pode conferir no arquivo disponível conforme gist a seguir.
using IdentityProvider.Data;
using IdentityProvider.Models;
using IdentityProvider.Seeds;
using IdentityServer4.Configuration;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using System;
using System.Reflection;
using System.Security.Cryptography.X509Certificates;
namespace IdentityProvider
{
public class Startup
{
private readonly IConfiguration _configuration;
private readonly IWebHostEnvironment _webHostEnvironment;
public Startup(IConfiguration configuration, IWebHostEnvironment webHostEnvironment)
{
_configuration = configuration;
_webHostEnvironment = webHostEnvironment;
}
public void ConfigureServices(IServiceCollection services)
{
services.AddControllersWithViews();
var identityServer = services.AddIdentityServer(options =>
{
options.Events.RaiseErrorEvents = true;
options.Events.RaiseInformationEvents = true;
options.Events.RaiseFailureEvents = true;
options.Events.RaiseSuccessEvents = true;
});
#region Thumbprint
if (_webHostEnvironment.IsDevelopment())
identityServer.AddDeveloperSigningCredential();
else
identityServer.AddSigningCredential("4DFF9B8EBB5314B9A62EFA72DA8B4D7658231C05", StoreLocation.CurrentUser, NameType.Thumbprint);
#endregion
#region Uso do *.pfx direto
//if (_webHostEnvironment.IsDevelopment())
// identityServer.AddDeveloperSigningCredential();
//else
// identityServer.AddSigningCredential(new X509Certificate2("certificate.pfx", "987654321"));
#endregion
#region Forma de uso maqueado
//if (_webHostEnvironment.IsDevelopment())
// identityServer.AddDeveloperSigningCredential();
//else
// identityServer.AddCustomSigningCredential();
#endregion
#region Identity configuration
services.AddDbContext<ApplicationDbContext>(options => options.UseSqlServer(_configuration.GetConnectionString("DefaultConnection")));
services.AddIdentity<ApplicationUser, IdentityRole>(config =>
{
config.SignIn.RequireConfirmedEmail = true;
})
//services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = true)
.AddRoles<IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultUI()
.AddDefaultTokenProviders();
// esta configuração é para o token gerado e enviado para o usuário para recuperar a senha e para confirmar o e-mail do seu cadastro
// o token é enviado por e-mail para os dois casos
services.Configure<DataProtectionTokenProviderOptions>(o => o.TokenLifespan = TimeSpan.FromHours(1));
#endregion
#region IdentityServer configuration
var connectionString = _configuration.GetConnectionString("DefaultConnection");
var migrationsAssembly = typeof(Startup).GetTypeInfo().Assembly.GetName().Name;
identityServer.AddConfigurationStore(options =>
{
options.ConfigureDbContext = builder => builder.UseSqlServer(connectionString,
sqlServerOptionsAction: sqlOptions =>
{
sqlOptions.MigrationsAssembly(migrationsAssembly);
sqlOptions.EnableRetryOnFailure(maxRetryCount: 15, maxRetryDelay: TimeSpan.FromSeconds(30), errorNumbersToAdd: null);
});
})
.AddOperationalStore(options =>
{
options.ConfigureDbContext = builder => builder.UseSqlServer(connectionString,
sqlServerOptionsAction: sqlOptions =>
{
sqlOptions.MigrationsAssembly(migrationsAssembly);
sqlOptions.EnableRetryOnFailure(maxRetryCount: 15, maxRetryDelay: TimeSpan.FromSeconds(30), errorNumbersToAdd: null);
});
})
.AddAspNetIdentity<ApplicationUser>();
services.AddAuthentication();
#endregion
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.RunMigration();
app.SeeClient();
app.SeedApiResource();
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Home/Error");
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
app.UseHttpsRedirection();
}
app.UseStaticFiles();
app.UseRouting();
//app.UseAuthentication(); // UseAuthentication not needed -- UseIdentityServer add this
app.UseIdentityServer();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapDefaultControllerRoute();
endpoints.MapRazorPages();
});
}
}
}
Templates
Caso você ache mais prático seguir um template, o IdentityServer possui os seus, um deles já está integrado com o Identity e facilita o entendimento. Por uma questão de aprendizagem, eu vou mostrar o passo a passo.
Para instalar novos templates, eu sugiro você ver o post ASP.NET Core — Como instalar templates — IdentityServer4 e aí utilizar o comando a seguir para criar o projeto a partir do template que está integrado com o Identity.
dotnet new is4aspid -n IdentityServerAspNetIdentity
Migrations
Precisamos das famosas migrations para criar as tabelas necessárias do Identity, para isso basta executar os comandos a seguir dentro do visual studio através do Package Manager Console, você vai precisar que os pacotes abaixo estejam instalados:
Microsoft.EntityFramework Microsoft.EntityFramework.Tools
Add-Migration Initial_Identity -Context ApplicationDbContext
Update-Database -Context ApplicationDbContext
Caso você queira que a própria execução da aplicação crie as tabelas após criar as migrations basta acrescentar a linha a seguir lá no método de extensão “RunMigration”.
serviceScope.ServiceProvider.GetRequiredService().Database.Migrate();
Banco de dados
Até agora eu só configurei uma string de conexão, ou seja, o IdentityServer e o Identity tem suas tabelas criadas em uma única database, isso não vai criar nenhum impedimento no funcionamento, porém caso você queira separar, também pode ser uma boa prática.
Microsserviço (STS + Users)
Estamos chegando a um ponto onde podemos começar a falar sobre microsserviços e o Identity é encarado como um. Tudo bem, você pode se perguntar “o microsserviço não deveria fazer uma única coisa?”, a resposta é “sim, DEVE”. Mas tudo na vida tem um porém, e no caso do Identity ele não faz somente uma coisa, ele faz várias, porém não teria nenhum sentido se todas as suas funcionalidades fossem independentes, demandaria muito mais tempo para ser desenvolvido, sem contar todo o processo de implantação e gestão de algo deste porte. Mas a parte boa é que ele possui o seu próprio banco de dados que poderia ainda ser separado em até três, dois para o IdentityServer e um para o Identity, o banco de dados do IdentityServer faria toda o controle voltado para Security Token Service (STS) e o Identity para suas funcionalidades.
Esta aplicação que chamei de “Identity Provider” também pode ser configurada para ter alta escalabilidade e/ou disponibilidade.
Execução
Execute o projeto, agora além de poder solicitar um token para acessar a API payment você também tem uma interface web padrão, ela ainda não possui estilos nem nada, pois farei este ajuste na próxima parte.
Caso você tenha dúvidas, eu disponibilizei o código no GitHub em um branch chamado identityserver-identity.
Continua em ASP.NET Core 3.1 — IdentityServer4 — Identity-UI (Parte 6).