Emissão de Tokens no ASP.NET Web API


Em aplicações Web é comum o cliente precisar se autenticar para ter acesso à determinados recurso da aplicação. Quando o usuário se identifica para a aplicação, e se ela validar o mesmo, gravamos um cookie no navegador para que ele possa manter a autenticação válida durante a sua navegação (e enquanto o mesmo não expirar), evitando assim que ele tenha que se (re)autenticar toda cada vez que quer acessar um recurso protegido.

Quando estamos trabalhando com serviços, onde são outros sistemas que os consomem, precisamos autenticar a aplicação/usuário para que ele possa acessar os recursos que eles disponibilizam. Enquanto aplicações Web (que possuem uma interface) mantém um cookie, os serviços lidam de uma forma diferente, ou seja, recorrem à tokens para autenticar e identificar o chamador, e a partir deste momento, o mesmo deverá apresentar o token a cada vez que deseja acessar algum dos recursos expostos pelo serviço.

Ao invés de termos controllers e actions que servirão para esse tipo de atividade, podemos (e devemos) recorrer à alguns recursos que a própria tecnologia nos oferece. Quando optamos por hospedar e executar o ASP.NET Web API no OWIN, ele já traz alguns middlewares para autenticação, e entre eles, a possibilidade de utilizar o a tecnologia OAuth2 para guiar todo o processo de autenticação. O primeiro passo é instalar os seguintes pacotes (via Nuget): Microsoft.Owin.Host.SystemWeb, Microsoft.AspNet.WebApi.Owin e Microsoft.Owin.Security.OAuth.

Esses pacotes irão disponibilizar um conjunto de classes para trabalharmos com o OWIN dentro do ASP.NET Web API. O primeiro passo é realizar duas configurações dentro da classe Startup (exigência do OWIN): um middleware que será responsável pela geração de tokens e o outro que terá o papel de validar os tokens apresentados para o serviço antes deles serem executados.

public class Startup
{
    public void Configuration(IAppBuilder app)
    {
        var config = new HttpConfiguration();

        ConfigureAuthentication(app);
        WebApiConfig.Register(config);
        app.UseWebApi(config);
    }

    private static void ConfigureAuthentication(IAppBuilder app)
    {
        app.UseOAuthAuthorizationServer
        (
            new OAuthAuthorizationServerOptions()
            {
                AccessTokenExpireTimeSpan = TimeSpan.FromHours(1),
                AllowInsecureHttp = true,
                TokenEndpointPath = new PathString(“/issuer/token”),
                Provider = new OAuthAuthorizationServerProvider()
                {
                    OnValidateClientAuthentication = async ctx =>
                    {
                        await Task.Run(() => ctx.Validated());
                    },
                    OnGrantResourceOwnerCredentials = async ctx =>
                    {
                        await Task.Run(() =>
                        {
                            if (ctx.UserName != “Israel” || ctx.Password != “12345”)
                            {
                                ctx.Rejected();
                                return;
                            }

                            var identity = new ClaimsIdentity(
                                new[] {
                                        new Claim(ClaimTypes.Name, ctx.UserName),
                                        new Claim(ClaimTypes.Role, “Admin”)},
                                ctx.Options.AuthenticationType);

                            ctx.Validated(identity);
                        });
                    }
                }
            }
        );

        app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions());
    }
}

Através do método UseOAuthAuthorizationServer realizamos as configurações do emissor de tokens. As propriedades expostas pela classe OAuthAuthorizationServerOptions nos permite customizar diversas informações críticas para o processo de emissão. Entre elas temos o endpoint em que os clientes deverão visitar para gerar o token, o tempo de expiração para cada token e, por fim, e não menos importante, o provider. É o provider que podemos customizar a validação do usuário, verificando se ele é ou não um usuário válido. Para essa customização, basta herdar da classe OAuthAuthorizationServerProvider e sobrescrever (se assim desejar) diversos métodos para controlar todos os eventos gerados pelo protocolo OAuth2.

Para o exemplo estou optando por definir a propriedade AllowInsecureHttp como true, evitando a necessidade de expor o serviço sobre HTTPS, mas é importante que isso seja reavaliado antes de colocar em produção. Outro detalhe é que estou definindo, em hard-code, o nome e senha válidos; em um cenário real, é neste momento que deveríamos recorrer à algum repositório de usuários e validar se ele existe lá.

Depois de configurado o emissor de tokens, o middleware que habilitamos através do método UseOAuthBearerAuthentication é o responsável por validar os tokens que são enviados nas futuras requisições e identificar se eles são ou não válidos.

O fluxo necessário para a geração do token e para envio subsequente em novas requisições é bastante simples. Basta realizar um post para o endereço configurado na propriedade TokenEndpointPath, passando no corpo da mensagem três parâmetros: grant_type, username e password. Esses parâmetros serão utilizados pelo protocolo OAuth para validar o usuário e gerar o token caso ele seja válido. O parâmetro grant_type indica ao OAuth o tipo de autenticação que queremos fazer; neste caso o valor deverá ser “password”. Já os parâmetros username e password são autoexplicativos.

Se o usuário for válido, então o resultado será devolvido através do corpo da mensagem de resposta, formatado em JSON. Abaixo está um exemplo da resposta, mas o token foi reduzido por questões de espaço. O seu tamanho é muito maior que isso.

{
    “access_token”:”CPIA6Ha-9Bg2Yh8PZD-7Terzl9At…..UBp-WlpkNYn5ioD85U”,
    “token_type”:”bearer”,
    “expires_in”:3599
}

Agora compete à aplicação que consome este serviço armazenar o token e embutir nas futuras requisições. A exigência do protocolo é que o token seja incluído através do header Authorization na requisição, especificando além do token, também o seu tipo, que neste caso é bearer, e ele também é devolvido pelo emissor do token.

Para realizarmos os testes, vamos criar um controller e expor um método para que seja consumido apenas por usuários que estejam devidamente autenticados. Para isso, basta decorar o controller ou a ação com o atributo AuthorizeAttribute, conforme podemos visualizar no código abaixo:

public class TestController : ApiController
{
    [HttpGet]
    [Authorize]
    public string GetData()
    {
        return “Testing…”;
    }
}

Para exemplificar o consumo por parte do cliente, estou utilizando uma aplicação console para solicitar a emissão do token, e na sequência invocamos o método GetData da API passando o token como header, conforme o fluxo que foi explicado acima.

private async static void Invoke()
{
    using (var client = new HttpClient())
    {
        using (var tokenResponse = await client.PostAsync(“http://localhost:1195/issuer/token”, CreateContent()))
        {
            var tokenBody = await tokenResponse.Content.ReadAsStringAsync();
            dynamic parsedTokenBody = JsonConvert.DeserializeObject(tokenBody);

            using (var requestMessage = new HttpRequestMessage(HttpMethod.Get, “http://localhost:1195/api/Test/GetData”))
            {
                requestMessage.Headers.Authorization =
                    new AuthenticationHeaderValue(
                        parsedTokenBody.token_type.ToString(),
                        parsedTokenBody.access_token.ToString());

                using (var responseMessage = await client.SendAsync(requestMessage))
                {
                    var responseBody = await responseMessage.Content.ReadAsStringAsync();

                    Console.WriteLine(responseBody);
                }
            }
        }
    }
}

private static FormUrlEncodedContent CreateContent()
{
    return new FormUrlEncodedContent(new[]
    {
        new KeyValuePair<string, string>(“grant_type”, “password”),
        new KeyValuePair<string, string>(“username”, “Israel”),
        new KeyValuePair<string, string>(“password”, “12345”)
    });
}

Um detalhe importante é que dentro do controller, podemos fazer o (down)casting da propriedade User e chegar na instância da classe ClaimsPrincipal e, consequentemente, acessar o conjunto de claims que foi gerada pelo emissor do token para este usuário. Como disse anteriormente, claims estão em todo lugar.

((ClaimsPrincipal)this.User).Claims.First().Value;

Anúncios

7 comentários sobre “Emissão de Tokens no ASP.NET Web API

  1. Israel,

    há a possibilidade de ao instanciar a classe “OAuthAuthorizationServerOptions”, eu declarar que será utilizado um parâmetro dinâmico na propriedade “TokenEndpointPath”, pois minha autenticação precisa de um parâmetro que será utilizado na classe de provider e eu iria pegá-lo através da rota.

    Abraços,

    • Boas Felipe,

      Não sei se entendi. Essa configuração é feita logo nos primeiros estágios da inicialização da aplicação. Você está dizendo que este valor vem de onde?

  2. Pingback: Externalizando a Emissão de Tokens | Israel Aece
  3. Olá Israel,

    Muito bem explicado o artigo. Parabéns!! Eu estou com dúvida sobre como validar um token gerado pelo facebook ou google na minha web api. Tenho um app que fará a autenticação no facebook e deverá enviar o token gerado no facebook para a web api. Como posso fazer essa validação?

  4. Olá Israel,

    Muito bem explicado o artigo. Parabéns!! Eu estou com dúvida sobre como validar um token gerado pelo facebook ou google na minha web api. Tenho um app que fará a autenticação no facebook e deverá enviar o token gerado no facebook para a web api. Como posso fazer essa validação?

Deixe uma resposta

Preencha os seus dados abaixo ou clique em um ícone para log in:

Logotipo do WordPress.com

Você está comentando utilizando sua conta WordPress.com. Sair / Alterar )

Imagem do Twitter

Você está comentando utilizando sua conta Twitter. Sair / Alterar )

Foto do Facebook

Você está comentando utilizando sua conta Facebook. Sair / Alterar )

Foto do Google+

Você está comentando utilizando sua conta Google+. Sair / Alterar )

Conectando a %s