
Compartilhar:
Hui Jing é Developer Advocate na Nexmo. Ela tem um amor imenso por CSS e tipografia e, de modo geral, é apaixonada por tudo que diz respeito à web.
Como adicionar a autenticação de dois fatores com Node.js e Koa.js
Tempo de leitura: 14 minutos
A autenticação de dois fatores (2FA) deve seu nome ao fato de que são necessários dois elementos para verificar sua identidade: algo que você sabe, como uma senha, e algo que você possui, como o código de verificação do seu dispositivo móvel ou um token físico.
Adicionar a autenticação de duas etapas (2FA) ao seu aplicativo não precisa ser uma tarefa difícil. Este tutorial abordará como implementar a 2FA em seus aplicativos e serviços web para oferecer uma camada adicional de segurança com a ajuda da Verify API. Vamos criar uma aplicação simples em Koa.js para entender como funciona o mecanismo subjacente. Isso facilitará a compreensão de como isso se encaixará em seus próprios projetos existentes, mesmo que você não esteja usando o Koa.js.
Este tutorial abordará como implementar um sistema de token de verificação com a Verify API e o Koa.js. Temos um tutorial semelhante em Node.js usando o Express.js — você pode encontrá-lo aqui.
Você começaria com uma página de login que solicita ao usuário um número de celular. Após o envio dos dados, ele seria solicitado a inserir um código de verificação enviado para o número de celular por SMS. Assim que isso for concluído, ele poderá acessar o aplicativo.
Pré-requisitos
Conhecimentos básicos de JavaScript
Node.js instalado no seu computador
Depois de criar um Account na API da Vonage, você poderá encontrar sua chave de API e seu segredo de API na parte superior do Painel da API da Vonage.
Este tutorial vai guiá-lo por todo o processo, do zero. Se quiser ver o código final, você pode clonar o repositório Git deste projeto. Também temos uma versão no Glitch, que apresenta um design mais extravagante, e você pode fazer um remix também. Observe que há pequenas diferenças na implementação do Glitch para se adequar à forma como os projetos são hospedados na plataforma.
Glitch version of demo
Começando um projeto Koa.js do zero
Crie uma pasta de projeto no seu computador e, em seguida, execute o comando a seguir para configurar um novo projeto Node.js.
Isso acionará uma série de solicitações que gerarão seu package.json arquivo. Você pode optar por deixar as respostas em branco para usar os valores padrão, se desejar.
Configuring package.json
Em seguida, instale o Koa.js. Observe que o Koa requer o Node v7.6.0 ou superior para oferecer suporte ao ES2015 e às funções assíncronas.
Crie um server.js arquivo na pasta do seu projeto.
Cole o código a seguir no arquivo que você acabou de criar.
const Koa = require('koa')
const port = process.env.PORT || 3000
const app = new Koa()
app.use(async ctx => {
ctx.body = 'Hello Unicorn 🦄'
})
const listener = app.listen(port, function() {
console.log('Your app is listening on port ' + listener.address().port)
})
Execute o server.js arquivo.
Se você acessar http://localhost:3000 no seu navegador, você deverá ver uma página em branco com o texto “Olá, Unicórnio 🦄”.
Check that server is running
Você também deve instalar o dotenv, que permite carregar variáveis de ambiente armazenadas em um .env arquivo no process.env.
E agora você pode criar o .env arquivo, que deve conter, no mínimo, as seguintes variáveis:
Para acessar as variáveis de ambiente, você precisará incluí-lo, de preferência no início do seu server.js arquivo.
require('dotenv').config()Se você ainda não criado um Account no Nexmo ainda, agora é um ótimo momento para fazê-lo. Depois de fazer login no painel, suas credenciais de API devem ser a primeira coisa que você verá. Certifique-se de colocar tanto a chave quanto o segredo entre aspas.
Estrutura do projeto
No momento, seu projeto provavelmente teria apenas um package.json, um server.js arquivo e um .env arquivo. Vamos configurar a estrutura do projeto para que você possa ter uma interface básica com a qual os usuários possam interagir.
PROJECT_NAME/
|-- public/
| |-- client.js
| `-- style.css
|-- views/
| `-- index.html
|-- .env
|-- package.json
`-- server.jsCom isso, você precisará fazer alguns ajustes no server.js arquivo para servir o index.html arquivo e os recursos relacionados, em vez de apenas uma linha de texto. O Koa.js é um framework bastante básico, portanto, quaisquer funcionalidades adicionais para roteamento ou veiculação de recursos estáticos precisam ser instaladas separadamente. Aqui está a lista de módulos adicionais e suas finalidades:
koa-staticpara servir recursos estáticoskoa-bodyparserpara processar dados enviados por meio de solicitações POSTkoa-routerpara roteamentokoa-viewspara renderizar modelos
Este exemplo também utiliza Nunjucks para renderizar arquivos de modelo. A Verify API do Vonage será usada para gerar o código de verificação por SMS; portanto, você precisará instalar também a biblioteca cliente do Vonage para Node.js.
Exibição de recursos estáticos e arquivos HTML
Para permitir que o aplicativo sirva recursos estáticos, como folhas de estilo e JavaScript do lado do cliente, a partir da pasta pasta /public , você pode adicionar o seguinte ao arquivo server.js arquivo:
const serve = require('koa-static')
app.use(serve('./public'))Para servir arquivos HTML a partir da pasta /views , você pode utilizar koa-views, que oferece uma render() função. O mecanismo de modelos usado neste exemplo é o Nunjucks, mas você pode escolher livremente o mecanismo de modelos que melhor se adequar às suas necessidades.
const views = require('koa-views')
app.use(views('./views', { map: { html: 'nunjucks' }}))O próximo passo seria configurar algumas rotas básicas para servir as páginas do seu aplicativo.
const Router = require('koa-router')
const router = new Router()
router.get('/', (ctx, next) => {
return ctx.render('./index')
})
app.use(router.routes()).use(router.allowedMethods())
Para este exemplo, você precisará de 3 páginas: a index.html que servirá como página de destino principal, verify.html para que os usuários insiram seu código de verificação e result.html para indicar se a verificação foi bem-sucedida ou não.
A estrutura do formulário da web é bastante simples, e você pode personalizá-lo com CSS da maneira que quiser.
<form method="post" action="verify">
<input name="phone" type="tel" placeholder="+6588888888">
<button>Get OTP</button>
</form>Este formulário enviará os dados inseridos pelo usuário para a /verify rota, e você pode usar o número de telefone inserido no campo de entrada para acionar a solicitação do código de verificação. Um formulário semelhante pode ser usado para as outras duas rotas para /check e /cancel também.
<form method="post" action="check">
<input name="pin" placeholder="Enter PIN">
<input name="reqId" type="hidden" value="{{ reqId }}">
<button>Verify</button>
</form><form method="post" action="cancel">
<input name="reqId" type="hidden" value="{{ reqId }}">
<button class="inline">Cancel verification</button>
</form> Tratamento das entradas do usuário
Além disso, para processar as entradas do usuário por meio de formulários da web, você precisará de algumas rotas para lidar POST as solicitações também. Certifique-se de declarar bodyparser() antes de qualquer uma das rotas.
const bodyParser = require('koa-bodyparser')
/* This should appear before any routes */
app.use(bodyParser())
router.post('/verify/', async (ctx, next) => {
const payload = await ctx.request.body
/* Function to trigger verification code here */
})
router.post('/check/', async (ctx, next) => {
const payload = await ctx.request.body
/* Function to check verification code here */
})
router.post('/cancel/', async (ctx, next) => {
const payload = await ctx.request.body
/* Function to cancel verification code here */
})
Agora que você já consegue obter o número de telefone do usuário, precisará usar a Verify API para enviar um código PIN para ele. Inicialize uma nova instância do Nexmo com suas credenciais da API da Vonage.
const Nexmo = require('nexmo');
const nexmo = new Nexmo({
apiKey: YOUR_API_KEY,
apiSecret: YOUR_API_SECRET
});Há três funções que precisamos cuidar. A primeira é acionar o código de verificação com a nexmo.verify.request() função. Isso envolve o número de telefone do usuário e uma string com o nome da marca, que será exibida ao usuário como remetente.
async function verify(number) {
return new Promise(function(resolve, reject) {
nexmo.verify.request({
number: number,
brand: process.env.NEXMO_BRAND_NAME
}, (err, result) => {
if (err) {
console.error(err)
reject(err)
} else {
resolve(result)
}
})
})
}
Assim que o usuário receber o código PIN por SMS, ele deverá inseri-lo na nexmo.verify.check() função, para que ele possa ser verificado. Você notará um request_id parâmetro. Esse valor é obtido quando o código PIN é gerado com sucesso. Existem várias maneiras de passar o ID da solicitação para a nexmo.verify.check() função, e este exemplo utiliza um campo oculto no formulário .
async function check(reqId, code) {
return new Promise(function(resolve, reject) {
nexmo.verify.check({
request_id: reqId,
code: code
}, (err, result) => {
if (err) {
console.error(err)
reject(err)
} else {
resolve(result)
}
})
})
}
A última função oferece ao usuário a opção de cancelar a verificação caso ele mude de ideia. Ela utiliza a nexmo.verify.control() função e, mais uma vez, requer o ID da solicitação gerado ao acionar o código PIN e um valor de string igual a cancel.
async function cancel(reqId) {
return new Promise(function(resolve, reject) {
nexmo.verify.control({
request_id: reqId,
cmd: 'cancel'
}, (err, result) => {
if (err) {
console.error(err)
reject(err)
} else {
resolve(result)
}
})
})
}
Landing page for demo
Agora você precisa utilizar essas três funções nas rotas que definimos anteriormente, começando pela que aciona o código de verificação.
router.post('/verify/', async (ctx, next) => {
const payload = await ctx.request.body
const phone = payload.phone
const result = await verify(phone)
const reqId = result.request_id
ctx.status = 200
return ctx.render('./verify', { reqId: reqId })
})
O ctx.request.body ficará mais ou menos assim:
{
"phone": "+40987654321"
}Você pode pegar esse número de telefone e passá-lo para a verify() função. Desde que seja um número de telefone válido, o código de verificação será enviado e você receberá uma resposta contendo um request_id e status.
{
"request_id": "1bf002ecd1e94d8aa81ba7463b19f583",
"status": "0"
}A partir daí, você pode enviar o ID da solicitação para o front-end, para que seja utilizado quando o usuário inserir o código de verificação.
The request_id is passed to the frontend
Quando o usuário inserir o PIN correto, você precisará inserir tanto o PIN quanto o ID da solicitação na check() função.
router.post('/check/', async (ctx, next) => {
const payload = await ctx.request.body
const code = payload.pin
const reqId = payload.reqId
const result = await check(reqId, code)
const status = result.status
ctx.status = 200
return ctx.render('./result', { status: status })
})
Mais uma vez, esses dois valores podem ser obtidos a partir do ctx.request.body e, se o PIN for validado como correto, você receberá uma resposta semelhante a esta:
{
"request_id": "1bf002ecd1e94d8aa81ba7463b19f583",
"status": "0",
"event_id": "150000001AC57AB2",
"price": "0.10000000",
"currency": "EUR"
}Você pode então usar o código de status para determinar qual mensagem deseja exibir ao usuário. Este exemplo utiliza o Nunjucks; portanto, a marcação na página de resultados poderia ser algo como isto:
{% if status == 0 %}
<p>Code verified successfully. ¯\_(ツ)_/¯</p>
{% else %}
<p>Something went wrong… ಠ_ಠ</p>
<p>Please contact the administrator for more information.</p>
{% endif %}
Verification messages
Essa foi uma análise detalhada de cada parte do código, mas, para ter uma ideia de como o aplicativo se apresenta como um todo, confira o código-fonte no GitHub.
Outros aspectos a serem considerados
Este tutorial é uma versão simplificada, destacando apenas os elementos necessários para implementar a autenticação de dois fatores. No entanto, há inúmeros aspectos que precisam ser considerados em uma aplicação real. Um dos mais importantes é o tratamento de erros. A Verify API retorna um valor de status de 0 para consultas bem-sucedidas, mas qualquer outro valor indica um erro.
Esses erros devem ser tratados, e a interface do usuário no front-end deve refletir quaisquer erros potenciais que impeçam a verificação bem-sucedida. Também pode ser uma boa ideia implementar algum tipo de validação no front-end ou até mesmo utilizar a Number Insight API da Vonage para garantir que apenas números de telefone válidos sejam passados para a Verify API.
E agora, para onde vamos?
Se você quiser explorar mais essas APIs, aqui estão alguns links que podem ser úteis:
Documentação da Verify API no portal do desenvolvedor
Série de tutoriais sobre várias APIs da Vonage
Se precisar de nós, acesse o canal do Slack da Comunidade de Desenvolvedores da Vonage
Compartilhe sua opinião conosco enviando um tweet para @VonageDev