Neste artigo você aprenderá a implementar a segurança por Bearer Authentication em uma Web API do .NET Core 2.0. A solução de autenticação pode ser dividida em duas partes, há o Emissor de Tokens que é uma aplicação independente e os Clientes que são outras Web APIs que possui métodos que privados por autenticação, desta forma:

  1. Serviço de autenticação: uma Web API que recebe as credenciais do usuário para gerar Tokens de Autenticação.
  2. Serviço cliente: uma Web API que possui métodos restritos a usuários autenticados.

Conceitos importantes

  • Bearer Token (JWT – JSON Web Token): é um padrão da indústria para a representação de permissões entre dois sistemas de forma segura. Para o desenvolvedor é uma string codificada que carrega as Claims de acesso, essa string é parecida com isto:
  • O Token JWT codificado acima por der decodificado numa objeto JSON como este: 
  • Há na internet alguns sites que fazem essa codificação/decodificação de JWT.
  • Claims: são reinvidicações ou permissões atribuídas aos usuários. Podem ser Roles como “Administrador”, “Colaborador” ou propriedades de interesse do programador.

Como visto acima, o JWT é apenas uma string contendo as permissões de acesso e emitido por um “Sistema Autenticador”. E como fica a segurança disso, eu poderia editar o meu token e tentar ganhar acesso a um sistema? Não, pois há uma chave que apenas o “Sistema Autenticador” e os “Sistemas Clientes” conhecem para validar se o token está íntegro.

No exemplo a seguir, as chaves usadas para gerar e verificar os tokens são:

  • SecretKey:  321B9B8C-C785-4DCF-88F7-39DCD935BDE2
  • Issuer:  http://minhaempresa

Voltando aos conceitos. É importante saber que o Emissor do JWT pode ser uma aplicação separada dos outros sistemas que são protegidos pela segurança por Bearer Tokens.

E no código a seguir teremos duas aplicações Web API.

  • Auth Web API – Emite Tokens
  • Cliente Web API – Possui métodos protegidos por Bearer Tokens.

A Web API de Autenticação

Essa Web API possui um método GetJwtSecurityToken(…) que usa um algoritmo de criptografia simétrica para gerar os Tokens. O algoritmo HmacSha256 precisa de uma chave secreta para realizar a criptografia e descriptografia, eu tenho usado um texto como um Guid assim “321B9B8C-C785-4DCF-88F7-39DCD935BDE2”.

Outras informações que fazem parte do seu Token JWT são o Issuer e o Audience que podem ser um texto simples como “http://minhaempresa”.

Os métodos a seguir consideram a existência de uma classe chamada LoginCommand que possui as propridades Username/Password, você vai perceber que não estamos validando se as credenciais estão certas ou não (fica a cargo do nosso leitor implementar essa regra).

O código relevante para gerar um Bearer Token é este.

[AllowAnonymous]
[HttpPost]
public IActionResult Token([FromBody] LoginCommand loginCommand)
{
    //
    // TODO: Implementar uma verificação 
    // se loginCommand.Username e loginCommand.Password são válidos.
    //

    var token = GetJwtSecurityToken(loginCommand.Username);

    return Ok(new
    {
        token = new JwtSecurityTokenHandler().WriteToken(token),
        expiration = token.ValidTo
    });
}

private JwtSecurityToken GetJwtSecurityToken(string user)
{
    return new JwtSecurityToken(
        config.Issuer,
        config.Issuer,
        GetTokenClaims(user),
        expires: DateTime.UtcNow.AddDays(1),
        signingCredentials: new SigningCredentials(
            new SymmetricSecurityKey(Encoding.UTF8.GetBytes(config.SecretKey)),
            SecurityAlgorithms.HmacSha256)
    );
}

private static IEnumerable GetTokenClaims(string user)
{
    return new List
    {
        new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
        new Claim(JwtRegisteredClaimNames.Sub, user)
    };
}

A Web API Cliente

O JWT (Token) possui a característica de uma autenticação Offline, ou seja basta usar o mesmo algoritmo de criptografia e as mesma chaves para verificar se um Token é válido ou não. Dispensando a necessidade de comunicar com o Emissor para realizar este procedimento.

Dessa forma, o cliente deve implementar o mesmo algoritmo de criptografia, as mesmas chaves e usar os mesmos nomes para o Issuer e Audience.

Atenção: A sua chave é privada e deve ser guardada em segurança.

Vamos aos detalhes de implementação. Primeiro acrescente o serviço de autenticação no método ConfigureServices(…) assim:

services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddJwtBearer(options =>
    {
        options.SaveToken = true;
        options.RequireHttpsMetadata = false;
        options.TokenValidationParameters = new TokenValidationParameters()
        {
            ValidIssuer = Configuration.GetSection("Security").GetValue("Issuer"),
            ValidAudience = Configuration.GetSection("Security").GetValue("Issuer"),
            IssuerSigningKey = new SymmetricSecurityKey(
                Encoding.UTF8.GetBytes(
                    Configuration.GetSection("Security").GetValue("SecretKey")))
        };
    });

E no método Configure(…) especifique que a sua pipeline deve verificar pela autenticação:

app.UseAuthentication();

Se você estiver usando o Swagger você deve acrescentar isto no seu ConfigureServices(..) e um botão para autenticação será criado no canto superior direito:

services.AddSwaggerGen(options =>
{
    options.AddSecurityDefinition(
        "Bearer",
        new ApiKeyScheme()
        {
            In = "header",
            Description = "Please insert JWT with Bearer into field",
            Name = "Authorization",
            Type = "apiKey"
        });

    options.DescribeAllEnumsAsStrings();

    options.SwaggerDoc("v1", new Swashbuckle.AspNetCore.Swagger.Info
    {
        Title = "Blogging HTTP API",
        Version = "v1",
        Description = "The Blogging Service HTTP API",
        TermsOfService = "Terms Of Service"
    });
});

A partir de agora basta marcar os seus Controllers ou Actions com o atributo [Authorize] e eles só serão acessíveis se no Header da chamada estiver um Bearer Token.

Sobre a Autorização

O processo de autorização é feito nos clientes usando Policies. Pode ser feito da seguinte forma:

services.AddAuthorization(options =>
    {
        options.AddPolicy("WritersPolicy", policy =>
                            policy.RequireClaim(ClaimTypes.Role, "writer"));

        options.AddPolicy("ReadersPolicy", policy =>
                            policy.RequireClaim(ClaimTypes.Role, "reader"));
    });

Essas duas políticas de acesso permitem criar atributos Authorize neste modelo [Authorize(Policy = “WritersPolicy”)] nos Constrollers e Actions. Neste exemplo de autorização estamos considerando que o Emissor do JWT acrescentou uma role no token assim new Claim(ClaimTypes.Role, “writer”) ou assim new Claim(ClaimTypes.Role, “reader”) .

Se você deseja ver uma solução real que implementa Bearer Authentication acesse o Jambo no Github para DDD, Event Sourcing e Bearer Tokens.

Update 03/out/2017 15h: Acrescentei conceitos importantes.