
Compartilhar:
Ex-redator técnico da Vonage. Adora experimentar e documentar APIs.
Adicione a autenticação de dois fatores (2FA) ao seu aplicativo com a Verify API e o SDK para Golang
Tempo de leitura: 16 minutos
Quando você se esforça para desenvolver um aplicativo web que ofereça valor real aos seus usuários, pode ser realmente desanimador vê-lo sendo usado de forma indevida. Vazamento de credenciais, cadastros falsos... sempre há uma pequena minoria tentando usar sua plataforma para seus próprios fins nefastos.
Embora seja praticamente impossível impedir que isso aconteça em algum nível, é possível dissuadir todos os invasores, exceto os mais obstinados, ao implementar a autenticação de dois fatores (2FA).
O que é a autenticação de dois fatores (2FA)?
A autenticação de dois fatores (2FA) é uma camada extra de proteção que exige que o usuário forneça algo além do nome de usuário e da senha para utilizar o seu serviço. Normalmente, trata-se do acesso a um dispositivo móvel capaz de receber um código de segurança, que o usuário deve inserir no seu aplicativo como parte do processo de cadastro ou login. Como o seu número de telefone é exclusivo, fica muito mais difícil para golpistas se passarem por usuários existentes ou criarem várias contas falsas.
Outro bom caso de uso para a autenticação de duas etapas (2FA) é a “autenticação reforçada”. Isso é ideal para situações em que um usuário tenta realizar uma ação no seu aplicativo que poderia ter consequências negativas caso esse usuário não seja quem afirma ser. Por exemplo, se você tiver um aplicativo bancário e o usuário tentar adicionar um novo beneficiário. Em casos como esse, a 2FA oferece uma garantia de que o usuário é legítimo.
A Verify API vonage
A Verify API do Vonage Verify facilita muito a implementação da autenticação de duas etapas (2FA) em suas Applications, e é isso que esperamos demonstrar na postagem de hoje. Vamos criar um site simples para uma empresa fictícia chamada Acme Inc., que exige que os usuários se cadastrem usando seus dispositivos móveis para adicionar um certo nível de proteção contra uso fraudulento.
E, para deixar tudo ainda mais empolgante, vamos desenvolver isso em nosso novíssimo (beta) SDK do Go. Isso deve agradar a todos vocês, Gophers experientes, e talvez até incentive outros a terem sua primeira experiência com desenvolvimento em Go? (Alerta de spoiler: acho que vocês vão adorar Go!)
Vamos lá! (Se você não tiver tempo para programar junto, pode encontrar o código-fonte no GitHub).
Crie seu projeto
Crie um diretório para o seu projeto e, dentro dele, execute go mod init. Isso gera um go.mod arquivo para gerenciar as dependências.
Em seguida, crie um main.go arquivo. É nele que você escreverá o código do seu aplicativo:
Vamos usar modelos do Go para construir nosso site. Vamos criar um diretório para eles (tmpl) e também para o CSS que usaremos para estilizar nossas páginas (static):
Configurar o SDK do Vonage Go
Vamos usar a Verify API para exigir que os usuários comprovem que são os proprietários do dispositivo que desejam registrar em nosso serviço. A Verify API torna todo o processo de autenticação de dois fatores realmente simples.
Não faz muito tempo, nós, desenvolvedores do Go, teríamos que fazer todas as chamadas de API necessárias manualmente, mas, nestes tempos mais avançados, contamos com nosso novíssimo SDK do Go da Vonage para facilitar a vida; então, vamos fazer toda a instalação e configuração.
Para usar o SDK do Go, precisamos fornecer nossa chave e nosso segredo da API, que podem ser encontrados no Painel do Desenvolvedor. Ora, não queremos simplesmente colocar essas credenciais diretamente em nosso código, pois qualquer pessoa que tenha acesso à nossa chave e ao nosso segredo poderá fazer chamadas à API às nossas custas. Portanto, vamos configurar esses detalhes em um .env arquivo e usar o github.com/joho/godotenv pacote para ler esses valores em variáveis de ambiente.
Primeiro, crie o .env arquivo no diretório do seu projeto:
VONAGE_API_KEY=<YOUR_API_KEY>
VONAGE_API_SECRET=<YOUR_API_SECRET>Substituir <YOUR_API_KEY> e <YOUR_API_SECRET> pela sua própria chave e segredo da API.
Em seguida, execute go get para instalar dotenv e o SDK do Vonage Go:
Ao main.go escreva um código para ler suas credenciais da API da Vonage a partir do .env arquivo e use-as para instanciar uma instância do SDK do Vonage Go VerifyClient:
package main
import (
"log"
"os"
"github.com/joho/godotenv"
"github.com/vonage/vonage-go-sdk"
)
var verifyClient *vonage.VerifyClient
func main() {
err := godotenv.Load()
if err != nil {
log.Fatal("Error loading .env file")
os.Exit(1)
}
apiKey := os.Getenv("VONAGE_API_KEY")
apiSecret := os.Getenv("VONAGE_API_SECRET")
auth := vonage.CreateAuthFromKeySecret(apiKey, apiSecret)
verifyClient = vonage.NewVerifyClient(auth)
} Criar a interface do usuário
Para entender quais páginas nosso aplicativo web deve apresentar aos usuários, considere o fluxo de trabalho que vamos implementar:
The verification process
Com isso, dá para ver que precisamos das seguintes páginas:
A página inicial (
/): Se o usuário estiver cadastrado, exibimos aqui seu nome e número de telefone cadastrados.A página de inscrição (
/register): Onde o usuário pode inserir seu nome e número de telefone para se cadastrar em nosso serviço.O código de verificação (
/enter-code): onde o usuário insere o código PIN enviado ao seu celular pela Verify API.
Vamos criar essas páginas usando modelos Go. Eles facilitam a passagem de variáveis do código para a página e também permitem que um modelo inclua outros como “partials”, para que você não precise duplicar conteúdos comuns, como cabeçalhos, menus e assim por diante.
Primeiro, adicione o html/template módulo à lista de importações em main.go. Em seguida, crie os seguintes modelos no tmpl diretório:
O modelo de página base
Este é o layout básico que todos os nossos modelos utilizarão. A diretiva {{template "main" .}} permite preencher o corpo da página com conteúdo de outros modelos:
<!-- base.layout.gohtml-->
{{define "base"}}
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Go Verify Demo</title>
<link rel="stylesheet" href="/static/style.css">
<link rel="preconnect" href="https://fonts.gstatic.com">
<link href="https://fonts.googleapis.com/css2?family=Poppins:wght@300&family=Seymour+One&display=swap" rel="stylesheet">
</head>
<body>
{{template "main" .}}
</body>
</html>
{{end}} O modelo da página inicial
Quando o usuário tiver se cadastrado com sucesso no serviço, esta página exibirá seu nome e número de telefone:
<!-- home.page.gohtml -->
{{template "base" .}}
{{define "main"}}
<h1>Welcome to Acme Inc {{.Name}}!</h1>
<p>Your registered phone number is: {{.Phone}}</p>
{{end}} O modelo da página de cadastro
Este modelo inclui um formulário no qual o usuário pode inserir seu nome e número de telefone para se cadastrar no serviço:
<!-- register.page.gohtml -->
{{template "base" .}}
{{define "main"}}
<h1>Acme inc.</h1>
<p>Please register using your mobile/cell phone number for the exciting benefits our service provides!</p>
<form action="/verify">
<fieldset>
<label for="name">Your name:</label>
<input type="text" class="ghost-input" id="name" name="name" placeholder="Jane Smith" required/>
<label for="phone_number">Your mobile number:</label>
<input type="text" class="ghost-input" id="phone_number" name="phone_number" placeholder="447700900001"required/>
<button type="submit" class="ghost-button">Register</button>
</fieldset>
</form>
{{end}}
O modelo da página “Digite o código”
Esta página exibe um formulário no qual o usuário insere o código que recebeu da Verify API:
<!-- entercode.page.gohtml -->
{{template "base" .}}
{{define "main"}}
<h1>Acme inc.</h1>
<p>Please enter the code you received by SMS:</p>
<form action="/check-code">
<fieldset>
<label for="pin_code">Enter PIN:</label>
<input type="text" class="ghost-input" id="pin_code" name="pin_code" placeholder="123456" required><br><br>
<button type="submit" class="ghost-button">Let's Go!</button>
</fieldset>
</form>
{{end}}
Deixe com uma aparência bonita (mais ou menos)
Por fim, vamos adicionar um pouco de CSS para que essas páginas pareçam que a gente se esforçou algum esforço na criação delas. (Infelizmente, não sou especialista em front-end, então PRs são muito bem-vindos!)
Baixe o style.css arquivo no repositório do GitHub e inclua-o no seu static diretório.
O Go precisa ser capaz de servir esse conteúdo CSS estático a partir do seu sistema de arquivos local; portanto, você precisa informar ao seu aplicativo como fazer isso.
Em main.go, primeiro importe o net/http módulo e, em seguida, modifique sua main() função da seguinte maneira:
func main() {
err := godotenv.Load()
if err != nil {
log.Fatal("Error loading .env file")
os.Exit(1)
}
apiKey := os.Getenv("VONAGE_API_KEY")
apiSecret := os.Getenv("VONAGE_API_SECRET")
auth := vonage.CreateAuthFromKeySecret(apiKey, apiSecret)
verifyClient = vonage.NewVerifyClient(auth)
mux := http.NewServeMux()
mux.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir("static"))))
} Gerenciar a sessão do usuário
Precisamos pensar em como vamos verificar se um usuário já se cadastrou em nosso serviço, para que possamos fazer o login dele ou redirecioná-lo para a página de cadastro.
Para isso, vamos utilizar cookies de sessão. Existe um ótimo módulo que podemos usar para gerenciar sessões por meio de cookies e do sistema de arquivos em Go: o módulo Gorilla sessions.
Para instalá-lo, execute:
Adicione-o à sua lista de importações em main.go e crie o cookie de sessão. Enquanto estiver lá, declare também um struct chamada UserData que armazenará o nome e o número de telefone de um usuário cadastrado. Vamos passar isso struct para o nosso modelo de página inicial, para que possamos exibir esses detalhes assim que o usuário se registrar com sucesso:
package main
import (
"html/template"
"log"
"net/http"
"os"
"github.com/gorilla/sessions"
"github.com/joho/godotenv"
"github.com/vonage/vonage-go-sdk"
)
var (
// Key must be 16, 24 or 32 bytes long (AES-128, AES-192 or AES-256)
key = []byte("super-secret-key")
store = sessions.NewCookieStore(key)
)
// UserData - Info from session that we'll use in the UI
type UserData struct {
Name string
Phone string
}
... Definir as rotas
Agora precisamos pensar em quais rotas devemos definir para gerenciar nosso fluxo de trabalho. Vamos criar as seguintes rotas:
O ponto de entrada para nosso aplicativo (
/). Isso exibirá a página inicial (se o usuário estiver cadastrado) ou redirecionará para a/registerrota (caso não esteja).O rota (
/register). Isso exibirá o formulário de cadastro que, ao ser enviado, dará início ao processo de verificação ao chamar a/verifyrota.A verificação (
/verify). Isso envia uma solicitação de verificação à Verify API para que um código seja enviado ao dispositivo móvel do usuário. Em seguida, o usuário é redirecionado para a/enter-coderota.O digite o código (
/enter-code) exibe ao usuário um formulário contendo um campo de texto onde ele pode inserir o código PIN que recebeu. Quando o formulário é enviado, redirecionamos para a/check-coderota.O código de verificação (
check-code). Isso compara o código digitado pelo usuário com o que foi enviado pela Verify API. Se houver correspondência, armazenamos os dados do usuário na sessão e redirecionamos para a página inicial (/). Se não houver correspondência, registraremos um erro no console. (Poderíamos lidar com isso de maneira mais elegante, mas vou deixar isso como um exercício para o leitor!)O clara (
/clear). Isso exclui o cookie de sessão para que você possa “cancelar o cadastro” de um usuário cadastrado. Útil para testes!
Ufa! É bastante código para escrever. Felizmente, é tudo bem simples.
Primeiro, defina alguns manipuladores para as rotas na sua main função e inicie seu servidor para escutar as solicitações recebidas:
func main() {
err := godotenv.Load()
if err != nil {
log.Fatal("Error loading .env file")
os.Exit(1)
}
apiKey := os.Getenv("VONAGE_API_KEY")
apiSecret := os.Getenv("VONAGE_API_SECRET")
auth := vonage.CreateAuthFromKeySecret(apiKey, apiSecret)
verifyClient = vonage.NewVerifyClient(auth)
mux := http.NewServeMux()
mux.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir("static"))))
mux.HandleFunc("/", home)
mux.HandleFunc("/register", register)
mux.HandleFunc("/verify", verify)
mux.HandleFunc("/enter-code", enterCode)
mux.HandleFunc("/check-code", checkCode)
mux.HandleFunc("/clear", unregister)
log.Println("Starting server on :5000")
err = http.ListenAndServe(":5000", mux)
log.Fatal(err)
} O manipulador de rota inicial
Crie o home manipulador acima da sua main função. Esse manipulador é acionado sempre que um usuário acessa a raiz do seu aplicativo web em /.
Ele carrega o cookie de sessão e verifica se o registered valor da sessão está definido. Se estiver, ele armazena as informações do cookie em uma instância de UserData e usa isso para renderizar a página inicial com o nome e o número de telefone do usuário.
Caso contrário, ele redireciona para a /register rota:
func home(w http.ResponseWriter, r *http.Request) {
session, _ := store.Get(r, "acmeinc-cookie")
// Check if user is authenticated
if auth, ok := session.Values["registered"].(bool); !ok || !auth {
// Not authenticated, so user must register
http.Redirect(w, r, "/register", 302)
}
userData := UserData{
Name: fmt.Sprintf("%v", session.Values["name"]),
Phone: fmt.Sprintf("%v", session.Values["phoneNumber"]),
}
files := []string{
"./tmpl/home.page.gohtml",
"./tmpl/base.layout.gohtml",
}
/* Use the template.ParseFiles() function to read the files and store the
templates in a template set.*/
ts, err := template.ParseFiles(files...)
if err != nil {
log.Println(err.Error())
http.Error(w, "Internal Server Error", 500)
return
}
err = ts.Execute(w, userData)
if err != nil {
log.Println(err.Error())
http.Error(w, "Internal Server Error", 500)
}
} O manipulador de rota do registro
A rota de cadastro exibe um formulário para que o usuário insira seu nome e número de telefone para se cadastrar no serviço.
Primeiro, adicione fmt à sua lista de importações. Em seguida, crie o register manipulador acima da sua main função:
func register(w http.ResponseWriter, r *http.Request) {
files := []string{
"./tmpl/register.page.gohtml",
"./tmpl/base.layout.gohtml",
}
ts, err := template.ParseFiles(files...)
if err != nil {
log.Println(err.Error())
http.Error(w, "Internal Server Error", 500)
return
}
err = ts.Execute(w, nil)
if err != nil {
log.Println(err.Error())
http.Error(w, "Internal Server Error", 500)
}
} O manipulador de rota “Verify”
Quando o usuário preencher seus dados de cadastro e clicar no botão “Cadastre-se”, o aplicativo chama a /verify rota. É aqui que criamos a solicitação inicial à Verify API para gerar um código e enviá-lo ao celular do usuário.
Quando você envia uma solicitação à Verify API, ela retorna um request_id para identificar essa solicitação específica. Você precisará desse ID ao verificar o código inserido pelo usuário; portanto, armazene-o em uma variável global:
package main
import (
...
)
var (
...
)
// UserData: Info from session that we'll use in the UI
type UserData struct {
...
}
var verifyClient *vonage.VerifyClient
var requestID stringIniciar essa solicitação é muito simples usando o SDK do Go. Chamamos o VerifyClient.Request método, passando o número de telefone do usuário, o texto que queremos que apareça na mensagem SMS de verificação e algumas opções para personalizar o processo de verificação.
Neste exemplo, as opções que estamos especificando são:
CodeLength: Esse é o comprimento do código que estamos enviando ao usuário. O padrão é de quatro dígitos, mas estamos gerando um código de seis dígitos.Lg: Especifica o idioma em que estamos enviando o SMS de verificação, a partir da lista de idiomas disponíveis.WorkflowID: A Verify API realiza várias tentativas para enviar um código de verificação. A forma e o momento em que essas tentativas são feitas dependem do fluxo de trabalho escolhido. Estamos usando o fluxo de trabalho com o ID4, que envia o código por SMS e, em seguida, aguarda dois minutos para que o código seja verificado. Se não for verificado após esse tempo, ele envia o código novamente e aguarda mais três minutos antes de cancelar o processo. Outros fluxos de trabalho utilizam uma combinação de SMS e chamadas de Voice com conversão de texto em fala para enviar o código de verificação.
Também vamos armazenar as informações que nosso usuário inseriu em nosso cookie de sessão.
Escreva o código para o verify manipulador de rota acima da sua main função:
func verify(w http.ResponseWriter, r *http.Request) {
session, _ := store.Get(r, "acmeinc-cookie")
// retrieve user's name and phone number from the submitted form
userName := r.URL.Query().Get("name")
phoneNumber := r.URL.Query().Get("phone_number")
session.Values["name"] = userName
session.Values["phoneNumber"] = phoneNumber
session.Save(r, w)
log.Println("Verifying...." + userName + " at " + phoneNumber)
response, errResp, err := verifyClient.Request(phoneNumber, "GoTest", vonage.VerifyOpts{CodeLength: 6, Lg: "en-gb", WorkflowID: 4})
if err != nil {
fmt.Printf("%#v\n", err)
} else if response.Status != "0" {
fmt.Println("Error status " + errResp.Status + ": " + errResp.ErrorText)
} else {
requestID = response.RequestId
fmt.Println("Request started: " + response.RequestId)
// redirect to "check" page
http.Redirect(w, r, "/enter-code", 302)
}
} O manipulador de rota “enter code”
Se tudo tiver corrido conforme o planejado, o usuário receberá agora um SMS contendo um código PIN. Nosso /enter-code rotina disponibilizará um formulário para que o usuário insira esse código.
Adicione o enterCode manipulador de rota ao seu aplicativo:
func enterCode(w http.ResponseWriter, r *http.Request) {
files := []string{
"./tmpl/entercode.page.gohtml",
"./tmpl/base.layout.gohtml",
}
ts, err := template.ParseFiles(files...)
if err != nil {
log.Println(err.Error())
http.Error(w, "Internal Server Error", 500)
return
}
err = ts.Execute(w, nil)
if err != nil {
log.Println(err.Error())
http.Error(w, "Internal Server Error", 500)
}
} O manipulador de rota do código de verificação
Quando o usuário digitar o código que recebeu por SMS, precisamos enviar uma solicitação de Verify para garantir que o código digitado seja o mesmo que foi enviado a ele. Usamos o VerifyClient.Check método para isso, passando o ID da solicitação que fizemos no verify manipulador de rota e o código que ele digitou em nossa página.
Se o código corresponder, definimos o registered valor do cookie de sessão para true e redirecionamos para a página inicial. Caso contrário, registraremos uma mensagem de erro no console:
func checkCode(w http.ResponseWriter, r *http.Request) {
// Retrieve the PIN code that the user entered
session, _ := store.Get(r, "acmeinc-cookie")
pinCode := r.URL.Query().Get("pin_code")
response, errResp, err := verifyClient.Check(requestID, pinCode)
if err != nil {
fmt.Printf("%#v\n", err)
} else if response.Status != "0" {
fmt.Println("Error status " + errResp.Status + ": " + errResp.ErrorText)
} else {
fmt.Println("Request complete: " + response.RequestId)
// Set user as authenticated and return to home page
session.Values["registered"] = true
session.Save(r, w)
http.Redirect(w, r, "/", 302)
}
} O manipulador de rota “clear”
Isso será útil para testes. Permite que você reinicie a sessão do usuário enviando uma solicitação para http://localhost:5000/clear, o que exclui o cookie de sessão.
Crie o unregister manipulador de rota da seguinte maneira:
func unregister(w http.ResponseWriter, r *http.Request) {
// Delete the session
session, _ := store.Get(r, "acmeinc-cookie")
session.Options.MaxAge = -1
session.Save(r, w)
http.Redirect(w, r, "/", 302)
} Experimente!
Pronto! Agora é hora de testar seu aplicativo.
Inicie o aplicativo executando
go run main.goAcesse http://localhost:5000 no seu navegador
Digite seu nome e número de telefone, incluindo o código de discagem, mas omitindo os zeros à esquerda. Por exemplo, o número de celular do Reino Unido
07700900001deve ser inserido como447700900001:
Registration pageVocê receberá um SMS contendo um código de seis dígitos:
The verification code, sent by SMSDigite esse código no aplicativo:
Entering the verificaiton codeVocê está cadastrado e será direcionado para a página inicial:

E agora?
Espero que você tenha percebido como é fácil usar a Verify API e o SDK do Go para habilitar a autenticação de dois fatores em seus aplicativos. Na verdade, todo o processo de verificação exigiu apenas algumas linhas de código! A maior parte do trabalho aqui foi criar e gerenciar a interface do usuário.
Observação: Se você não conseguiu acompanhar, pode encontrar o código-fonte completo deste tutorial no GitHub.
Acha que essa demonstração poderia ser melhorada? Concordo! Estas são as melhorias que me vêm à cabeça:
A saída não tão elegante quando o usuário digita o código errado. Em vez disso, você poderia redirecionar o usuário para a página de inserção do código e, ao mesmo tempo, verificar o andamento da solicitação, talvez até mesmo cancelando uma solicitação em andamento após muitas tentativas malsucedidas.
O formato no qual os usuários devem inserir seus números de telefone. As APIs da Vonage esperam que os números de telefone estejam no formato E.164, mas não há motivo para você impor isso aos seus usuários! Você pode fazer com que eles insiram o número local e o país a partir de uma lista suspensa e usar a Number Insight API da Vonage para convertê-lo no formato correto. Isso também serviria como uma verificação prática para garantir que o número inserido pelo usuário seja válido.
Esperamos que você esteja tão animado com o nosso novo Go SDK quanto nós. Para saber mais sobre ele e sobre a Verify API, confira os seguintes recursos: