Facebook Pixel
Implementando Segurança – Bearer Token no .NET Core e Swagger

Implementando Segurança – Bearer Token no .NET Core e Swagger

Este artigo é parte da série:

Na maior parte das vezes, a melhor forma de garantir a segurança da nossa API será através da tramitação de um token gerado dinamicamente para identificar o usuário que está a está chamando e os direitos que ele possui. No caso do C#, a Microsoft tem até uma biblioteca inteira destinada a isso, que é o Identity Framework, mas não iremos falar dele neste momento e sim como tratar esses tokens.

O token poderia ser apenas mais um parâmetro da função a ser chamada, por exemplo:

public function int Soma (int ValorA, int ValorB, string Token) {...}

Os motivos pelos quais não se deve fazer desta forma serão descrirtos em outro artigo mas, por hora, vamos nos atentar ao fato de que o token deve ser transmitido dentro do HTTP header na diretiva “Authorization”. No exemplo abaixo, os HTTP headers de uma requisição tipo POST e conteúdo JSON, que é a grande maioria dos casos. Ainda a título de exemplo, vamos usar o token “123456”.

authorization: bearer <seu token>
accept: text/plain
content-type: application/json


Como Ler o HTTP Header no C#

É bem simples: basta colar a linha abaixo:

string token = HttpContext.Request.Headers["Authorization"].FirstOrDefault()?.Split(" ").Last();

Exemplo aplicado dentro de uma função cujo token tem que ser “123456”:

        /// <summary>
        /// Soma dois números
        /// </summary>
        /// <param name="ValorA">Primeiro valor a ser somado</param>
        /// <param name="ValorB">Segundo valor a ser somado</param>
        /// <returns>A soma do primeiro valor com o segundo valor</returns>
        [HttpPost("Soma")]
        public ActionResult<int> Soma(int ValorA, int ValorB)
        {
            string token = HttpContext.Request.Headers["Authorization"].FirstOrDefault()?.Split(" ").Last();
            if (token != "123456")
            {
                return Unauthorized($"Acesso não autorizado. Token: [{token}]");
            }
            int ret = ValorA + ValorB;
            return Ok(ret);
        }

Dessa forma, conseguimos com muita facilidade validar um token manualmente dentro de cada uma das funções, embora haja muitas formas bem mais sofisticadas de fazer isso.

Como Configurar o Swagger para Enviar o Token

Efetuando o procedimento acima, não mais será possível testar a função “Soma” no Swagger, uma vez que não teremos como enviar o token no HTTP header. Para que isso seja possível, faça a seguinte modificação no método AddSwaggerGen() em Program.cs:

using Microsoft.OpenApi.Models;
using System.Reflection;

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.
builder.Services.AddControllers();

// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen(options =>
{
    options.SwaggerDoc("v1", new OpenApiInfo
    {
        Version = "v1",
        Title = "Minha API",
        Description = "Descrição da minha API",
    });
    options.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme
    {
        Description = "Digite o token.",
        Name = "Authorization",
        In = ParameterLocation.Header,
        Type = SecuritySchemeType.ApiKey,
        Scheme = "Bearer"
    });
    options.AddSecurityRequirement(new OpenApiSecurityRequirement
    {
        {
            new OpenApiSecurityScheme
            {
                Reference = new OpenApiReference
                {
                    Type = ReferenceType.SecurityScheme,
                    Id = "Bearer"
                }
            },
            Array.Empty<string>()
        }
    });
});


Como Fazer a Autenticação a Nível Global

Até o momento estamos fazendo a autenticação manualmente dentro de cada função, porém podemos configurar a REST API para fazer a autenticação a nível global, desta forma não será necessário realizar este processo dentro de cada função.

A título de exemplo, farei aqui uma autenticação manual bem simples. Tudo o que faremos é verificar se o bearer token é igual a “123456”. Com este exemplo fica fácil de você implementar a sua própria lógica para cada programa.

app.UseRouting();
// Autenticação
app.Use(async (context, next) =>
{
    var endpoint = context.GetEndpoint();
    if (endpoint != null && endpoint.Metadata.GetMetadata<Microsoft.AspNetCore.Authorization.AuthorizeAttribute>() != null)
    {
        if (!context.Request.Headers.TryGetValue("Authorization", out var authHeader))
        {
            context.Response.StatusCode = 401;
            await context.Response.WriteAsync("Missing Authorization Header");
            return;
        }

        var token = authHeader.ToString().Split(' ').Last();
        if (token != "123456")
        {
            context.Response.StatusCode = 401;
            await context.Response.WriteAsync("Invalid Token");
            return;
        }

        var claims = new[] { new Claim(ClaimTypes.Name, "AuthenticatedUser") };
        var identity = new ClaimsIdentity(claims, "Bearer");
        var principal = new ClaimsPrincipal(identity);
        context.User = principal;
    }
    await next();
});
app.UseAuthentication();
app.UseAuthorization();

Feito isso, vai bastar adicionar a diretiva [Authorize] às funções que necessitam autenticação, no caso da rotina “Soma” que fizemos acima, ela ficaria assim:

        /// <summary>
        /// Soma dois números
        /// </summary>
        /// <param name="ValorA">Primeiro valor a ser somado</param>
        /// <param name="ValorB">Segundo valor a ser somado</param>
        /// <returns>A soma do primeiro valor com o segundo valor</returns>
        [HttpPost("Soma")]
        [Authorize]
        public ActionResult<int> Soma(int ValorA, int ValorB)
        {
            string token = HttpContext.Request.Headers["Authorization"].FirstOrDefault()?.Split(" ").Last();
            if (token != "123456")
            {
                return Unauthorized($"Acesso não autorizado. Token: [{token}]");
            }
            int ret = ValorA + ValorB;
            return Ok(ret);
        }

Os métodos que não tiverem a diretiva [Authorize] funcionarão de qualquer forma, sem validação do token de autenticação.

Exemplo Completo

Se você está acompanhando a série de artigos explicando como construir uma REST API do zero, nosso “Program.cs” ficou da seguinte forma:

using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Options;
using Microsoft.IdentityModel.Tokens;
using Microsoft.OpenApi.Models;
using System.Reflection;
using System.Security.Claims;
using System.Text;
using System.Text.Encodings.Web;
using Treino_REST_02.Models;

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.
builder.Services.AddControllers();

// Swagger
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen(opts =>
{
    opts.SwaggerDoc("v1", new OpenApiInfo
    {
        Version = "v1",
        Title = "Treino REST API",
        Description = "Como desenvolver um REST API utilizando C# e .NET Core",
        TermsOfService = new Uri("https://www.larsoft.com.br/autenticacao-em-2-fatores-2fa-nos-sistemas-larsoft/"),
        Contact = new OpenApiContact
        {
            Name = "Entre em contato com a Larsoft",
            Url = new Uri("https://www.larsoft.com.br/institucional/contato/")
        },
        License = new OpenApiLicense
        {
            Name = "Como a Larsoft trata a segurança de dados",
            Url = new Uri("https://www.larsoft.com.br/institucional/seguranca/")
        }
    });
    opts.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme {
        Name = "Authorization",
        Description = "Digite o token.",
        In = ParameterLocation.Header,
        Type = SecuritySchemeType.ApiKey,
        Scheme = "Bearer"
    });
    opts.AddSecurityRequirement(new OpenApiSecurityRequirement {
        {
            new OpenApiSecurityScheme
            {
                Reference = new OpenApiReference
                {
                    Type = ReferenceType.SecurityScheme,
                    Id = "Bearer"
                }
            },
            Array.Empty<string>()
        }
    });
    var xmlFile = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml";
    var xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFile);
    opts.IncludeXmlComments(xmlPath, true);
});

var app = builder.Build();

app.UseSwagger();
app.UseSwaggerUI();
app.UseHttpsRedirection();
app.UseRouting();

// Autenticação
app.Use(async (context, next) =>
{
    if (!context.Request.Headers.TryGetValue("Authorization", out var authHeader))
    {
        context.Response.StatusCode = 401;
        await context.Response.WriteAsync("Missing Authorization Header");
        return;
    }

    var token = authHeader.ToString().Split(' ').Last();
    if (token != "123456")
    {
        context.Response.StatusCode = 401;
        await context.Response.WriteAsync("Token inválido");
        return;
    }

    var claims = new[] { new Claim(ClaimTypes.Name, "AuthenticatedUser") };
    var identity = new ClaimsIdentity(claims, "Bearer");
    var principal = new ClaimsPrincipal(identity);
    context.User = principal;

    await next();
});
app.UseAuthentication();
app.UseAuthorization();

app.MapControllers();

app.UseDefaultFiles(new DefaultFilesOptions
{
    DefaultFileNames = new List<string> { "index.html" }
});
app.UseStaticFiles();

app.Run();

Agora, ao executar o Swagger, haverá um botão “Authorize” para que você possa entrar com o token.

Uma vez que tenha entrado com o token, todas as requisições feitas no Swagger daqui para frente, vão carregar o HTTP header “Authorization: Bearer <Token que você digitou>”.

Próxima etapa:

Deixe seu comentário