Migrar a autenticação e a Identity para o ASP.NET Core 2.0
Por Scott Addie e Hao Kung
O ASP.NET Core 2.0 tem um novo modelo para autenticação e Identity isso simplifica a configuração usando serviços. Os aplicativos de ASP.NET Core 1.x que usam autenticação ou Identity podem ser atualizados para usar o novo modelo, conforme descrito abaixo.
Atualizar namespaces
No 1.x, classes como IdentityRole
e IdentityUser
foram encontradas no namespace Microsoft.AspNetCore.Identity.EntityFrameworkCore
.
Na versão 2.0, o namespace Microsoft.AspNetCore.Identity tornou-se a nova home de várias dessas classes. Com o código padrão Identity, as classes afetadas incluem ApplicationUser
e Startup
. Ajuste suas instruções using
para resolver as referências afetadas.
Middleware e serviços de autenticação
Em projetos do 1.x, a autenticação é configurada por meio do middleware. Um método middleware é invocado para cada esquema de autenticação que você deseja dar suporte.
O exemplo do 1.x a seguir configura a autenticação do Facebook com Identity em Startup.cs
:
public void ConfigureServices(IServiceCollection services)
{
services.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>();
}
public void Configure(IApplicationBuilder app, ILoggerFactory loggerfactory)
{
app.UseIdentity();
app.UseFacebookAuthentication(new FacebookOptions {
AppId = Configuration["auth:facebook:appid"],
AppSecret = Configuration["auth:facebook:appsecret"]
});
}
Em projetos do 2.0, a autenticação é configurada por meio de serviços. Cada esquema de autenticação é registrado no método ConfigureServices
de Startup.cs
. O método UseIdentity
é substituído por UseAuthentication
.
O exemplo do 2.0.x a seguir configura a autenticação do Facebook com Identity em Startup.cs
:
public void ConfigureServices(IServiceCollection services)
{
services.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>();
// If you want to tweak Identity cookies, they're no longer part of IdentityOptions.
services.ConfigureApplicationCookie(options => options.LoginPath = "/Account/LogIn");
services.AddAuthentication()
.AddFacebook(options =>
{
options.AppId = Configuration["auth:facebook:appid"];
options.AppSecret = Configuration["auth:facebook:appsecret"];
});
}
public void Configure(IApplicationBuilder app, ILoggerFactory loggerfactory) {
app.UseAuthentication();
}
O método UseAuthentication
adiciona um único componente de middleware de autenticação, que é responsável pela autenticação automática e pelo tratamento de solicitações de autenticação remota. Ele substitui todos os componentes de middleware individuais por um único componente de middleware comum.
As instruções de migração do 2.0 para cada esquema de autenticação principal estão descritas abaixo.
Autenticação baseada em Cookie
Selecione uma das duas opções abaixo e faça as alterações necessárias em Startup.cs
:
Use cookies com Identity
Substitua
UseIdentity
porUseAuthentication
no métodoConfigure
:app.UseAuthentication();
Invoque o método
AddIdentity
no métodoConfigureServices
para adicionar os serviços de autenticação cookie.Opcionalmente, invoque o método
ConfigureApplicationCookie
ouConfigureExternalCookie
no métodoConfigureServices
para ajustar as configurações do Identitycookie.services.AddIdentity<ApplicationUser, IdentityRole>() .AddEntityFrameworkStores<ApplicationDbContext>() .AddDefaultTokenProviders(); services.ConfigureApplicationCookie(options => options.LoginPath = "/Account/LogIn");
Use cookies sem Identity
Substitua a chamada de método
UseCookieAuthentication
no métodoConfigure
porUseAuthentication
:app.UseAuthentication();
Invoque os métodos
AddAuthentication
eAddCookie
no métodoConfigureServices
:// If you don't want the cookie to be automatically authenticated and assigned to HttpContext.User, // remove the CookieAuthenticationDefaults.AuthenticationScheme parameter passed to AddAuthentication. services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme) .AddCookie(options => { options.LoginPath = "/Account/LogIn"; options.LogoutPath = "/Account/LogOff"; });
Autenticação do Portador JWT
Faça as seguintes alterações em Startup.cs
:
Substitua a chamada de método
UseJwtBearerAuthentication
no métodoConfigure
porUseAuthentication
:app.UseAuthentication();
Invoque o método
AddJwtBearer
no métodoConfigureServices
:services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) .AddJwtBearer(options => { options.Audience = "http://localhost:5001/"; options.Authority = "http://localhost:5000/"; });
Esse snippet de código não usa Identity, portanto, o esquema padrão deve ser definido passando
JwtBearerDefaults.AuthenticationScheme
para o métodoAddAuthentication
.
Falha na autenticação OpenID Connect (OIDC)
Faça as seguintes alterações em Startup.cs
:
Substitua a chamada de método
UseOpenIdConnectAuthentication
no métodoConfigure
porUseAuthentication
:app.UseAuthentication();
Invoque o método
AddOpenIdConnect
no métodoConfigureServices
:services.AddAuthentication(options => { options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme; options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme; }) .AddCookie() .AddOpenIdConnect(options => { options.Authority = Configuration["auth:oidc:authority"]; options.ClientId = Configuration["auth:oidc:clientid"]; });
Substitua a propriedade
PostLogoutRedirectUri
na açãoOpenIdConnectOptions
porSignedOutRedirectUri
:.AddOpenIdConnect(options => { options.SignedOutRedirectUri = "https://contoso.com"; });
Autenticação do Facebook
Faça as seguintes alterações em Startup.cs
:
Substitua a chamada de método
UseFacebookAuthentication
no métodoConfigure
porUseAuthentication
:app.UseAuthentication();
Invoque o método
AddFacebook
no métodoConfigureServices
:services.AddAuthentication() .AddFacebook(options => { options.AppId = Configuration["auth:facebook:appid"]; options.AppSecret = Configuration["auth:facebook:appsecret"]; });
Autenticação do Google
Faça as seguintes alterações em Startup.cs
:
Substitua a chamada de método
UseGoogleAuthentication
no métodoConfigure
porUseAuthentication
:app.UseAuthentication();
Invoque o método
AddGoogle
no métodoConfigureServices
:services.AddAuthentication() .AddGoogle(options => { options.ClientId = Configuration["auth:google:clientid"]; options.ClientSecret = Configuration["auth:google:clientsecret"]; });
Autenticação de Conta da Microsoft
Para obter mais informações sobre a autenticação da conta Microsoft, consulte esse problema do GitHub.
Faça as seguintes alterações em Startup.cs
:
Substitua a chamada de método
UseMicrosoftAccountAuthentication
no métodoConfigure
porUseAuthentication
:app.UseAuthentication();
Invoque o método
AddMicrosoftAccount
no métodoConfigureServices
:services.AddAuthentication() .AddMicrosoftAccount(options => { options.ClientId = Configuration["auth:microsoft:clientid"]; options.ClientSecret = Configuration["auth:microsoft:clientsecret"]; });
Autenticação do Twitter
Faça as seguintes alterações em Startup.cs
:
Substitua a chamada de método
UseTwitterAuthentication
no métodoConfigure
porUseAuthentication
:app.UseAuthentication();
Invoque o método
AddTwitter
no métodoConfigureServices
:services.AddAuthentication() .AddTwitter(options => { options.ConsumerKey = Configuration["auth:twitter:consumerkey"]; options.ConsumerSecret = Configuration["auth:twitter:consumersecret"]; });
Como configurar esquemas de autenticação padrão
No 1.x, as propriedades AutomaticAuthenticate
e AutomaticChallenge
da classe base AuthenticationOptions foram destinadas a ser definidas em um único esquema de autenticação. Não havia uma maneira boa de impor isso.
Na versão 2.0, essas duas propriedades foram removidas como propriedades na instância individual AuthenticationOptions
. Elas podem ser configuradas na chamada de método AddAuthentication
dentro do método ConfigureServices
de Startup.cs
:
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme);
No snippet de código anterior, o esquema padrão é definido como CookieAuthenticationDefaults.AuthenticationScheme
("Cookies").
Como alternativa, use uma versão sobrecarregada do método AddAuthentication
para definir mais de uma propriedade. No exemplo de método sobrecarregado a seguir, o esquema padrão é definido como CookieAuthenticationDefaults.AuthenticationScheme
. Como alternativa, o esquema de autenticação pode ser especificado em seus atributos individuais ou políticas de autorização do [Authorize]
.
services.AddAuthentication(options =>
{
options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
});
Defina um esquema padrão em 2.0 se uma das seguintes condições for verdadeira:
- Você deseja que o usuário seja conectado automaticamente
- Você usa o atributo ou as políticas de autorização do
[Authorize]
sem especificar esquemas
A exceção a essa regra é o método AddIdentity
. Esse método adiciona cookies para você e define os esquemas de autenticação e de desafio padrão para o aplicativo cookieIdentityConstants.ApplicationScheme
. Além disso, ele define o esquema de entrada padrão para o cookieIdentityConstants.ExternalScheme
externo.
Usar extensões de autenticação HttpContext
A interface IAuthenticationManager
é o principal ponto de entrada no sistema de autenticação 1.x. Ele foi substituído por um novo conjunto de métodos de extensão HttpContext
no namespace Microsoft.AspNetCore.Authentication
.
Por exemplo, projetos 1.x fazem referência a uma propriedadeAuthentication
:
// Clear the existing external cookie to ensure a clean login process
await HttpContext.Authentication.SignOutAsync(_externalCookieScheme);
Em projetos 2.0, importe o namespace Microsoft.AspNetCore.Authentication
e exclua as referências de propriedade Authentication
:
// Clear the existing external cookie to ensure a clean login process
await HttpContext.SignOutAsync(IdentityConstants.ExternalScheme);
Autenticação do Windows (HTTP.sys/IISIntegration)
Há duas variações de autenticação do Windows:
O host só permite usuários autenticados. Essa variação não é afetada pelas alterações 2.0.
O host permite usuários anônimos e autenticados. Essa variação é afetada pelas alterações 2.0. Por exemplo, o aplicativo deve permitir usuários anônimos na camada IIS ou HTTP.sys, mas autorizar usuários no nível do controlador. Nesse cenário, defina o esquema padrão no método
Startup.ConfigureServices
.Para Microsoft.AspNetCore.Server.IISIntegration, defina o esquema padrão como
IISDefaults.AuthenticationScheme
:using Microsoft.AspNetCore.Server.IISIntegration; services.AddAuthentication(IISDefaults.AuthenticationScheme);
Para Microsoft.AspNetCore.Server.IISIntegration, defina o esquema padrão como
HttpSysDefaults.AuthenticationScheme
:using Microsoft.AspNetCore.Server.HttpSys; services.AddAuthentication(HttpSysDefaults.AuthenticationScheme);
A falha ao definir o esquema padrão impede que a solicitação de autorização (desafio) funcione com a seguinte exceção:
System.InvalidOperationException
: nenhum authenticationScheme foi especificado e não foi encontrado nenhum DefaultAuthenticateScheme.
Para obter mais informações, confira Configurar autenticação do Windows no ASP.NET Core.
Instâncias de IdentityCookieOptions
Um efeito colateral das alterações 2.0 é a opção para usar opções nomeadas em vez de instâncias de opções do cookie. A capacidade de personalizar os nomes do esquema Identitycookie é removida.
Por exemplo, projetos 1.x usam injeção de construtor para passar um parâmetro IdentityCookieOptions
para AccountController.cs
e ManageController.cs
. O esquema de autenticação externa cookie é acessado a partir da instância fornecida:
public AccountController(
UserManager<ApplicationUser> userManager,
SignInManager<ApplicationUser> signInManager,
IOptions<IdentityCookieOptions> identityCookieOptions,
IEmailSender emailSender,
ISmsSender smsSender,
ILoggerFactory loggerFactory)
{
_userManager = userManager;
_signInManager = signInManager;
_externalCookieScheme = identityCookieOptions.Value.ExternalCookieAuthenticationScheme;
_emailSender = emailSender;
_smsSender = smsSender;
_logger = loggerFactory.CreateLogger<AccountController>();
}
A injeção de construtor mencionada anteriormente torna-se desnecessária em projetos 2.0 e o campo _externalCookieScheme
pode ser excluído:
public AccountController(
UserManager<ApplicationUser> userManager,
SignInManager<ApplicationUser> signInManager,
IEmailSender emailSender,
ISmsSender smsSender,
ILoggerFactory loggerFactory)
{
_userManager = userManager;
_signInManager = signInManager;
_emailSender = emailSender;
_smsSender = smsSender;
_logger = loggerFactory.CreateLogger<AccountController>();
}
Os projetos 1.x usaram o campo _externalCookieScheme
da seguinte maneira:
// Clear the existing external cookie to ensure a clean login process
await HttpContext.Authentication.SignOutAsync(_externalCookieScheme);
Em projetos 2.0, substitua o código anterior pelo seguinte. A constante IdentityConstants.ExternalScheme
pode ser usada diretamente.
// Clear the existing external cookie to ensure a clean login process
await HttpContext.SignOutAsync(IdentityConstants.ExternalScheme);
Resolva a chamada recém-adicionada SignOutAsync
importando o seguinte namespace:
using Microsoft.AspNetCore.Authentication;
Adicionar propriedades de navegação POCO do IdentityUser
As propriedades de navegação da Entity Framework (EF) Core do POCO base IdentityUser
(Objeto CLR Antigo Simples) foram removidas. Se o projeto 1.x usou essas propriedades, adicione-as manualmente de volta ao projeto 2.0:
/// <summary>
/// Navigation property for the roles this user belongs to.
/// </summary>
public virtual ICollection<IdentityUserRole<int>> Roles { get; } = new List<IdentityUserRole<int>>();
/// <summary>
/// Navigation property for the claims this user possesses.
/// </summary>
public virtual ICollection<IdentityUserClaim<int>> Claims { get; } = new List<IdentityUserClaim<int>>();
/// <summary>
/// Navigation property for this users login accounts.
/// </summary>
public virtual ICollection<IdentityUserLogin<int>> Logins { get; } = new List<IdentityUserLogin<int>>();
Para evitar chaves estrangeiras duplicadas ao executar migrações EF Core, adicione o seguinte ao método IdentityDbContext
da classe OnModelCreating
(após a chamada base.OnModelCreating();
):
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
// Customize the ASP.NET Core Identity model and override the defaults if needed.
// For example, you can rename the ASP.NET Core Identity table names and more.
// Add your customizations after calling base.OnModelCreating(builder);
builder.Entity<ApplicationUser>()
.HasMany(e => e.Claims)
.WithOne()
.HasForeignKey(e => e.UserId)
.IsRequired()
.OnDelete(DeleteBehavior.Cascade);
builder.Entity<ApplicationUser>()
.HasMany(e => e.Logins)
.WithOne()
.HasForeignKey(e => e.UserId)
.IsRequired()
.OnDelete(DeleteBehavior.Cascade);
builder.Entity<ApplicationUser>()
.HasMany(e => e.Roles)
.WithOne()
.HasForeignKey(e => e.UserId)
.IsRequired()
.OnDelete(DeleteBehavior.Cascade);
}
Substituir GetExternalAuthenticationSchemes
O método síncrono GetExternalAuthenticationSchemes
foi removido em favor de uma versão assíncrona. Os projetos 1.x têm o seguinte código em Controllers/ManageController.cs
:
var otherLogins = _signInManager.GetExternalAuthenticationSchemes().Where(auth => userLogins.All(ul => auth.AuthenticationScheme != ul.LoginProvider)).ToList();
Esse método também aparece em Views/Account/Login.cshtml
:
@{
var loginProviders = SignInManager.GetExternalAuthenticationSchemes().ToList();
if (loginProviders.Count == 0)
{
<div>
<p>
There are no external authentication services configured. See <a href="https://go.microsoft.com/fwlink/?LinkID=532715">this article</a>
for details on setting up this ASP.NET application to support logging in via external services.
</p>
</div>
}
else
{
<form asp-controller="Account" asp-action="ExternalLogin" asp-route-returnurl="@ViewData["ReturnUrl"]" method="post" class="form-horizontal">
<div>
<p>
@foreach (var provider in loginProviders)
{
<button type="submit" class="btn btn-default" name="provider" value="@provider.AuthenticationScheme" title="Log in using your @provider.DisplayName account">@provider.AuthenticationScheme</button>
}
</p>
</div>
</form>
}
}
Em projetos 2.0, use o método GetExternalAuthenticationSchemesAsync. A alteração em ManageController.cs
é semelhante ao código a seguir:
var schemes = await _signInManager.GetExternalAuthenticationSchemesAsync();
var otherLogins = schemes.Where(auth => userLogins.All(ul => auth.Name != ul.LoginProvider)).ToList();
No Login.cshtml
, a propriedade AuthenticationScheme
acessada no loop foreach
é alterada para Name
:
@{
var loginProviders = (await SignInManager.GetExternalAuthenticationSchemesAsync()).ToList();
if (loginProviders.Count == 0)
{
<div>
<p>
There are no external authentication services configured. See <a href="https://go.microsoft.com/fwlink/?LinkID=532715">this article</a>
for details on setting up this ASP.NET application to support logging in via external services.
</p>
</div>
}
else
{
<form asp-controller="Account" asp-action="ExternalLogin" asp-route-returnurl="@ViewData["ReturnUrl"]" method="post" class="form-horizontal">
<div>
<p>
@foreach (var provider in loginProviders)
{
<button type="submit" class="btn btn-default" name="provider" value="@provider.Name" title="Log in using your @provider.DisplayName account">@provider.DisplayName</button>
}
</p>
</div>
</form>
}
}
Alteração da propriedade ManageLoginsViewModel
Um objeto ManageLoginsViewModel
é usado na ação ManageLogins
de ManageController.cs
. Em projetos 1.x, o tipo de retorno da propriedade OtherLogins
do objeto é IList<AuthenticationDescription>
. Esse tipo de retorno requer uma importação de Microsoft.AspNetCore.Http.Authentication
:
using System.Collections.Generic;
using Microsoft.AspNetCore.Http.Authentication;
using Microsoft.AspNetCore.Identity;
namespace AspNetCoreDotNetCore1App.Models.ManageViewModels
{
public class ManageLoginsViewModel
{
public IList<UserLoginInfo> CurrentLogins { get; set; }
public IList<AuthenticationDescription> OtherLogins { get; set; }
}
}
Em projetos 2.0, o tipo de retorno muda para IList<AuthenticationScheme>
. Esse novo tipo de retorno requer a substituição da importação Microsoft.AspNetCore.Http.Authentication
por uma importação Microsoft.AspNetCore.Authentication
.
using System.Collections.Generic;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Identity;
namespace AspNetCoreDotNetCore2App.Models.ManageViewModels
{
public class ManageLoginsViewModel
{
public IList<UserLoginInfo> CurrentLogins { get; set; }
public IList<AuthenticationScheme> OtherLogins { get; set; }
}
}
Recursos adicionais
Para obter mais informações, consulte Discussão sobre o problema do Auth 2.0 no GitHub.