ASP .NET Core MVC - Protegendo uma WEB API - VI


  Neste artigo vamos continuar a criação de uma WEB API usando a ASP .NET Core 3.0 para a seguir mostrar como consumir a API em uma aplicação ASP .NET Core MVC.

Continuando a quinta parte do artigo vou mostrar agora uma forma de proteger a nossa Web API usando chaves.

Até o momento a nossa Web API ApiReservas (criada na primeira parte do artigo) pode ser acessada por qualquer usuário anônimo.

Geralmente desejamos restringir o acesso a nossa API e existem muitas formas de proteger uma WEB API como:

Neste artigo eu vou mostrar como usar uma chave de API ou API Key.

O que é uma API Key ?

Uma chave de API é um pedaço de código atribuído a um programa, desenvolvedor ou usuário específico que é usado sempre que a entidade faz uma chamada para uma API. Essa chave geralmente é uma longa sequência de caracteres gerados que seguem um conjunto de regras de geração especificado pela autoridade que os cria. Exemplo:  TP84UTvzJKds1Jomx8gIbTXcEEJSUilGqpxCcmnx&yldoidjdç.

Após a criação da conta ou o registro do aplicativo, muitos provedores de API atribuem chaves de API a seus desenvolvedores, permitindo que funcionem de maneira semelhante a um nome de usuário e senha de conta. As chaves da API são únicas e, por isso, muitos provedores optaram por usá-las como um tipo de camada de segurança, impedindo a entrada e outros direitos a quem não puder fornecer a chave para o serviço solicitado.

De forma bem simples, o cliente terá que enviar a chave da API junto com o request, e, a chave será verificada no controlador da API e se estiver correta a resposta será enviada ao cliente.

Uma forma muito usada de enviar a chave para a API é usar o cabeçalho da requisição HTTP.

Antes de mostrar uma implementação bem simples de utilização dessa abordagem cabe destacar que ela não é recomendada para aplicações comerciais com dados sensíveis pois apresenta diversas vulnerabilidades.

No nosso exemplo vamos restringir o acesso a nossa API de forma que somente clientes autorizados poderão  alterar uma reserva e incluir uma reserva.

Ajustando o código da Web API - ApiReservas

Para isso vamos alterar os métodos Action Put e Post da nossa API conforme abaixo:

1- POST

         [HttpPost]
        public IActionResult Post([FromBody] Reserva res)
        {
        
   if (!Authenticate())
                return Unauthorized("401 - Não Autorizado");
            return Ok
(repository.AddReserva(new Reserva
            {
                Nome = res.Nome,
                InicioLocacao = res.InicioLocacao,
                FimLocacao = res.FimLocacao
            }));
        }

2- PUT

            [HttpPut]
        public IActionResult Put([FromForm] Reserva res)
        {
           
if (!Authenticate())
            {
                return Unauthorized("401 - Não Autorizado");
            }
            return Ok
(repository.UpdateReserva(res));
        }

Em ambos os métodos Action definimos um método Authenticate que irá retornar true ou false conforme a chave secrete for ou não válida. Se não for válida emitimos uma mensagem de '41 - Não Autorizado'.

Precisamos agora incluir o método Authenticate() no controlador ReservasController do projeto ApiResevas:

   bool Authenticate()
   {
       var chavesSecretas = new[] { "numsey@2019", "twly@wzpou#1pcmb8cnm45uz@m0yr@" };
       StringValues key = Request.Headers["Key"];
       int count = (from t in chavesSecretas where t == key select t).Count();
       return count == 0 ? false : true;
   }

Neste código estamos definindo as chaves secretas que estamos esperando. Veja que podemos definir mais de uma chave secreta.

Depois obtemos a chave secreta enviada pelo cliente no header do request obtendo o valor de ['key'].

A seguir verificamos se a chave enviada existe em nosso array de chaves secretas e retornamos false se não existir e true se a chave existir.

Ajustando o código do projeto Mvc_Reservas

Precisamos agora ajustar o código do controlador ReservasController do projeto Mvc_Reservas.

Vamos ajustar os métodos Action HttpPost AddReserva e HttpPost UpdateReserva :

1- HttpPost AddReserva

        [HttpPost]
        public async Task<IActionResult> AddReserva(Reserva reserva)
        {
            Reserva reservaRecebida = new Reserva();

            using (var httpClient = new HttpClient())
            {
                httpClient.DefaultRequestHeaders.Add("Key", "numsey@2018");
                StringContent content = new StringContent(JsonConvert.SerializeObject(reserva),
                                                 Encoding.UTF8, "application/json");

                using (var response = await httpClient.PostAsync(apiUrl, content))
                {
                    string apiResponse = await response.Content.ReadAsStringAsync();
                    if (
apiResponse.Contains("401"))
                    {
                        ViewBag.Result = apiResponse;
                        return View();
                    }
                    else
                    {
                        reservaRecebida = JsonConvert.DeserializeObject<Reserva>(apiResponse);
                    }

                }
            }
            return View(reservaRecebida);
        }

Neste código incluimos o header do request o valor da chave secreta. Para este exemplo estou incluindo uma senha incorreta - numsey@2018 - (a senha correta é numsey@2019). Assim poderemos testar quando o usuário não esta autorizado.

A seguir verificamos se no response existe o código 401, e, em caso positivo retornamos para a view e exibimos a mensagem.

2- HttpPost UpdateReseva

       [HttpPost]
        public async Task<IActionResult> UpdateReserva(Reserva reserva)
        {
            Reserva reservaRecebida = new Reserva();

            using (var httpClient = new HttpClient())
            {
                httpClient.DefaultRequestHeaders.Add("Key", "numsey@2019");

                var content = new MultipartFormDataContent();

                content.Add(new StringContent(reserva.ReservaId.ToString()), "ReservaId");
                content.Add(new StringContent(reserva.Nome), "Nome");
                content.Add(new StringContent(reserva.InicioLocacao), "InicioLocacao");
                content.Add(new StringContent(reserva.FimLocacao), "FimLocacao");

                using (var response = await httpClient.PutAsync(apiUrl, content))
                {
                    string apiResponse = await response.Content.ReadAsStringAsync();
                    if (
apiResponse.Contains("401"))
                    {
                        ViewBag.Result = apiResponse;
                        return View();
                    }
                    else
                    {
                        ViewBag.Result = "Reserva atualizada com Sucesso";
                        reservaRecebida = JsonConvert.DeserializeObject<Reserva>(apiResponse);
                    }

                }
            }
            return View(reservaRecebida);
        }

Neste código realizamos o mesmo procedimento que no método anterior. Aqui estamos incluindo no header a senha secreta correta.

Ajustando as Views para exibir a mensagem

Agora para poder exibir as mensagens ao usuário vamos ajustar as views AddReserva e UpdateReserve incluindo o código abaixo no fim do formulário:

<h3 class="alert">@ViewBag.Result</h3>

Pronto. Agora é só alegria...

Testando as opções para Incluir e Atualizar uma reserva obtems o resultado a seguir:

Nota: Para testar excute o projeto ApiReservas e a seguir o projeto Mvc_Reservas.

Temos assim a nossa implementação funcionando, e agora somente os usuários que enviarem a chave secreta no header do request pode incluir e alterar reservas.

Deixo aqui um alerta que esta abordagem não deve ser usada em projetos críticos no ambiente de produção pois a chave secreta pode ser obtida pela decompilação do projeto e por outros meios mais avançados. 

Os grandes problemas com as chaves de API ocorrem quando os usuários finais, e não os desenvolvedores, começam a fazer chamadas de API com essas chaves, que geralmente expõem sua API a riscos de segurança e gerenciamento. O que se resume é que as API Keys não são, por natureza, uma solução completa.

Embora possam ser perfeitamente adequadas para fins de somente leitura, são uma solução muito fraca para corresponder à complexidade de um sistema de API de alto uso. Sempre que você começa a integrar outras funcionalidades, como gravação, modificação, exclusão e muito mais, entra necessariamente no domínio de Identificação, Autenticação e Autorização.

Na próxima parte do artigo veremos como receber dados em diferentes formatos.

Pegue o código dos projetos aqui: ApiReservas_2.zip e Mvc_Reservas_2.zip

"Bendito seja o Deus e Pai de nosso Senhor Jesus Cristo, o Pai das misericórdias e o Deus de toda a consolação;
Que nos consola em toda a nossa tribulação, para que também possamos consolar os que estiverem em alguma tribulação, com a consolação com que nós mesmos somos consolados por Deus."
2 Coríntios 1:3,4

Referências:


José Carlos Macoratti