ASP .NET Core - Introdução ao Identity

  Neste artigo vou apresentar os conceitos básicos relacionados com a ASP .NET Core Identity.

A ASP.NET Core Identity é um sistema de associação que permite adicionar funcionalidade de login ao seu aplicativo. Os usuários podem criar uma conta e fazer o login com um nome de usuário e senha ou podem usar um provedor de login externo, como Facebook, Google, Conta da Microsoft, Twitter, etc.

Podemos configurar a ASP.NET Core Identity para usar um banco de dados SQL Server para armazenar nomes de usuário, senhas e dados de perfil. Como alternativa, você pode usar seu próprio armazenamento persistente para armazenar dados em outro fonte de armazenamento como o Azure Table Storage.

Nota:  Adaptado do original https://docs.microsoft.com/en-us/aspnet/core/security/authentication/identity.

Neste artigo vamos apresentar o funcionamento (ou recordar) da ASP .NET Core Identity para adicionar as funcionalidades de registrar, fazer o login e o logout de um usuário.

Para acompanhar este artigo você precisa ter instalado o Visual Studio Community 2017 com os seguintes workloads e recursos instalados:

Se você não tem nenhuma noção sobre como funciona o padrão MVC e sua implementação ASP .NET sugiro que leia esses artigos :

Criando uma nova aplicação ASP .NET MVC com Identity

Abra no VS 2017 e no menu File clique em New Project;

A seguir selecione o template Visual C# -> Web e marque ASP .NET Core Web Application (.NET Framework);

Informe o nome AspnCore_Identity e clique no botão OK;

Na próxima janela escolha a versão ASP .NET Core 1.1 e marque o template Web Application com autenticação : Individual User Accounts e clique no botão OK;

O projeto será criado contendo o pacote Microsoft.AspNetCore.Identity.EntityFrameworkCore  que irá persitir os dados do da identidade do usuário e o schema para o SQL server usando o Entity Framework Core.

Podemos verificar na janela Solution Explorer e expandindo o item Nuget :

Os serviços do Identity foram adicionados na aplicação no método ConfigureServices do arquivo Startup.cs , conforme vemos a seguir no código destacado em azul :

         ,,,
        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            // Add framework services.
            services.AddDbContext<ApplicationDbContext>(options =>
                options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
            services.AddIdentity<ApplicationUser, IdentityRole>()
                .AddEntityFrameworkStores<ApplicationDbContext>()
                .AddDefaultTokenProviders();
            services.AddMvc();
            // Add application services.
            services.AddTransient<IEmailSender, AuthMessageSender>();
            services.AddTransient<ISmsSender, AuthMessageSender>();
        }
      .....

Esses serviços são então disponibilizados para o aplicativo através de injeção de dependência.

O recurso Identity está habilitado para o aplicativo chamando UseIdentity no método Configure da classe Startup.  Isso adiciona uma autenticação baseada em cookie ao pipeline de solicitação.

Executando a aplicação iremos obter a página incial com os links para Registrar um usuário e fazer o login:

Vamos registrar um novo usuário clicando no link Register e informando os dados :

Se você obtiver a página com a mensagem de erro conforme mostrada a seguir, não se preocupe, basta clicar no botão Apply Migrations e dar um Refresh na página:

A página será recarregada e exibirá o email do usuário registrado e link para fazer o log out:

Vamos entender o que aconteceu por 'trás dos panos'...

Quando o usuário clicou no link Register, os serviços UserManager e SignInManager são injetados no controlador AccountController conforme mostra o código abaixo:

using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using AspnCore_Identity.Models;
using AspnCore_Identity.Models.AccountViewModels;
using AspnCore_Identity.Services;
namespace AspnCore_Identity.Controllers
{
    [Authorize]
    public class AccountController : Controller
    {
        private readonly UserManager<ApplicationUser> _userManager;
        private readonly SignInManager<ApplicationUser> _signInManager;
        private readonly IEmailSender _emailSender;
        private readonly ISmsSender _smsSender;
        private readonly ILogger _logger;
        private readonly string _externalCookieScheme;
        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 seguir a Action Register cria o usuário chamando o método CreateAsync do objeto UserManager  como vemos a seguir:

         ...
        // GET: /Account/Register
        [HttpGet]
        [AllowAnonymous]
        public IActionResult Register(string returnUrl = null)
        {
            ViewData["ReturnUrl"] = returnUrl;
            return View();
        }
        //
        // POST: /Account/Register
        [HttpPost]
        [AllowAnonymous]
        [ValidateAntiForgeryToken]
        public async Task<IActionResult> Register(RegisterViewModel model, string returnUrl = null)
        {
            ViewData["ReturnUrl"] = returnUrl;
            if (ModelState.IsValid)
            {
                var user = new ApplicationUser { UserName = model.Email, Email = model.Email };
                var result = await _userManager.CreateAsync(user, model.Password);
                if (result.Succeeded)
                {
                    // For more information on how to enable account confirmation and password reset please visit https://go.microsoft.com/fwlink/?LinkID=532713
                    // Send an email with this link
                    //var code = await _userManager.GenerateEmailConfirmationTokenAsync(user);
                    //var callbackUrl = Url.Action(nameof(ConfirmEmail), "Account", new { userId = user.Id, code = code }, protocol: HttpContext.Request.Scheme);
                    //await _emailSender.SendEmailAsync(model.Email, "Confirm your account",
                    //    $"Please confirm your account by clicking this link: <a href='{callbackUrl}'>link</a>");
                    await _signInManager.SignInAsync(user, isPersistent: false);
                    _logger.LogInformation(3, "User created a new account with password.");
                    return RedirectToLocal(returnUrl);
                }
                AddErrors(result);
            }
            // If we got this far, something failed, redisplay form
            return View(model);
        }

Se o usuário foi criado com êxito, o usuário está conectado pelo método SignInAsync, também contido na Action Register.

Ao iniciar a sessão, o método SignInAsync armazena um cookie com as reivindicações(claims) do usuário e  chama a tarefa SignInAsync, que está contida na classe SignInManager.

Se necessário, você pode acessar os detalhes de identidade do usuário dentro de uma ação do controlador. Por exemplo, ao definir um ponto de interrupção dentro do método Action Index do HomeController, você pode exibir os detalhes via User.claims. Ao fazer o login do usuário, você pode tomar decisões de autorização.

Como usuário registrado, você pode efetuar login no aplicativo clicando no link Log in. Quando um usuário registrado faz logon, a Action Login do AccountController é chamada. Em seguida, a Action Login   marca o usuário usando o método PasswordSignInAsync contido na Action Login.

         //
        // GET: /Account/Login
        [HttpGet]
        [AllowAnonymous]
        public async Task<IActionResult> Login(string returnUrl = null)
        {
            // Clear the existing external cookie to ensure a clean login process
            await HttpContext.Authentication.SignOutAsync(_externalCookieScheme);
            ViewData["ReturnUrl"] = returnUrl;
            return View();
        }
        //
        // POST: /Account/Login
        [HttpPost]
        [AllowAnonymous]
        [ValidateAntiForgeryToken]
        public async Task<IActionResult> Login(LoginViewModel model, string returnUrl = null)
        {
            ViewData["ReturnUrl"] = returnUrl;
            if (ModelState.IsValid)
            {
                // This doesn't count login failures towards account lockout
                // To enable password failures to trigger account lockout, set lockoutOnFailure: true
                var result = await _signInManager.PasswordSignInAsync(model.Email, model.Password, model.RememberMe, lockoutOnFailure: false);
                if (result.Succeeded)
                {
                    _logger.LogInformation(1, "User logged in.");
                    return RedirectToLocal(returnUrl);
                }
                if (result.RequiresTwoFactor)
                {
                    return RedirectToAction(nameof(SendCode), new { ReturnUrl = returnUrl, RememberMe = model.RememberMe });
                }
                if (result.IsLockedOut)
                {
                    _logger.LogWarning(2, "User account locked out.");
                    return View("Lockout");
                }
                else
                {
                    ModelState.AddModelError(string.Empty, "Invalid login attempt.");
                    return View(model);
                }
            }
            // If we got this far, something failed, redisplay form
            return View(model);
        }

Ao clicar no link Log off será chamada a Action LogOut do controlador AccountController. Nesta Action o método SignOutAsync da classes SignInManager será invocado para fazer o logout:

         //
        // POST: /Account/Logout
        [HttpPost]
        [ValidateAntiForgeryToken]
        public async Task<IActionResult> Logout()
        {
            await _signInManager.SignOutAsync();
            _logger.LogInformation(4, "User logged out.");
            return RedirectToAction(nameof(HomeController.Index), "Home");
        }
 

Você também pode alterar o comportamento do identity usando o método Configure da classe Startup

// Configure Identity
services.Configure<IdentityOptions>(options =>
{
    // Password settings
    options.Password.RequireDigit = true;
    options.Password.RequiredLength = 8;
    options.Password.RequireNonAlphanumeric = false;
    options.Password.RequireUppercase = true;
    options.Password.RequireLowercase = false;
    
    // Lockout settings
    options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(30);
    options.Lockout.MaxFailedAccessAttempts = 10;
    
    // Cookie settings
    options.Cookies.ApplicationCookie.ExpireTimeSpan = TimeSpan.FromDays(150);
    options.Cookies.ApplicationCookie.LoginPath = "/Account/LogIn";
    options.Cookies.ApplicationCookie.LogoutPath = "/Account/LogOut";
    
    // User settings
    options.User.RequireUniqueEmail = true;
});

Vamos agora dar uma espiada no banco de dados do usuário usado pelo Identity.

Pare a aplicação e no menu View clique em SQL Server Object Explorer.

A seguir clique na conexão : (localDb)\MSSSQLLocalDB (SQL Server....)

Depois expanda o item DataBase e clique em  aspnet-AspnCore_Identity... , que é o nome da nossa aplicação.

Finalmente expanda os itens Tables.

Clique com o botão direito sobre a tabela dbo.AspNetUsers e seleciona a opção View Data:

Você deverá visualizar o usuário que registrou ao clica no link Register:

Para concluir vejamos quais os componentes do Identity.

Os Componentes do Identity

O assembly de referência primária para o sistema Identity é o pacote Microsoft.AspNetCore.Identity. Este pacote contém o conjunto básico de interfaces para ASP.NET Core Identity.

As seguintes dependências são necessárias para usar o sistema de identidade em aplicativos ASP.NET Core:

Com isso apresentamos de forma resumida como funciona a ASP .NET Core Identity para as funcionalidades de Registro e Login.

"Se alguém me serve, siga-me, e onde eu estiver, ali estará também o meu servo. E, se alguém me servir, meu Pai o honrará. "
João 12:26

Veja os Destaques e novidades do SUPER DVD Visual Basic (sempre atualizado) : clique e confira !

Quer migrar para o VB .NET ?

Quer aprender C# ??

Quer aprender os conceitos da Programação Orientada a objetos ?

Quer aprender o gerar relatórios com o ReportViewer no VS 2013 ?

  Gostou ?   Compartilhe no Facebook   Compartilhe no Twitter

Referências:


José Carlos Macoratti