Fala Galera,
Hoje vamos falar sobre como podemos melhorar a segurança do nosso Web API utilizando OAuth JSON Web Token (JWT). Porém primeiro precisamos entender o que é o JSON Web Token (JWT) e porque necessitamos de utilizar o JWT.
Gostaria de fazer uma implementação do JWT com o .NET Core 3.0 utilizando Identity Server v4 e EntityFramework Core? Não perca a versão mais recente deste conteúdo, clique aqui e leia o post Implementando OAuth JSON Web Token com OWIN no ASP.NET Web API – (ASP.NET Identity + Identity Server v4)
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
Utilizando o JSON Web Token no ASP.NET Web API e OWIN Middleware
Não há um suporte direto para a criação de JSON Web Token no ASP.NET Web API sendo assim para que possamos criar nossos Tokens precisamos implementar a interface ISecureDataFormat.
Para consumir um JSON Web Token porém já existe um pacote chamado “Microsoft.Owin.Security.Jwt” que entende e deserializa os Tokens com pouca linhas de código, sendo assim a maior parte do trabalho é a codificação do servidor de autenticação.
Então vamos ao código!!!
Criando um Servidor de Autenticação Web Api
Vamos criar um novo Projeto Web API eu coloquei com o nome de “JWT” mas sinta-se a vontade para usar um nome de sua preferencia =]
Para se criar um servidor de autenticação precisamos adicionar alguns pacotes, eles são encontrados no NuGet então nossa vida fica muito mais fácil.
Com o projeto criado vamos ao Package Manager Console e adicione os seguintes pacotes:
- Install-Package Microsoft.Owin.Cors
- Install-Package System.IdentityModel.Tokens.Jwt -Version 4.0.2.206221351
- Install-Package Thinktecture.IdentityModel.Core
Cada pacote tem sua responsabilidade dentro do processo de criação do Token JWT . O pacote System.IdentityModel.Tokens.Jwt ele é responsável por validar, parsear e gerar os tokens enquanto o Thinktecture.IdentityModel.Core será útil quando formos precisar assinar os Tokens
Vamos abrir o arquivo Startup.Auth.cs e adicionar o código abaixo:
using System; using System.Collections.Generic; using System.Linq; using Microsoft.AspNet.Identity; using Microsoft.AspNet.Identity.EntityFramework; using Microsoft.Owin; using Microsoft.Owin.Security.Cookies; using Microsoft.Owin.Security.Google; using Microsoft.Owin.Security.OAuth; using Owin; using JsonWebToken.Providers; using JsonWebToken.Models; namespace JsonWebToken { public partial class Startup { public void ConfigureAuth(IAppBuilder app) { OAuthAuthorizationServerOptions authServerOptions = new OAuthAuthorizationServerOptions() { //Em produção se atentar que devemos usar HTTPS AllowInsecureHttp = true, TokenEndpointPath = new PathString("/oauth2/token"), AccessTokenExpireTimeSpan = TimeSpan.FromMinutes(30), Provider = new CustomOAuthProvider(), AccessTokenFormat = new CustomJwtFormat("http://localhost") }; app.UseOAuthAuthorizationServer(authServerOptions); } } }
Pronto configuramos nosso servidor de autenticação JWT, porém nosso projeto ainda não está compilando precisamos implementar as classes CustomOAuthProviderJwt e CustomJwtFormat.
Então vamos ao trabalho.
Vamos primeiro criar a classe CustomOAuthProviderJwt, com a classe criada adicione o código abaixo:
using Microsoft.Owin.Security; using Microsoft.Owin.Security.OAuth; using System.Collections.Generic; using System.Security.Claims; using System.Threading.Tasks; namespace JsonWebToken { internal class CustomOAuthProviderJwt : OAuthAuthorizationServerProvider { public override Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context) { string clientId = string.Empty; string clientSecret = string.Empty; string symmetricKeyAsBase64 = string.Empty; if (!context.TryGetBasicCredentials(out clientId, out clientSecret)) { context.TryGetFormCredentials(out clientId, out clientSecret); } if (context.ClientId == null) { context.SetError("invalid_clientId", "client_Id não pode ser nulo"); return Task.FromResult<object>(null); } context.Validated(); return Task.FromResult<object>(null); } public override Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context) { context.OwinContext.Response.Headers.Add("Access-Control-Allow-Origin", new[] { "*" }); //FAKE FAZER A VALIDAÇÃO NO BANCO DE DADOS if (context.UserName != context.Password) { context.SetError("invalid_grant", "Usuário ou senha invalidos"); return Task.FromResult<object>(null); } var identity = new ClaimsIdentity("JWT"); identity.AddClaim(new Claim(ClaimTypes.Name, context.UserName)); identity.AddClaim(new Claim("sub", context.UserName)); identity.AddClaim(new Claim(ClaimTypes.Role, "Administrator")); //PEGAR AS ROLES CORRETAS var props = new AuthenticationProperties(new Dictionary<string, string> { { "audience", (context.ClientId == null) ? string.Empty : context.ClientId } }); var ticket = new AuthenticationTicket(identity, props); context.Validated(ticket); return Task.FromResult<object>(null); } } }
Ainda falta implementar a classe CustomJwtFormat, essa classe será responsável por gerar os Tokens JWT então vamos adicionar o código abaixo na classe.
using Microsoft.Owin.Security; using Microsoft.Owin.Security.DataHandler.Encoder; using System; using System.Collections.Generic; using System.IdentityModel.Tokens; using System.Linq; using System.Web; using Thinktecture.IdentityModel.Tokens; namespace JsonWebToken { public class CustomJwtFormat : ISecureDataFormat<AuthenticationTicket> { private readonly string _issuer = string.Empty; private readonly string Base64Secret = "IxrAjDoa2FqElO7IhrSrUJELhUckePEPVpaePlS_Xaw"; public CustomJwtFormat(string issuer) { _issuer = issuer; } public string Protect(AuthenticationTicket data) { if (data == null) throw new ArgumentNullException("data"); string symmetricKeyAsBase64 = Base64Secret; var keyByteArray = TextEncodings.Base64Url.Decode(symmetricKeyAsBase64); var signingKey = new HmacSigningCredentials(keyByteArray); var issued = data.Properties.IssuedUtc; var expires = data.Properties.ExpiresUtc; var token = new JwtSecurityToken(_issuer, null, data.Identity.Claims, issued.Value.UtcDateTime, expires.Value.UtcDateTime, signingKey); var handler = new JwtSecurityTokenHandler(); var jwt = handler.WriteToken(token); return jwt; } public AuthenticationTicket Unprotect(string protectedText) { throw new NotImplementedException(); } } }
Tudo pronto na nossa implementação de JWT para testar usarei o Postman para enviar as requisições e gerar um Token de acesso.
Veja na imagem abaixo:
Geramos nosso Token como podem ver na resposta abaixo:
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1bmlxdWVfbmFtZSI6ImFkbWluIiwic3ViIjoiYWRtaW4iLCJyb2xlIjoiQWRtaW5pc3RyYXRvciIsImlzcyI6Imh0dHA6Ly9sb2NhbGhvc3Q6MTc0MS8iLCJleHAiOjE0NjMxMDQ3MjMsIm5iZiI6MTQ2MzEwMjkyM30.kvO-ltZg7iB5WdowjD_YIilHVBDPiU6RM1a8RqYnb6c
Notem que existem um parâmetro chamado client_id, esse parâmetro é um Hash Code aleatório. A principal função dele é validar o acesso ao recursos, ou seja, mesmo que o usuário e senha estejam corretos, temos que validar se a origem da requisição é válida. Então resumindo, sempre que alguém quiser nossa Api deverá ser cadastrada na base de dados e geraremos uma chave de acesso para ele. Essa chave de acesso é o client_id.
Vamos validar se nosso Token realmente foi gerado de forma correta.
Existe um programa online chamado jwt.io, ele faz a verificação online de Token JWT.
Veja na imagem abaixo a validação do nosso Token
Como podem ver, nosso Token está com a assinatura inválida. Para corrigir vamos pegar a chave de assinatura que usamos para gerar o Token e colocar na caixa de texto com o nome Secret. Agora nosso Token está com a assinatura válida, veja na imagem abaixo
Gerando AccessKey e ClientId dinamicamente.
Para gerar nosso client_id, ou seja, a chave que dará acesso a nossa API devemos criar implementação de geração de Token, ai vem a pergunta, porque deveríamos gerar ClientId e um AccessKey se o usuário e senha me basta ?
A resposta é simples, como vamos garantir que somente dispositivos ou programas autorizados podem ter acesso a nossa API ? Então imagine o cenário no qual nossa API é acessado por um APP que construirmos, temos que garantir que somente nosso APP seja autorizado a acessar esses recursos e é ai que entra o AccessKey e ClientId.
Vamos criar nossa classe que controlará a geração do AccessKey e ClientId. Adicione uma nova classe chamada WebApplicationAccess e adicione o código abaixo. Notem que no seu construtor estático já estou adicionando um acesso para facilitar nossos testes.
public class WebApplicationAccess { public String ClientId { get; set; } public String AccessKey { get; set; } public String SecretKey { get; set; } public String ApplicationName { get; set; } public static ConcurrentDictionary<string, WebApplicationAccess> WebApplicationAccessList = new ConcurrentDictionary<string, WebApplicationAccess>(); static WebApplicationAccess() { WebApplicationAccessList.TryAdd("e84a2d13704647d18277966ec839d39e", new WebApplicationAccess() { AccessKey = "CgP7NyLXtaGmyOgjj3sUMwmAlrSKqa5JyZ4P1OlfQeM", SecretKey = "UTboSKRUb13ZmztLtyB0W4BN37ndx_aK8__ry9sxfv8", ApplicationName = "ApplicationTests", ClientId = "e84a2d13704647d18277966ec839d39e" }); } public static WebApplicationAccess GrantApplication(string name) { var clientId = Guid.NewGuid().ToString("N"); var key = new byte[32]; RNGCryptoServiceProvider.Create().GetBytes(key); var base64Secret = TextEncodings.Base64Url.Encode(key); var accessKey = new byte[32]; RNGCryptoServiceProvider.Create().GetBytes(key); var accessKeyText = TextEncodings.Base64Url.Encode(key); WebApplicationAccess newWebApplication = new WebApplicationAccess { ClientId = clientId, SecretKey = base64Secret, AccessKey = accessKeyText, ApplicationName = name }; WebApplicationAccessList.TryAdd(clientId, newWebApplication); return newWebApplication; } public static WebApplicationAccess Find(string clientId) { WebApplicationAccess webApplication = null; if (WebApplicationAccessList.TryGetValue(clientId, out webApplication)) { return webApplication; } return null; } }
Nossa classe que irá gerenciar os acessos a nossa API está criada.
Vamos adicionar um WebApi chamado WebApplicationAccessController para criar nosso Token. Esse WebApi Controller só tem um recurso que é para criar as chaves de acesso para a nossa API, obviamente por motivos de exemplo ele não irá gravar no banco de dados mas na vida real iríamos gravar na base de dados e assim consultar posteriormente.
Vamos ao código.
public class WebApplicationAccessController : ApiController { // POST api/<controller> public IHttpActionResult Post([FromBody] string applicationName) { var webApplication = WebApplicationAccess.GrantApplication(applicationName); return Ok<WebApplicationAccess>(webApplication); } }
Com tudo criado, devemos agora alterar nossa classe chamada CustomOAuthProviderJwt para validar o ClientId e o AccessKey.
Validando o ClientId e AccessKey no CustomOAuthProviderJwt
Vamos alterar o CustomOAuthProviderJwt para validar o ClientId e o AccessKey. Devemos alterar o trecho no qual validamos o ClientId. Esse método é o ValidateClientAuthentication. Vamos ao código do CustomOAuthProviderJwt.
internal class CustomOAuthProviderJwt : OAuthAuthorizationServerProvider { public override Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context) { string clientId = string.Empty; string clientSecret = string.Empty; string symmetricKeyAsBase64 = string.Empty; if (!context.TryGetBasicCredentials(out clientId, out clientSecret)) { context.TryGetFormCredentials(out clientId, out clientSecret); } if (context.ClientId == null) { context.SetError("invalid_clientId", "client_Id não pode ser nulo"); return Task.FromResult<object>(null); } //Procurando pelo Client Id var token = context.ClientId.Split(':'); var client_id = token.First(); var accessKey = token.Last(); var applicationAccess = WebApplicationAccess.Find(client_id); if (applicationAccess == null) { context.SetError("invalid_clientId", "client_Id não encontrado"); return Task.FromResult<object>(null); } if (applicationAccess.AccessKey != accessKey) { context.SetError("invalid_clientId", "access key não encontrado ou inválido"); return Task.FromResult<object>(null); } context.Validated(); return Task.FromResult<object>(null); } public override Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context) { context.OwinContext.Response.Headers.Add("Access-Control-Allow-Origin", new[] { "*" }); //FAKE FAZER A VALIDAÇÃO NO BANCO DE DADOS if (context.UserName != context.Password) { context.SetError("invalid_grant", "Usuário ou senha invalidos"); return Task.FromResult<object>(null); } var identity = new ClaimsIdentity("JWT"); identity.AddClaim(new Claim(ClaimTypes.Name, context.UserName)); identity.AddClaim(new Claim("sub", context.UserName)); identity.AddClaim(new Claim(ClaimTypes.Role, "Administrator")); //PEGAR AS ROLES CORRETAS var props = new AuthenticationProperties(new Dictionary<string, string> { { "audience", (context.ClientId == null) ? string.Empty : context.ClientId } }); var ticket = new AuthenticationTicket(identity, props); context.Validated(ticket); return Task.FromResult<object>(null); }
Nossa classe CustomOAuthProviderJwt agora valida o ClientId e o AccessKey. Porém ainda temos mais trabalho a fazer, devemos alterar o CustomJwtFormat para assinar o Token por ClientId. Vamos alterar o método Protect, adicione o código abaixo:
public class CustomJwtFormat : ISecureDataFormat<AuthenticationTicket> { private readonly string _issuer = string.Empty; public CustomJwtFormat(string issuer) { _issuer = issuer; } public string Protect(AuthenticationTicket data) { if (data == null) throw new ArgumentNullException("data"); string audience = data.Properties.Dictionary["audience"]; if (string.IsNullOrWhiteSpace(audience)) throw new InvalidOperationException("ClientId e AccessKey não foi encontrado"); var keys = audience.Split(':'); var client_id = keys.First(); var accessKey = keys.Last(); var applicationAccess = WebApplicationAccess.Find(client_id); var keyByteArray = TextEncodings.Base64Url.Decode(applicationAccess.SecretKey); var signingKey = new HmacSigningCredentials(keyByteArray); var issued = data.Properties.IssuedUtc; var expires = data.Properties.ExpiresUtc; var token = new JwtSecurityToken(_issuer, client_id, data.Identity.Claims, issued.Value.UtcDateTime, expires.Value.UtcDateTime, signingKey); var handler = new JwtSecurityTokenHandler(); var jwt = handler.WriteToken(token); return jwt; } public AuthenticationTicket Unprotect(string protectedText) { throw new NotImplementedException(); } }
Agora vamos testar novamente. Notem que agora temos que passar no paramêtro client_id, o ClientId e o AccessKey separado por dois pontos (:). Vamos voltar ao Postman.
Pronto nosso Token foi gerado com sucesso. Vamos valida-ló no jwt.io
Notem agora que no Payload agora temos a propriedade aud essa propriedade está preenchida com o ClientId que está obtendo o Token também temos mais uma informação o Token foi assinado pelo Secret Key do ClientId, assim, cada ClientId tem seu próprio SecretKey tornando assim nosso Token mais seguro.
Configurando nossa Aplicação MVC para usar Bearer Token.
Com nossa implementação de Token toda feita, devemos configurar nossa aplicação usá-lo Token de Autenticação.
Adicione o seguinte pacote através do Package Manager Console
- Install–Package Microsoft.Owin.Security.Jwt
Depois do pacote adicionado, vamos abrir Startup.Auth.cs e adicione o código abaixo.
public partial class Startup { // For more information on configuring authentication, please visit http://go.microsoft.com/fwlink/?LinkId=301864 public void ConfigureAuth(IAppBuilder app) { OAuthAuthorizationServerOptions authServerOptions = new OAuthAuthorizationServerOptions() { //Em produção se atentar que devemos usar HTTPS AllowInsecureHttp = true, TokenEndpointPath = new PathString("/oauth2/token"), AccessTokenExpireTimeSpan = TimeSpan.FromMinutes(30), Provider = new CustomOAuthProviderJwt(), AccessTokenFormat = new CustomJwtFormat("http://localhost:1741/") }; app.UseOAuthAuthorizationServer(authServerOptions); var issuer = "http://localhost:1741/"; var audience = WebApplicationAccess.WebApplicationAccessList.Select(x => x.Value.ClientId).AsEnumerable(); var secretsSymmetricKey = (from x in WebApplicationAccess.WebApplicationAccessList select new SymmetricKeyIssuerSecurityTokenProvider(issuer, TextEncodings.Base64Url.Decode(x.Value.SecretKey))).ToArray(); app.UseJwtBearerAuthentication( new JwtBearerAuthenticationOptions { AuthenticationMode = AuthenticationMode.Active, AllowedAudiences = audience, IssuerSecurityTokenProviders = secretsSymmetricKey }); } }
No código acima estamos pegando todos os ClientId registrados com seus Secret Keys assim o servidor de Api será capaz de validar o Token gerado.
Com essa alteração podemos utilizar a anotação [Authorize] nos nossos WebApi, caso o Token não seja válido o acesso ao recurso será negado.
Vamos criar um WebApi básico para fazer nossos testes, vamos ao código:
[Authorize] public class ValuesController : ApiController { // GET api/values public IEnumerable<string> Get() { return new string[] { "value1", "value2" }; } // GET api/values/5 public string Get(int id) { return "value"; } // POST api/values public void Post([FromBody]string value) { } // PUT api/values/5 public void Put(int id, [FromBody]string value) { } // DELETE api/values/5 public void Delete(int id) { } }
Agora vamos voltar no Postman para fazer as requisições de testes. Primeiro devemos gerar o Token.
Com o Token gerado, devemos passar ele no Header do Request para chamar nosso recurso. No cabeçalho Header o nome da chave que iremos utilizar será o Authorization.
Passando o Token correto conseguimos acessar o recurso tranquilamente, agora vamos testar o caso de exceção, iremos passar um Token inválido. O servidor deverá retorna um erro 403 de acesso não autorizado.
Como podem ver, o servidor não aceitou a requisição ao recurso e retornou um erro de acesso não autorizado. Agora vamos testar não passando o Token de acesso, o servidor deverá retornar erro de acesso não autorizado.
O servidor também não autorizou o acesso ao recurso pois não passamos o Token de acesso.
Assim chegamos ao fim do post, usando JSON Web Token (JWT) facilita e padroniza a forma de separar o servidor de autorização e o servidor de recursos.
Podemos usar o JWT para criar serviços RestFul seguros e compartilhados de formas seguras entre os clientes que usam os serviços.
O código fonte deste post se encontra no GitHub através deste link.
Não deixem de comentar.
Abs e até o próximo.
Muito bom, Rafael!
Vejo que, infelizmente, ainda existem muitos desenvolvedores ignorando a importância da segurança quando trabalham em uma aplicação. Acho que posts como esse podem ajudar por abordar da explicação teórica até a implementação.
Muito bom!
Valeu, Eduardo.
Espero realmente que meus posts ajudem de alguma forma a mudar esse pensamento.
Abs.,
Parabéns pelo post! Vale a pena fazer um video. Muito obrigado 😉
Realmente, estou considerando esta hipótese
Muito obrigado pela visita
Abs.,
Rafael,
Muito bom o post eu implementei uma Aplicação com este tipo de configuração no inicio do mês, mas não utilizei apenas o Oauth com OWIN, pois não gostei do Json web token pois ele é apenas um json em base 64 que é facilmente convertido
Mas fiquei com uma dúvida neste modelo é possível ter um token de refresh ? pois na minha implementação é gerado um access_token e refresh_token evitando a necessidade de quem está consumindo enviar o usuário a senha quando o token expira
Oi Paulo,
Sim e possível gerar o refresh token sim, na configuração do JWT no Start.Auth.cs temos que configurar a geração de refresh_token.
Depois temos que alterar a forma de geração do token no JWTCustomFormat.
Abs.,
Seu artigo é ótimo, mas gostaria de deixar a dica de gerar uma vídeo aula, ira ser bem interessante também.
Muito Obrigado, Paulo.
Então estava com um canal no youtube mas por questão profissionais acabei não postando mais videos.
Tento no meu post passar o passo a passo de um forma clara.
Abs.,
Rafael,
JWT são diferentes dos tokens gerados pela próprio oAuth no .net ?
Na verdade não. Tokens JWT estão contido no modelo de autenticação do OAuth e não é exclusivo da linguagem .NET a geração do token e sim pelo modelo de autenticação OAuth.
Existe uma RFC 7519 (https://tools.ietf.org/html/rfc7519) que trata justamento disso sobre o modelo de autenticação JWT com OAuth,
Abs e obrigado pela visita
Muito bom o artigo. Uma observação, está ocorrendo um erro no CustomJwtFormat
Error CS1503 Argument 6: cannot convert from ‘Thinktecture.IdentityModel.Tokens.HmacSigningCredentials’ to ‘Microsoft.IdentityModel.Tokens.SigningCredentials
Se este erro está acontecendo pode ser pq vc está usando a ultima versão do nuget. System.IdentityModel.Tokens.Jwt
Portando o procedimento é desinstalar o System.IdentityModel.Tokens.Jwt e instalar o da versão 4.0.2.206221351
Segue abaixo:
Uninstall “System.IdentityModel.Tokens.Jwt”
Install-Package System.IdentityModel.Tokens.Jwt -Version 4.0.2.206221351
Oi Alexandre,
Muito obrigado pela visita.
Realmente está acontecendo esse problema.
Vou verificar e corrigir.
Abs.,
Boa noite Rafael, gostaria de tirar uma dúvida, tem como controlar os tokens gerados por usuário por exemplo: cada usuário só poderá ter um token ativo, se ele gerar um segundo token o anterior é apagado. Não estou entendo como ele mantém esses tokens ativos ou como posso acessar eles.
Oi Adriano,
Cada token é gerado por usuário, ou seja, o usuário somente terá um token ativo, caso o token esteja expirado o proprio sistema gera um automaticamente.
Para melhor entendimento, o token é o como se fossem uma sessão do usuário no ASP.NET, aonde cada usuário somente tem uma sessão valida e caso ela se expire ele tem que se logar novamente.
Para acessar os token podemos usar dentro do WEbAPI a propriedade User que fica dentro do APIController. Lá contém as informações de acesso do usuário.
Abs e obrigado pela visita
Não compreendi onde o servidor salva os tokens. Vejo a aplicação gerar os tokens mas não vejo eles serem salvos .
Oi Greidimar,
O Servidor não salva o token, o que ele faz é validar a sua assinatura baseados em algoritmos como HMAC256.
Abs.,
Rafael Cruz
“Então imagine o cenário no qual nossa API é acessado por um APP que construirmos, temos que garantir que somente nosso APP seja autorizado a acessar esses recursos e é ai que entra o AccessKey e ClientId.”
De acordo com uma resposta no fórum “Engenharia de Software” (em inglês) do grupo StackExchange (http://softwareengineering.stackexchange.com/a/219041), isso que você tentou fazer para bloquear sua API de “acesso indevido por apps terceiros” é IMPOSSÍVEL de se conseguir, em resumo devido à possibilidade de realização de engenharia reversa do app, de modo que qualquer pessoa com suficiente habilidade técnica e suficiente interesse poderia descobrir exatamente o que você colocou no código e arquivos de configuração do seu app, e assim efetuar a mesma ação quando acessando sua API, que no final pensaria que está recebendo uma requisição do seu app, quando na verdade a requisição foi proveniente de uma “cópia” do seu app (ou pelo menos da requisição que seu app faz ao seu serviço ou web api).
Fora isso, gostei muito do artigo. Bastante abrangente e completo. Estarei usando como base para a criação de um serviço de autenticação/autorização em um sistema que estou desenvolvendo com ASP.NET Web Api. Obrigado por compartilhar.
Entendo e concordo, a partir do momento que se descobre o secret key e impossivel de evitar, e isso acontece com todas as API uma exemplo claro é a api do Facebook caso voce descubra o Secret Key e Access Key você terá todas as informações necessarias para acessar a API.
Obrigado pela visita
Abs.,
“Então imagine o cenário no qual nossa API é acessado por um APP que construirmos, temos que garantir que somente nosso APP seja autorizado a acessar esses recursos e é ai que entra o AccessKey e ClientId.”
Um invasor que faça um ataque Man in the middle, não poderia obter AccessKey e ClientId no momento do envio?
Sim ele poderia descobrir o accessKey e o clientId, uma maneira de evitar isso seria criptografar essas dados de envio com um hash utilizando um salt para a variação do hash e no servidor web fazer a comparação do hash. Acho que isso ajudaria.
Abs.,
Caro Rafael,
Você conseguiu corrigir o erro? Uninstall “System.IdentityModel.Tokens.Jwt”
Install-Package System.IdentityModel.Tokens.Jwt -Version 4.0.2.206221351
Oi Sergio,
Corrigi sim e muito obrigado pela informação também fiz a alteração no post para adicionar o pacote com a referencia a esta versão.
abs.,
Rafael, abaixo o erro
Error CS1503 Argument 6: cannot convert from ‘Thinktecture.IdentityModel.Tokens.HmacSigningCredentials’ to ‘Microsoft.IdentityModel.Tokens.SigningCredentials
Oi Sergio,
Para ajustar esse erro temos que instalar o pacote System.IdentityModel.Tokens.Jwt na sua versão 4.0.2.206221351, como você mesmo mencionou.
Também fiz uma correção no post para assim referenciar esse pacote na sua versão correta.
Abs.,
Bom dia Rafael.
Eu implementei o servidor de autenticação, e coloquei minha aplicação para se logar e consumir o Json… Pelo Postman consigo passar o “Authorization” para meu controller que esta com a anotação [Authorize], funciona tudo perfeitamente…. porém não encontrei uma forma de passar esse header em um redirecttoaction (um redirecionamento de pagina) chamando uma nova ação de um controller. Será que seria possível fazê-lo????
Oi Schmidt,
É possível sim fazer isso, você antes do chamar o redictiontoaction deve usar o objeto response e adicionar o Header Authorization e o seu valor.
Assim deve funcionar de boa =]
Abs e obrigado pela visita
Bom dia Rafael…
Então, eu tentei dessa forma, porém pelo que entendi, quando você redireciona uma action, ela recarrega, e o que foi passado nos headers se perdem… não consegui fazer funcionar, se não fosse pedir de mais, se desse para você testar no seu projeto… só para ver se é algo errado no meu… 😀
Fico grato!!!
Bom dia Rafael,
Fiz esta implementação e funcionou tudo normal pelo postman. Porém, quando implementei no client usando jquery Ajax, não consegui carregar no context os dados de clientID, username e password. Segue minha chamada no jquery:
$.ajax({
global: false,
type: ‘POST’,
url: HelperJS.getBaseURL(‘oauth2/token’) ,
cache: false,
headers: {
‘Content-type’: ‘application/x-www-form-urlencoded’
},
data: ‘username=’ + $scope.userName + ‘&password=’ + $scope.password,
success: function (data) {
fnSuccess(data);
},
error: function (jqXHR, textStatus, errorThrown) {
HelperJS.showError
}
});
Se conseguir me ajudar eu agradeço.
Oi Alexandre,
Você deve estar esquecendo de passar algum parâmetro, senão me engano o grant_type=password
Acredito que esse seja o problema.
abs.,
Boa tarde, é possível realizar esse Oauth junto a autenticação do facebook?
Oi Wallace, não a própria autenticação do facebook é via oauth, seria como se tivesse sobreposição de autenticação.
o próprio facebook já usa esse modelo de oauth para a sua autenticação via login e a geração de token seria também diferente já que você deveria saber qual foi a criptografia usada para gerar token iguais.
Abs
Obrigado por compartilhar!!
Você teria um exemplo de como armazenar e recuperar o token do cookie?
Oi Tiago,
Obrigado pela visita! Não tenho um exemplo não, mas não seria tão complicado, basta no retorno gravar o token no cookie em javascript.
Talvez esse post do stackoverflow possa te dar um caminho.
https://stackoverflow.com/questions/4825683/how-do-i-create-and-read-a-value-from-cookie
Abs.,
Fala rafael!
Muito bom seu post. Estou com uma duvida, por que devo usar o formato x-www-form-urlencoded no Postman e se teria como eu passar no formato application/json?
Olá André, pelo meu exemplo neste post eu demonstrei utilizando x-www-form-urlencoded porém você pode utilizar o application/json,
você deve modicar a forma que você lê os inputs para saber como buscar via json.
Abs.,
Caso meu ‘authorization-server’ e meu ‘resource-server’ não sejam os mesmos, eu teria que ‘duplicar’ a implementação em ambos?
Oi Pedro,
Não, cada um vai fazer o papel que lhe cabe, por exemplo no caso do authorization-server, você pegará a implementação de geração de token e validação do token no resource-server ele somente será capaz de enviar um token para o authorization-server e saber se está valido ou não.
Abs
Muito bom e explicado!
Deu tudo certo aqui.
Porém agora quero fazer a verificação com o banco de dados. Tem como fazer um exemplo usando essa base que criamos, porém agora buscando os dados no banco e salvando o token no banco também?
Abraço!
Fala Nilton,
Sobre o banco de dados não tenho um tutorial para esse exemplo, porém não faz muito sentido gravar um token no banco de dados já que o mesmo expira.
Abs.,
Estou tentando seguir o tutorial, gero o token porém não consigo autenticar, é possivel criar uma solução híbrida entre autenticação por sessão e token?
Estou tentando seguir o tutorial, gero o token porém não consigo autenticar, o token é gerado normalmente, consigo validar no jwt.io mas na aplicação sempre diz que nao tenho acesso, alguma ideia do q pode ser?
é possivel criar uma solução híbrida entre autenticação por sessão e token?
Olá Jonnison, muito obrigado pela visita!
Sim, com a técnica do refresh token, é possível simular uma sessão.
Sobre o problema do acesso, pode ser o scope entre o client e o token podem estar diferente, o segredo entre os dois devem está iguais também.
Já verificou essas partes?
Sou novo no C#, como verifico isso?
tem 2 lugares no seu codigo,
1 codigo q está gerando o TOKEN, e ele usa um SECRET para isso
e deve ter outro codigo para validar esse token, e deve ter o mesmo SECRET se nao nao funciona corretante.
The solution for Severity cannot convert from ‘Thinktecture.IdentityModel.Tokens.HmacSigningCredentials’ to ‘Microsoft.IdentityModel.Tokens.SigningCredentials’
var securityKey = new Microsoft.IdentityModel.Tokens.SymmetricSecurityKey(keyByteArray);
var signingCredentials = new Microsoft.IdentityModel.Tokens.SigningCredentials(
securityKey, SecurityAlgorithms.HmacSha256Signature);
var token = new JwtSecurityToken(_issuer, client_id, data.Identity.Claims, issued.Value.UtcDateTime, expires.Value.UtcDateTime, signingCredentials);
Ótimo post sobre o assunto, realmente me salvou no momento de autenticar com json webtoken.
Muito obrigado Pedro!
Estou escrevendo uma atualização com NET Core e Identity Server
Abs e obrigado pela visita
Pingback: [Updated] Implementando OAuth JSON Web Token com OWIN no ASP.NET Web API - (ASP.NET Identity + Identity Server v4) - Rafael Cruz