https://a.storyblok.com/f/270183/27258/ca22882604/blog_go_verify2_1200x600.png

Implementar a autenticação multifatorial em Go com o Verify

Publicado em November 13, 2020

Tempo de leitura: 10 minutos

Seja em um projeto com milhões de usuários ou apenas em um pequeno projeto paralelo, garantir que as Applications que você desenvolve sejam seguras e protegidas é de extrema importância. Applications que não são seguras podem expor os dados dos usuários a hackers, resultando em perda financeira e de confiança. É função do desenvolvedor de software garantir que o código escrito adote uma abordagem que priorize a segurança, sem deixar nenhuma lacuna. Uma maneira de garantir a segurança de suas Applications é integrar um sistema de autenticação multifatorial, comumente chamado de MFA.

A autenticação multifatorial é utilizada para aumentar a segurança das Applications, bem como para verificar a identidade do usuário. Neste artigo, aprenderemos como implementar um sistema de MFA usando a linguagem de programação Go e a Verify API.

Pré-requisitos

Para acompanhar este artigo, você precisará de:

  • Go (versão 1.14 ou superior)

  • Módulos Go habilitados

  • Um editor de texto de sua escolha

  • Conhecimentos básicos de Go

Account da API da Vonage

Para concluir este tutorial, você precisará de uma Account na API da Vonage. Se você ainda não tiver uma, pode se cadastrar hoje mesmo e começar a desenvolver com crédito gratuito. Depois de criar um Account, você encontrará sua chave de API e seu segredo de API na parte superior do Painel da API da Vonage.

Configuração do projeto

Vamos criar um aplicativo simples que gera um código de verificação e o envia para um número de telefone fornecido pelo usuário. O usuário digita o código em uma página de confirmação, e o aplicativo verifica se ele é válido.

App workflowApp workflow

A imagem acima é um esboço do fluxo de trabalho do aplicativo que vamos desenvolver neste artigo.

Para começar, crie os arquivos e pastas de acordo com a estrutura em árvore abaixo:

├── static/ │ ├── index.html │ ├── form.html ├── .env ├── utils/ │ ├── verify.go └── server.go

A pasta “static” contém duas páginas HTML (formulários) que serão utilizadas para coletar, respectivamente, o número de telefone do usuário e o código de confirmação.

Os arquivos Env são usados para armazenar variáveis de ambiente e valores que devem ser mantidos em sigilo. O .env arquivo conterá a chave e o segredo da API da Vonage, que você pode obter no painel de controle.

O utils diretório contém um verify.go arquivo que abrigará todo o código diretamente relacionado à Verify API do Vonage. O server.go arquivo na raiz do nosso projeto conterá a lógica relacionada ao servidor que fornece os arquivos HTML.

Configurar as visualizações

O primeiro passo para criar nosso aplicativo web é criar um servidor web simples que irá exibir os formulários HTML. No server.go arquivo, inclua o seguinte:

func main() {
//Create the fileServer Handler
   fileServer := http.FileServer(http.Dir("./static"))
//Create a New Serve Mux to register handler
   mux := http.NewServeMux()
   mux.Handle("/", fileServer)
//Create the server on Port 8080 and print start message!
   fmt.Printf("Starting server at port 8080\n")
   log.Fatal(http.ListenAndServe(":8080", mux))
}

Importamos o net/http package e chamamos sua FileServer função, passando a rota para o static diretório como seu primeiro argumento. A http.FileServer função retorna um manipulador que registramos em um padrão de URL. Para registrar um manipulador, precisamos criar um novo mux de serviço usando a http.NewServeMux função e chamamos seu Handle método, passando / como nosso padrão, juntamente com o manipulador do servidor de arquivos que geramos acima. Em seguida, criamos o servidor usando o http.ListenAndServe método. Passamos a porta e o serve mux que criamos acima e, em seguida, exibimos uma mensagem no terminal para nos notificar de que o servidor está em funcionamento.

Ao executar o server.go arquivo, você receberá uma mensagem de inicialização indicando que o servidor foi iniciado. No entanto, se você acessar localhost:8080/ no navegador, você verá apenas uma página em branco. Isso ocorre porque nosso index.html arquivo está vazio.

No index.html file, crie um formulário intitulado Registrar com um campo de entrada para o número de telefone do usuário e um botão de envio:

<form method="POST" action="/form">
   <h2>Register</h2>
   <label>Please Input your phone number:</label><br/>
   <input name="phone" type="tel" required value="" >
   <input type="submit" value="Submit">
</form>

O método do formulário é POST, já que o servidor receberá os dados fornecidos pelo usuário. Definimos a ação do formulário como uma rota, /form, que vamos configurar para processar os dados retornados pelo formulário de cadastro.

Agora que a primeira página de cadastro está pronta, podemos criar uma segunda página para a confirmação do código enviado ao usuário. A página de confirmação é semelhante à página de cadastro, com apenas algumas alterações nos rótulos dos campos e nas entradas:

<form method="POST" action="/confirm">
   <h2>Confirm Phone Number</h2>
   <label for="phonenum">Please Input the confirmation code sent to your phone:</label><br/>
   <input id="phonenum" name="confirmation" type="tel" required value="" >
   <input name="phone" type="hidden" value="{{ .Phone }}">
   <input name="requestId" type="hidden" value="{{ .Id }}">
   <input type="submit" value="Submit">
</form>

O texto completo dos dois arquivos HTML pode ser visto no repositório de exemplo. Após configurar os dois arquivos HTML, reinicie o servidor. Se tudo correu bem, o index.html arquivo deve aparecer quando você acessar localhost:8080 no navegador.

Registration formRegistration form

Se você digitar um número de telefone e clicar no botão “Enviar”, perceberá que será redirecionado para localhost:8080/form conforme especificado anteriormente por meio do atributo “action” do HTML. A /form rota ainda não está configurada para exibir o form.html arquivo. Na próxima seção, criaremos um manipulador para a /form rota que aceitará o número de telefone como argumento e renderizará o confirm.html arquivo.

Implementar a autenticação multifatorial

Nesta seção, discutiremos o núcleo do nosso aplicativo — a autenticação multifatorial — e o implementaremos em código. Usaremos a Verify API do Vonage para implementar a autenticação multifatorial.

O processo de implementação da autenticação multifatorial (MFA) com a Verify API é um processo de duas etapas:

  • O primeiro passo é iniciar uma solicitação de Verify. Nessa etapa, um código é enviado para o celular do usuário.

  • O segundo passo é verificar se o código de verificação fornecido pelo usuário está correto.

Quando uma solicitação de Verify é iniciada, um Request_id será gerado automaticamente a partir do número de telefone do usuário. Esse ID será usado na verificação na segunda etapa.

Os desenvolvedores da Vonage criaram um pacote Go para interagir com uma série de APIs da Vonage, incluindo a Verify API. Para começar, instale o pacote no seu projeto executando go get github.com/vonage/vonage-go-sdk no terminal.

Agora que já estamos prontos, vamos direto ao assunto!

No verify.go arquivo, crie quatro funções:

1. createClient

Esta função contém a lógica para criar um cliente. É necessário ter um cliente para que possamos interagir com a Verify API. Para criar um cliente, chamamos a NewVerifyClient função fornecida pelo vonage-go pacote que importamos anteriormente. A NewVerifyClient função requer um conjunto de autenticação, que criamos chamando a CreateAuthFromKeySecret função, passando a chave e o segredo da API

É uma boa prática armazenar informações confidenciais como variáveis de ambiente para evitar que caiam em mãos erradas. No .env arquivo criado acima, adicione sua chave e seu segredo da API no formato API_KEY=0000000.

Neste ponto, a createClient função fica assim:

func createClient() *vonage.VerifyClient{
   Key, _ := os.LookupEnv("API_KEY")
   Secret, _ := os.LookupEnv("API_SECRET")
   auth := vonage.CreateAuthFromKeySecret(Key, Secret)
   client := vonage.NewVerifyClient(auth)
   return client
}

2. função init

A init função é predefinida pelo Go e é usada para inicializar nosso aplicativo. Vamos usá-la para carregar nossas variáveis de ambiente a partir do .env arquivo antes que o restante do nosso código seja executado, mas primeiro precisamos instalar um pacote comumente usado para carregar variáveis de ambiente. No terminal, execute go get github.com/joho/godotenv' to install the package. Next, add the corresponding import. Theinitfunction will contain just a few lines of code which invokes thegodotenv` Função de carregamento.

func init() {
   // loads values from .env into the system
   if err := godotenv.Load(); err != nil {
      log.Print("No .env file found")
   }
}

3. Função VerStart

É nesta função que iniciamos a solicitação de verificação. A função será exportada para o pacote principal; portanto, colocamos a primeira letra da função em maiúscula, conforme é padrão na linguagem Go. Na VerStart função, iniciamos a solicitação chamando o método Request em um cliente. Chamamos a createClient função que criamos acima para obter um cliente no qual possamos chamar o Request método:

func VerStart(phoneNumber string) string{
   client := createClient()
verification, _, err := client.Request(phoneNumber, "Go-Tut MFA", vonage.VerifyOpts{
		CodeLength: 6,
	})

   if err != nil {
      log.Fatal(err)
   }
   return verification.RequestId
}

O Request método recebe três parâmetros:

  • O número de telefone a ser verificado

  • O nome da marca

  • Uma estrutura de opções usada para personalizar a OTP que será enviada ao usuário.

Em nosso aplicativo, queremos que o número de telefone seja dinâmico; por isso, o especificamos como um parâmetro para a Request função, que será passado quando a função for chamada. O nome da marca é uma string curta para indicar qual marca está enviando o SMS — neste exemplo, usei “Go-tut MFA”. Quando os parâmetros necessários são passados para o Request método, podemos lidar com seus valores de retorno. O Request método retorna uma resposta de verificação contendo um campo de status, uma resposta HTTP que vamos ignorar e um tipo de erro. Podemos retornar o status da resposta de verificação e lidar com o erro.

4. Função verCheck

Essa função contém a lógica relacionada à confirmação do código de verificação enviado pelo usuário. Essa função é semelhante à verStart função vista acima:

func VerCheck(reqId, code string) string{
   client := createClient()
   response, _, err := client.Check(reqId, code)
   if err != nil {
            log.Fatal(err)
   }
   if err != nil {
      log.Fatal(err)
   }

   return response.Status
}

A verCheck função recebe um ID de solicitação, que é um argumento obrigatório para a Check função, e um code parâmetro, que deve ser o código de confirmação enviado ao usuário. Essa função retornará um status, que é do tipo string. O status retornado indicará se o usuário digitou o código de confirmação correto. O status da resposta será 0 se, e somente se, o código de confirmação digitado pelo usuário estiver correto.

Na próxima seção, veremos onde as quatro funções criadas acima devem ser chamadas para que nosso aplicativo funcione.

Montar o aplicativo

Agora que todas as funções utilitárias de que nosso aplicativo precisa estão prontas, podemos juntar tudo isso em um aplicativo funcional. Já renderizamos nossa página inicial, mas também precisamos renderizar a página de confirmação que será exibida assim que o botão “Enviar” for clicado. Fazer isso em Go é bem diferente de outras linguagens. Teremos que criar uma função manipuladora e anexá-la à /form rota que recebe os dados do formulário de cadastro. Em seguida, criaremos uma função chamada formHandler para implementar a interface de manipulador do Go. Em seguida, analisamos o corpo da solicitação para que possamos acessar os dados do formulário a partir do index.html. Usamos o método de solicitação http ParseForm para isso.

Os dados do formulário podem ser extraídos do formulário analisado usando o formValue método. Esse método recebe o nome especificado no atributo `input` do HTML do nosso index.html arquivo. Podemos então importar e chamar a verRequest função com o número de telefone.

Para exibir a página de confirmação quando um usuário digitar seu número de telefone, teremos que criar uma função separada. Embora possamos simplesmente usar a http.Servefile função para exibir nosso form.html arquivo, essa não é a solução ideal no nosso caso. Além de renderizar o arquivo de confirmação, também precisamos passar o número de telefone do usuário e o ID da solicitação para que ele possa ser usado para chamar a verCheck função utilitária mencionada acima.

A função de renderização é bastante básica — ela recebe uma solicitação HTTP, um gravador de respostas e uma interface. A função de renderização utiliza o http/template para analisar os arquivos e executar com os dados fornecidos (o número de telefone do usuário e o ID da solicitação).

func render(w http.ResponseWriter, filename string, data interface{}) {
//parse the provided file
   tmpl, err := template.ParseFiles(filename)
   if err != nil {
      log.Println(err)
   }
//execute the file
   if err := tmpl.Execute(w, data); err != nil {
      log.Println(err)
   }
}

Com a função de renderização pronta, podemos chamá-la na formHandler função, passando a rota para o arquivo do formulário e nossa mensagem. Para passar uma mensagem, basta criar uma estrutura que defina os campos que queremos passar para o arquivo form.html:

type Message struct {
   Phone  string
   Id     string
}

Precisamos criar campos ocultos no form.html arquivo para receber as variáveis da estrutura da mensagem, adicionando o seguinte ao formulário:

<input name="phone" type="hidden" value="{{ .Phone }}">
<input name="requestId" type="hidden" value="{{ .Id }}">

Neste ponto, a função formHandler função fica assim

func formHandler (w http.ResponseWriter, r *http.Request){
//Parse the form
      if err := r.ParseForm(); err != nil {
         fmt.Fprintf(w, "ParseForm() err: %v", err)
         return
      }
//Get the value of the Input from the form
      Phone :=  r.FormValue("phone")
      Id := verify.VerStart(Phone)
      msg := &Message{
         Id: Id,
         Phone: Phone,
      }
//Render the form.
      render(w, "./static/form.html", msg)
}

Uma última coisa a fazer antes de testarmos tudo é registrar a formHandler função na /form rota. Para isso, basta adicionar apenas uma linha de código:

mux.HandleFunc("/form", formHandler)

E pronto! Você pode executar o programa, inserir seu número de telefone na página de cadastro e clicar em “Enviar”. Ao clicar no botão “Enviar”, você será redirecionado para a página de confirmação e receberá um SMS no seu celular.

SMS codeSMS code

Nosso aplicativo está tomando forma muito bem. O próximo passo é lidar com a inserção do código de confirmação pelo usuário. No estado atual do nosso aplicativo, se um usuário inserir seu código de confirmação e clicar em “Enviar”, ele será redirecionado para a rota de confirmação, que ainda não está configurada.

Vamos configurar o manipulador de confirmação para que ele receba o código de confirmação do cliente e chame a verCheck função util. O manipulador de confirmação é bastante semelhante ao manipulador de formulário, com apenas algumas diferenças.

Para começar, criamos a confirmHandler função que implementa a interface do manipulador e analisa a solicitação recebida, exatamente como fizemos para o formHandler function. Em seguida, extraímos os valores necessários para executar a verCheck função a partir do formulário. Há três valores que precisamos extrair:

  • O número de telefone

  • O ID da solicitação

  • O código de confirmação

Os dois primeiros valores foram passados a partir da formHandler usando a função render e ficaram ocultos no form.html file. Para obter os valores, basta adicionar as seguintes linhas, que já havíamos discutido anteriormente ao criar a formHandler função:

Id := r.FormValue("requestId")
phone := r.FormValue("phone")
confirmation := r.FormValue("confirmation")

Agora que recebemos todos os valores necessários para a segunda etapa do nosso fluxo de trabalho (verificação do código), podemos chamar a função verCheck function, passando o ID da solicitação e o código de confirmação que acabamos de extrair. Lembra-se de como configuramos a verCheck função anteriormente para que ela retornasse um status de sucesso? Podemos verificar esse status para ver se o código de confirmação inserido pelo usuário está correto e exibir uma mensagem de sucesso ou de falha em uma nova página da web usando a fmt.FPrint função. Neste ponto, o confirmHandler deveria ficar assim:

func confirmHandler(w http.ResponseWriter, r *http.Request) {
//Parse the form
   if err := r.ParseForm(); err != nil {
      fmt.Fprintf(w, "ParseForm() err: %v", err)
      return
   }
//Extract the Form values
   Id := r.FormValue("requestId")
   phone := r.FormValue("phone")
//Receive the confirmation code
   confirmation := r.FormValue("confirmation")
//Verify the Confirmation code
   response := verify.VerCheck(Id, confirmation)
//Check if the confirmation code is incorrect
   if response != "0" {
     fmt.Fprint(w,"Verification failed! Input the correct code sent to ", phone)
      return
   }

   fmt.Fprint(w,"🎉 Success! 🎉")
}

Neste momento, já estamos quase terminando nosso aplicativo. A última coisa que precisamos fazer é registrar a confirmHandler rota, adicionando a seguinte linha de código à função principal:

mux.HandleFunc("/confirm", confirmHandler)

E pronto! Se você iniciar o servidor e acessar o endereço no navegador, deverá ver o formulário de cadastro. Se você digitar seu número de telefone e clicar em “Enviar”, receberá o código no seu celular. Em seguida, você será redirecionado automaticamente para a página de confirmação, onde poderá inserir o código. Ao enviar o código, você deverá ver a mensagem “Sucesso” exibida na página.

Conclusão

Até agora, criamos um aplicativo em Go que utiliza a Verify API do Vonage e aprendemos sobre desenvolvimento web com Go. Espero que tenham gostado. O código completo desse aplicativo pode ser encontrado no GitHub.

Compartilhar:

https://a.storyblok.com/f/270183/400x588/18f261786d/oluwatobi-okewole.png
Oluwatobi Okewole

Oluwatobi is a software developer and writer. He loves to simplify complex topics, making them easy for anyone to understand