ASP.NET Identity: Autenticação de usuários com Claims

Veremos nesse artigo como autenticar usuários no sistema muito além do login e senha, comumente pedido.

Com a evolução da internet, as aplicações web ficaram mais rigorosas em relação à autenticação e a autorização de seus usuários. Hoje presenciamos sites autenticados por redes sociais, autorização de sites baseados na idade, e-commerces que vendem apenas para uma região especifica, ou seja, hoje um e-mail e uma senha não são suficientes para que possamos determinar a identidade do usuário ou se ele pode ou não usar o sistema.

Podemos usar roles para autenticar um grupo de usuário que contém características diferenciadas como, por exemplo, usuários normais e usuários administradores. Porém, quando estamos lidando com sistemas mais complexos, novos perfis devem ser levados em consideração como, por exemplo, setor da empresa, hierarquia de cargos, filiais, entre outros; e o uso de roles não é a forma mais adequada para resolver este problema. Aqui que entra o uso de Claims (declarações), os quais, a partir de pequenas informações do usuário, podem determinar uma política de segurança para o sistema.

A classe ClaimsIdentity e ClaimsPrincipal

Para que possamos entender as classes ClaimsIdentity e ClaimsPrincipal precisamos entender o conceito de duas classes pertencentes ao Namespace System.Security.Principal:

  1. IIdentity– Basicamente contém o nome do usuário autenticado e o tipo de autenticação que está sendo usada (cookieAutentication, TokenAutentication entre outros);
  2. IPrincipal– Contém a informação sobre permissões e autorização do usuário no contexto que ele está inserido.

Para construir uma identidade para um usuário utilizamos as seguintes classes:

  1. GenericPrincipal e GenericIdentity- Usada em ambientes Web e que herdam das respectivas classes ClaimsPrincipal e ClaimsIdentity;
  2. WindowsPrincipal e WindowsIdentity- Usada em ambientes intranet e que herdam das respectivas classes ClaimsPrincipal e ClaimsPrincipal;
  3. ClaimsPrincipal e ClaimsIdentity - Herdam das respectivas interfaces IPrincipal e IIdentity.

Note que as classes GenericIdentity e WindowsIdentity herdam da classe ClaimIdentity, mas nem sempre foi assim, pois antes da construção da versão 4.5 do framework .NET, estas classes herdavam diretamente da interface IIdentity. Já a partir da verão 4.5, estas classes passaram a herdar de claimsIdentity, o que obriga os desenvolvedores a trabalharem com Claims.

Para exemplificar a construção de uma identidade usando Claims, vamos analisar a classe Claim do namespace System.Security.Claims, presente na Figura 1.

Figura 1. Definição da classe Claim.

Note que para criar uma Claim basta apenas definir um tipo e um valor como, por exemplo:

Claim claim = new Claim("Nome Completo", "Fabio Rodrigues Fonseca");

Para facilitar a criação de Claims, o .NET Framework disponibiliza a classe estática ClaimTypes com tipos já definidos:

Claim claim = new Claim(ClaimTypes.Name, "Fabio Rodrigues Fonseca");

A identificação do usuário em um sistema pode ser realizada com a classe ClaimsIdentity, o qual, a partir de uma coleção de Claims, é criada a identidade que pode ser associada ao ambiente através da classe ClaimsPrincipal, conforme a Listagem 1.

Listagem 1. Criando uma identidade para o usuário.

Claim claim2 = new Claim(ClaimTypes.Name, "Fabio Rodrigues Fonseca"); Claim claim3 = new Claim(ClaimTypes.Role, "Administrador"); Claim claim4 = new Claim(ClaimTypes.Email, "fabison@ig.com.br"); IList<Claim> Claims = new List<Claim>() { claim2, claim3, claim4 }; //Criando uma Identidade e associando-a ao ambiente. ClaimsIdentity identity = new ClaimsIdentity(Claims); ClaimsPrincipal principal = new ClaimsPrincipal(identity); Thread.CurrentPrincipal = principal;

Veja que criamos uma identidade, mas o usuário ainda não esta autenticado, pois não foi descrito o tipo de autenticação do usuário (string) e, para solucionar este problema, basta preencher o construtor da classe ClaimsIdentity:

ClaimsIdentity identity = new ClaimsIdentity(Claims, "Devimedia");

Para definir o nome da identidade do usuário a Classe ClaimsIdentity escolhe, por padrão, o valor do ClaimTypes.Name, que, no nosso caso, será Fábio Rodrigues Fonseca. Porém, pode-se alterar esta propriedade e também definir as roles para esta identidade, conforme mostra a Listagem 2.

Listagem 2. Definindo as roles e o nome da Identidade.

//Criando uma Identidade e associando-a ao ambiente. ClaimsIdentity identity2 = new ClaimsIdentity(Claims, "Devimedia", ClaimTypes.Email, ClaimTypes.Role); ClaimsPrincipal principal2 = new ClaimsPrincipal(identity2); Thread.CurrentPrincipal = principal2;

Veja que definimos a claimType.Email como o nome da identidade e, caso haja roles na coleção “Claims”, elas serão as roles.

Para selecionar as Claims criadas no sistema basta selecionar a classe Thread.CurrentPrincipal, que contém as Claims do sistema, conforme a Listagem 3.

Listagem 3. Selecionando as Claims do usuario.

ClaimsPrincipal currentPrincipal = Thread.CurrentPrincipal as ClaimsPrincipal; Console.WriteLine("Claims do Usuário:\n"); foreach (Claim ci in currentPrincipal.Claims) Console.WriteLine(ci.Value);

Assim, o código inteiro fica igual ao da Listagem 4 e sua execução pode ser vista na Figura 2.

Listagem 4. Exemplo de Claims.

public static class Program { public static void Main() { Claim claim2 = new Claim(ClaimTypes.Name, "Fabio Rodrigues Fonseca"); Claim claim3 = new Claim(ClaimTypes.Role, "Administrador"); Claim claim4 = new Claim(ClaimTypes.Email, "fabison@ig.com.br"); IList<Claim> Claims = new List<Claim>() { claim2, claim3, claim4 }; //Criando uma Identidade e associando-a ao ambiente. ClaimsIdentity identity = new ClaimsIdentity(Claims, "Devimedia"); ClaimsPrincipal principal = new ClaimsPrincipal(identity); Thread.CurrentPrincipal = principal; Console.Write("\n\n"); Console.WriteLine("Usuário Autenticado:" + Thread.CurrentPrincipal.Identity.IsAuthenticated); Console.WriteLine("Identidade:" + Thread.CurrentPrincipal.Identity.Name); Console.Write("\n"); //Criando uma Identidade e associando-a ao ambiente. ClaimsIdentity identity2 = new ClaimsIdentity(Claims, "Devimedia", ClaimTypes.Email, ClaimTypes.Role); ClaimsPrincipal principal2 = new ClaimsPrincipal(identity2); Thread.CurrentPrincipal = principal2; Console.WriteLine("Usuário Autenticado:" + Thread.CurrentPrincipal.Identity.IsAuthenticated); Console.WriteLine("Identidade:" + Thread.CurrentPrincipal.Identity.Name); Console.Write("\n"); ClaimsPrincipal currentPrincipal = Thread.CurrentPrincipal as ClaimsPrincipal; Console.WriteLine("Claims do Usuário:\n"); foreach (Claim ci in currentPrincipal.Claims) Console.WriteLine(ci.Value); Console.Write("\n"); Console.WriteLine(currentPrincipal.Identity.Name + " Pertence a role Administrador? \n" + currentPrincipal.IsInRole("Administrador")); Console.ReadKey(); } }

Figura 2. Resultado da Listagem 4

Para aplicar o conceito descrito vamos ver dois exemplos: o primeiro utilizando MVC5 com o ASP.NET Identity e o segundo utilizando WebApi com o ASP.NET Identity.

Para que o leitor possa entender plenamente este artigo é recomendado ler antes os artigos sobre ASP.Net Identity eOwin, já que o Identity suporta o Owin, e toda a parte de autenticação e autorização é baseada em middlewares.

ASP.Net MVC e Claims

Para falarmos de MVC e Claims vamos criar um projeto e baixar alguns pacotes via nuget. Para este fim ir até o menuFile > New > Projet > ASP.NET Web Application e escolha um template empty do Tipo MVC, conforme a Figura 3.

Figura 3. Criando um projeto MVC.

Baixaremos os seguintes pacotes via nuget:

Em seguida, vamos criar uma classe Startup, conforme a Listagem 5.

Listagem 5. Classe Startup.

public class Startup { public void Configuration(IAppBuilder app) { } }

Agora vamos criar os middlewares que serão responsáveis para a conexão com o banco, gerenciamento da conta do usuário, do login do usuário ed o modo de autenticação e suas configurações, conforme a Listagem 6. O contexto do Entity framework e o UserManager fica por conta das Listagens 7 e 8, respectivamente.

Listagem 6. Middlewares que serão executados por request.

public class Startup { public void Configuration(IAppBuilder app) { app.CreatePerOwinContext(Data.Contexto.Create); app.CreatePerOwinContext<AppUserManager>(AppUserManager.Create); app.CreatePerOwinContext<AppSignInManager>(AppSignInManager.Create); app.UseCookieAuthentication(new CookieAuthenticationOptions { AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie, LoginPath = new PathString("/Home/Login"), CookieName = "Devimedia", CookiePath = "/" }); } }

Listagem 7. Contexto do EF para criação e consulta do banco.

public class Contexto : IdentityDbContext<IdentityUser> { public Contexto() : base(@"Data Source=(localdb)\mssqllocaldb;Integrated Security=True; Initial Catalog=DeviMediaClaims; Connect Timeout=15; Encrypt=False;TrustServerCertificate=False") { } public static Contexto Create() { return new Contexto(); } }

Listagem 8. UserManger – Classe responsável por gerenciar a conta do usuário.

public class AppUserManager : UserManager<IdentityUser> { public AppUserManager(IUserStore<IdentityUser> store) : base(store) { } public static AppUserManager Create(IdentityFactoryOptions<AppUserManager> option, IOwinContext context) { var contexto = context.Get<Contexto>(); var store = new UserStore<IdentityUser>(contexto); var userManager = new AppUserManager(store); return userManager; } }

Para criar um SignManager usaremos o código da Listagem 9, que é a classe responsável por gerenciar o Login do usuário.

Listagem 9. AppSignInManger

public class AppSignInManager : SignInManager<IdentityUser, string> { public AppSignInManager(AppUserManager userManager, IAuthenticationManager authenticationManager) : base(userManager, authenticationManager) { } public static AppSignInManager Create(IdentityFactoryOptions<AppSignInManager> option, IOwinContext context) { var manager = context.GetUserManager<AppUserManager>(); var sign = new AppSignInManager(manager, context.Authentication); return sign; } }

E para criar uma Controller Home com uma ActionResult Index use o código presente na Listagem 10.

Listagem 10. Controller Home.

public class HomeController : Controller { public ActionResult Index() { return View(); } }

Para criar um usuário temos a ActionResult Create, a qual, a partir do objeto userManager, é criado o usuário ffonseca, como mostra a Listagem 11.

Listagem 11. ActionResult Create utilizada para criar um usuário.

public async Task<ActionResult> Create() { var usuario = new IdentityUser() { UserName = "ffonseca" }; var userManager = HttpContext.GetOwinContext().GetUserManager<AppUserManager>(); IdentityResult result = await userManager.CreateAsync(usuario, "123Tr0car@@"); if (result.Succeeded) ViewBag.Resultado = "Usuario Criado com sucesso"; else ViewBag.Resultado = string.Join(",", result.Errors); return View(); }

Quando o usuário estiver criado e for logar, algumas Claims serão geradas em tempo de execução. Para isso, um overrride no método CreateUserIdentityAsync da classe AppSignInManager será feito, conforme as Listagens 12 e 13.

Listagem 12. Action Login utilizada para Logar um usuário.

public async Task<ActionResult> Login() { var appAsign = HttpContext.GetOwinContext().Get<AppSignInManager>(); var userManager = HttpContext.GetOwinContext().GetUserManager<AppUserManager>(); var user = await userManager.FindAsync("ffonseca", "123Tr0car@@"); if (user != null) await appAsign.SignInAsync(user, true, true); return View(); }

Listagem 13. Override do Método CreateUserIdentityAsync utilizada para criar Claims quando o usuário logar.

public class AppSignInManager : SignInManager<IdentityUser, string> { public AppSignInManager(AppUserManager userManager, IAuthenticationManager authenticationManager) : base(userManager, authenticationManager) { } public static AppSignInManager Create(IdentityFactoryOptions<AppSignInManager> option, IOwinContext context) { var manager = context.GetUserManager<AppUserManager>(); var sign = new AppSignInManager(manager, context.Authentication); return sign; } public override async Task<ClaimsIdentity> CreateUserIdentityAsync(IdentityUser user) { ClaimsIdentity claimIdentity = await base.CreateUserIdentityAsync(user); claimIdentity.AddClaim(new Claim(ClaimTypes.Country, "Brasil")); claimIdentity.AddClaim(new Claim(ClaimTypes.Gender, "Masculino")); claimIdentity.AddClaim(new Claim(ClaimTypes.Role, "Administrador")); return claimIdentity; } }

Note que no método CreateUserIdentityAsync criamos um objeto do tipo ClaimsIdentity e inserimos algumas claims do usuário, inclusive uma role Administrador. Para que o leitor possa entender melhor, vamos debugar o código, conforme as Figuras 4 a 6.

Figura 4. Criando o usuário.

Figura 5. Logando o usuário.

Figura 6. Criando Claims partir da classe CreateUserIdentityAsync.

Note que quando o usuário Logar, automaticamente, será chamado o método CreateUserIdentityAsync.

Para verificar se as Claims foram criadas, foi criadq uma ActionResult, a qual seleciona as claims, a partir do objeto Thread.CurrentPrincipal, que é uma thread com a identidade do usuário no sistema, conforme a Listagem 14.

Listagem 14. Selecionando as Claims criadas.

[Authorize] public ActionResult VerificarClaims() { ClaimsPrincipal currentPrincipal = Thread.CurrentPrincipal as ClaimsPrincipal; var claims = new Collection<string>(); foreach (Claim ci in currentPrincipal.Claims) claims.Add("Tipo: " + ci.Type + " < ----- > Valor:" + ci.Value); return View(claims); }

Note que o objeto Thread.CurrentPrincipal retorna um objeto do tipo IPrincipal, logo, teremos que fazer um Cast para ClaimsPrincipal e assim ter a capacidade de selecionar as claims do Usuario logado. Executado o projeto e navegando até a ActionResult VerificarClaims, temos o resultado presente na Figura 7.

Figura 7. Claims criadas

Note que além das Claims criadas anteriormente, por padrão, algumas claims são criadas como, por exemplo, name, provider, etc.

Como criamos a Claim do Tipo Role com o nome Administrador, uma role Administrador foi criada, como mostram a Listagem 15 e a Figura 8.

Listagem 15. ActionResult que permite acesso à apenas Administradores.

[Authorize(Roles="Administrador")] public ActionResult SoAdministrador() { return View(); }

Figura 8. ActionResult SoAdministrador sendo acessada via debug.

Muitas vezes queremos que a política de segurança seja mais abrangente, ou seja, peça ao usuário para logar no sistema com algumas propriedades, como por exemplo, nacionalidade, sexo, idade, entre outros. Porém, ficar construindo inúmeras Roles não é a melhor forma de solucionar este problema, pois o código fica muito poluído e, por muitas vezes, conter mais roles do que o necessário, como podemos ver na Listagem 16.

Listagem 16. Exemplo de uma política de segurança que usa apenas Claims.

[Authorize(Roles="Role1, Role2, Role3, Role4...")] public ActionResult ActionTeste() { return View(); }

Para solucionar este problema, podemos criar um Filter que, a partir das Claims (Declarações do usuário), para deixar o código mais elegante, conforme as Listagens 17 e 18.

Listagem 17. Criando um filter para a política de acesso.

public class ClaimsAuthorizeAttribute : AuthorizeAttribute { public ClaimsAuthorizeAttribute() { } public override void OnAuthorization(AuthorizationContext filterContext) { var user = HttpContext.Current.User as ClaimsPrincipal; if (user.Claims.Where(c => c.Type == ClaimTypes.Country) .Any(x => x.Value == "Brasil") && user.IsInRole("Administrador")) { base.OnAuthorization(filterContext); } else { filterContext.Result = new RedirectToRouteResult( new RouteValueDictionary { { "action", "Login" }, { "controller", "Home" } }); } } }

Listagem 18. ActionResult decorada com o Filtro TestarClaimFilter.

[ClaimsAuthorizeAttribute] public ActionResult TestarClaimFilter() { return View(); }

Note que somente usuários Brasileiros e administradores serão capazes de acessar a ActionResult TestarClaimFilter, e como nosso usuário contém estes requisitos, ele será capaz de navegar através desta ActionResult.

Executando o projeto e navegando até a ActionResult TestarClaimFilter, notamos que o usuário acessa esta página e o filtro, conforme o debug visto nas Figuras 9 e 10.

Figura 9. Acessando o Filter e autorizando o usuário via debug.

Figura 10. Testando a actionResult TestarClaimFilter via Debug

WEB.API e Claims

Muitos projetistas de software optam em dividir o sistema em pequenos serviços, ao invés de centralizá-lo em uma única solução. Existem vários benefícios nesta abordagem, como por exemplo, a facilidade de deploy, testes, entendimento do sistema, build, atualização de frameworks, rapidez no desenvolvimento entre outros. Indo neste caminho, faremos uma demonstração de uma pequena implementação utilizando WEB.Api com ASP.NET Identity, abordando conceitos de Claims, com o objetivo de centralizar um serviço de autenticação e autorização em uma Api.

Para este fim, vamos criar um projeto Empty do Tipo WebApi, conforme a Figura 11.

Figura 11. Criando um projeto WebApi.

Após criar o projeto, baixe os seguintes pacotes via nuget, como fizermos no exemplo anterior:

Note que baixamos o pacote Microsoft.AspNet.WebApi.Owin, pois iremos trabalhar com Owin e WebApi. Como o Owin trabalha com middlewares, criamos uma classe Startup com o código da Listagem 19.

Listagem 19. Classe Startup.

public class Startup { public void Configuration(IAppBuilder app) { } }

Para que o sistema funcione corretamente iremos configurar a rota, o contexto e os managers, conforme a Listagem 20.

Listagem 20. Configuração das rotas e middlewares da classe Startup.

public class Startup { public void Configuration(IAppBuilder app) { var configuration = new HttpConfiguration(); ConfigureRotas(configuration); app.CreatePerOwinContext(Contexto.Create); app.CreatePerOwinContext<AppUserManager>(AppUserManager.Create); } } private static void ConfigureRotas(HttpConfiguration config) { config.MapHttpAttributeRoutes(); config.Routes.MapHttpRoute( name: "DefaultApi", routeTemplate: "api//", defaults: new { id = RouteParameter.Optional } ); }

A estratégia para o nosso projeto é gerar um token, de tal forma que, quando o usuário logar este token será criado e disponibilizado no Client. Para este fim, utilizamos um middleware disponibilizado pela DLL Microsoft.Owin.Security.OAuth, que será construído e disponibilizado por um provider herdado de OAuthAuthorizationServerProvider. O código completo da classe Startup fica igual ao da Listagem 21.

Listagem 21. Classe Startup completa

public class Startup { public void Configuration(IAppBuilder app) { var configuration = new HttpConfiguration(); ConfigureRotas(configuration); app.CreatePerOwinContext(Contexto.Create); app.CreatePerOwinContext<AppUserManager>(AppUserManager.Create); var oAuthServerOptions = new OAuthAuthorizationServerOptions() { AllowInsecureHttp = true, TokenEndpointPath = new PathString("/api/login"), AccessTokenExpireTimeSpan = TimeSpan.FromHours(1), Provider = new DeviMediaAuthorizationServerProvider() }; app.UseOAuthAuthorizationServer(oAuthServerOptions); app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions()); app.UseWebApi(configuration); } private static void ConfigureRotas(HttpConfiguration config) { config.MapHttpAttributeRoutes(); config.Routes.MapHttpRoute( name: "DefaultApi", routeTemplate: "api//", defaults: new { id = RouteParameter.Optional } ); } }

No código criamos um objeto do tipo OAuthAuthorizationServerOptions, que contém algumas configurações importantes:

Esta configuração será disponibilizada no middleware UseOAuthAuthorizationServer e, por fim, configuramos o middleware UseOAuthBearerAuthentication com as propriedades Default para gerar um token do tipo bearer, que trafega um token e não um usuário/senha. O provider DeviMediaAuthorizationServerProvider está descrito na Listagem 22.

Listagem 22. Provider customizado responsável por gerar o Token.

public class DeviMediaAuthorizationServerProvider : OAuthAuthorizationServerProvider { public override Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context) { context.Validated(); return Task.FromResult<object>(null); } public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context) { var userManager = context.OwinContext.GetUserManager<AppUserManager>(); var usuario = await userManager.FindAsync(context.UserName, context.Password); if (usuario == null) { context.SetError("invalid_grant", "Usuario inválido"); return; } ClaimsIdentity identity = await userManager.CreateIdentityAsync(usuario, context.Options.AuthenticationType); identity.AddClaim(new Claim(ClaimTypes.Country, "Brasil")); identity.AddClaim(new Claim(ClaimTypes.Role, "Administrador")); var tichet = new AuthenticationTicket(identity, GetProperties(usuario, identity.Claims)); context.Validated(tichet); } public override Task TokenEndpoint(OAuthTokenEndpointContext context) { foreach (var property in context.Properties.Dictionary) context.AdditionalResponseParameters.Add(property.Key, property.Value); return Task.FromResult<object>(null); } private static AuthenticationProperties GetProperties(IdentityUser usuario, IEnumerable<Claim> claims) { IDictionary<string, string> data = new Dictionary<string, string>(); data.Add(new KeyValuePair<string, string>("claims", string.Join(",", claims))); return new AuthenticationProperties(data); } }

Para criar um usuário criamos uma ApiController chamada AccountController e um método chamado Create com o código da Listagem 23.

Listagem 23. Classe responsável de criar um usuário.

[HttpPost] public async Task<HttpResponseMessage> Create() { HttpResponseMessage response; string resultado = string.Empty; try { var usuario = new IdentityUser() { UserName = "ffonseca" }; var userManager = Request.GetOwinContext().GetUserManager<AppUserManager>(); IdentityResult result = await userManager.CreateAsync(usuario, "123Tr0car@@"); if (result.Succeeded) resultado = "Usuario Criado com sucesso"; else resultado = string.Join(",", result.Errors); response = Request.CreateResponse(HttpStatusCode.OK, resultado); } catch (Exception ex) { resultado = ex.Message; response = Request.CreateResponse(HttpStatusCode.BadRequest, resultado); } return await Task.FromResult(response); }

Executando o código e navegando até a URL de criação do usuário via postman, temos o mesmo resultado da Figura 12.

Figura 12. Criando um usuário.

Para logar no sistema navegamos até a URL domínio/api/login, passando os seguintes parâmetros no cabeçalho da requisição:

Após executar a requisição, automaticamente será chamada o método GrantResourceOwnerCredentials, que irá criar um token com as Claims do usuário e inseri-las no token, conforme a Figura 13.

Figura 13. Criando o Token.

Note que foi gerado uma identidade no sistema, com o auxílio do método CreateIdentityAsync, o qual gerou um objeto ClaimsIdentity utilizado para inserir Claims adicionais e criar um Token; este token será utilizado toda vez que o usuário necessitar navegar na WebApi, ou seja, não será necessário logar toda hora - basta apenas passar o token no cabeçalho da requisição que o usuário será identificado.

A criação do Token é descrita na Figura 14.

Figura 14. Token gerado a partir das credenciais do usuário.

Note que algumas informações foram retornadas quando logamos como, por exemplo, o token, o tipo do token, a data de emissão etc. Caso queiramos retornar mais parâmetros na resposta da requisição temos que passar um dicionário de strings no construtor da classe AuthenticationTicket, conforme a Listagem 24.

Listagem 24. Definindo algumas propriedades.

Cvar tichet = new AuthenticationTicket(identity, GetProperties(usuario, identity.Claims)); private static AuthenticationProperties GetProperties(IdentityUser usuario, IEnumerable<Claim> claims) { IDictionary<string, string> data = new Dictionary<string, string>(); data.Add(new KeyValuePair<string, string>("claims", string.Join(",", claims))); return new AuthenticationProperties(data); }

Veja que inserimos todas as Claims do usuário. Por fim, teremos que também fazer um override no método TokenEndpoint adicionando os parâmetros na resposta da requisição, conforme a Listagem 25.

Listagem 25. Adicionado as propriedades na resposta da requisição.

public override Task TokenEndpoint(OAuthTokenEndpointContext context) { foreach (var property in context.Properties.Dictionary) context.AdditionalResponseParameters.Add(property.Key, property.Value); return Task.FromResult<object>(null); }

Executando o projeto e logando-se no sistema, temos o mesmo resultado da Figura 15.

Figura 15. Token e algumas propriedades.

Note que o token foi gerado e as informações das Claims do usuário foram disponibilizadas, conforme a Figura 16.

Figura 16. Propriedades disponibilizadas para o Client.

Para testar este token criamos um método chamado TesteLogin, o qual somente usuários autenticados poderão ter acesso a ela.

Listagem 26. Testando se o usuário esta logado.

[Authorize] [HttpGet] [Route("TesteLogin")] public string TesteLogin() { return "O Usuário esta logado."; }

Navegando, via postman, sem passar o token, observe que o acesso foi negado, conforme a Figura 17.

Figura 17. Usuário não autorizado.

Para que o usuário tenha acesso basta passar o parâmetro Authorization com o token criado no cabeçalho da requisição, como vemos na Figura 18.

Figura 18 Mensagem de Teste do Login.

Como este token contém todas as claims do usuário, e uma delas é uma role do tipo Administrador, então o usuário terá permissão de acesso a todos os métodos restritos a Administradores; para testar esta afirmação, construímos um método chamado SoAdministrador, conforme a Listagem 27.

Listagem 27. Testando se o usuário pertence a Role Administrador.

[Authorize(Roles = "Administrador")] [HttpGet] [Route("SoAdministrador")] public string SoAdministrador() { return "Usuário Administrador."; }

Executando o projeto novamente e navegando até o método SoAdminstrador com a passagem do token, podemos observar que o usuário pertence a role Administrador, conforme a Figura 19.

Figura 19. Mensagem de Teste de usuário administrador.

Analogamente ao exemplo com o ASP.NET MVC, podemos criar filtros de autorização; e para exemplificar este fato, vamos criar um filtro chamado ClaimsAuthorizeAttribute e um método chamado TesteFilter decorado com este Filter, conforme as Listagens 28 e 29.

Listagem 28. Filter que valida se o usuário e Administrador e Brasileiro.

public class ClaimsAuthorizeAttribute : AuthorizeAttribute { public ClaimsAuthorizeAttribute() { } public override void OnAuthorization(System.Web.Http.Controllers.HttpActionContext filterContext) { var user = HttpContext.Current.User as ClaimsPrincipal; if (user.Claims.Where(c => c.Type == ClaimTypes.Country) .Any(x => x.Value == "Brasil") && user.IsInRole("Administrador")) { base.OnAuthorization(filterContext); } else { base.HandleUnauthorizedRequest(filterContext); } } }

Listagem 29. Método decorado com o Filter ClaimsAuthorizeAttribute.

[ClaimsAuthorizeAttribute] [HttpGet] [Route("TesteFilter")] public string TesteFilter() { return "Usuário passou no filtro."; }

Note pelas listagens anteriores que somente usuários Brasileiros e Administradores serão capazes de acessar o método TestarFilter e, como o usuário logado contém estas Claims, logo ele terá acesso, conforme a Figura 20.

Figura 20. Mensagem de teste do Filtro.

Este artigo mostrou apenas uma pequena parte do que é possível fazer com Claims utilizado o ASP.NET Identity e Owin. A web está evoluindo e novos padrões de autorização e autenticação estão surgindo, e a Microsoft pensando nisso, disponibilizou este framework para facilitar a vida do desenvolvedor. Espero que tenham gostado e até mais.

Bibliografia

[1] Introdução ao ASP.NET Identity
http://www.asp.net/identity/overview/getting-started/introduction-to-aspnet-identity

[2] Autenticação com ASP.NET Web API
http://www.asp.net/web-api/overview/security/external-authentication-services

Artigos relacionados