
Compartilhar:
Ex-diretor de Formação de Desenvolvedores da Vonage. Com experiência como desenvolvedor criativo, gerente de produto e organizador de hack days, Martyn atua como defensor da tecnologia desde 2012, tendo trabalhado anteriormente no setor de radiodifusão e em grandes gravadoras. Ele se dedica a capacitar e empoderar desenvolvedores em todo o mundo.
Adicione a autenticação de dois fatores (2FA) ao seu aplicativo Nuxt com o Nexmo Verify
Tempo de leitura: 17 minutos
Neste tutorial, vamos criar um aplicativo básico com autenticação de dois fatores usando o framework Nuxt JS .
Nosso aplicativo contém uma seção secreta que só pode ser acessada se o usuário se identificar digitando um código PIN enviado por SMS.
Se você quiser conferir todo o código deste exemplo, pode dar uma olhada no repositório nexmo-verify-nuxt na nossa Comunidade Nexmo no GitHub.
Pré-requisitos
Se você quiser acompanhar, vai precisar do seguinte:
Node.js — este aplicativo foi desenvolvido usando a versão 10.0, mas deve funcionar normalmente com a versão 8 ou superior.
Experiência prévia com VueJS seria útil, mas não é absolutamente necessária. Este é um aplicativo bem básico e serve como uma boa introdução para quem está começando.
Introdução ao Nuxt
O Nuxt é um framework para a criação de “Applications Universais do Vue.js”. Essencialmente, isso significa que, ao usar o Nuxt, você tem a opção de preparar seu aplicativo finalizado para produção de três maneiras diferentes:
Trata-se de uma aplicação com renderização no lado do servidor, que gera todo o código HTML a ser exibido no navegador no lado do servidor durante a execução e, em seguida, o envia ao usuário.
Trata-se de uma aplicação de página única, no qual toda a renderização da interface do usuário ocorre no navegador durante a execução. Isso seria muito semelhante a uma aplicação tradicional do Vue.js.
Como um site estático, em que todo o HTML, CSS e JS é compilado em arquivos estáticos que você pode enviar para Amazon S3, GitHub Pages, Netlify — onde você quiser!
É uma estrutura realmente flexível com a qual, como fã do Vue JS, tenho gostado muito de trabalhar. Se você quiser saber mais sobre ela, confira a introdução ao Nuxt JS na documentação.
Por que usar o Nuxt nesse caso?
Escolhi o Nuxt para este tutorial porque queria destacar alguns recursos que realmente me impressionaram e que tornaram as possibilidades de trabalhar com um framework como esse praticamente ilimitadas. São eles:
Renderização no lado do servidor
Middleware de servidor personalizado
Não se preocupe muito se ainda não souber o que são essas coisas. Vamos abordar isso em breve. Vamos começar a trabalhar no código do nosso aplicativo instalando o Nuxt e criando nossa estrutura de pastas.
Estruturar o aplicativo
Comece criando uma pasta vazia para trabalhar; dê a ela o nome que quiser.
Vamos usar o NPM para instalar as dependências deste projeto, então comece executando:
Em seguida, instale as dependências:
Também trabalharemos com o excelente pacote dotenv no desenvolvimento para gerenciar nossas variáveis de ambiente; portanto, instale-o como uma devDependency.
Antes de entrarmos na estrutura de pastas do aplicativo, há uma pequena alteração a ser feita. Abra package.json no seu editor e substitua a scripts seção por isto:
"scripts": {
"dev": "nuxt",
"build": "nuxt build",
"start": "nuxt start"
} Adicionando a estrutura de pastas
O Nuxt utiliza um conjunto de pastas específicas para se organizar. Muitas vezes, a presença dessas pastas garante que as dependências necessárias para realizar determinadas tarefas sejam incluídas automaticamente quando você compila seu aplicativo.
Por exemplo, se você precisar usar o VueX no seu aplicativo para gerenciar o estado compartilhado, você não precisa instalá-lo. Basta criar uma pasta chamada store, coloque um index.js dentro dela e o Nuxt incluirá automaticamente o VueX para você. É muito legal.
Nós vamos usar o VueX e mais algumas coisas em nosso aplicativo; portanto, na raiz do seu diretório de trabalho, crie algumas pastas novas:
O pages, store, e middleware pastas são específicas do Nuxt; eis para que servem:
pages- É aqui que você armazena as páginas do seu site. O Nuxt oferece suporte à geração automática de rotas que se alinha à sua estrutura de pastas. Por exemplo, para acessar https://myapp.xyz/register, você precisaria ter uma pasta chamadaregisterdentro dapagespasta.store- Esta pasta contém seus arquivos do VueX Store. O VueX será incluído automaticamente se você tiver essa pasta.middleware- Se você quiser definir funções personalizadas para serem executadas antes da renderização das páginas, é aqui que elas devem ser definidas.
Se você quiser entender melhor como a estrutura de pastas se relaciona com as ações no Nuxt, confira o guia de estrutura de diretórios .
Configuração, Estilo, Loja e o Layout Padrão
Como é melhor nos concentrarmos no ponto principal deste tutorial, sugiro que você copie certas partes diretamente do repositório ou dos blocos de código, para agilizar o processo.
Na raiz do seu diretório de trabalho, crie um novo arquivo chamado nuxt.config.js e abra-o. Adicione o seguinte código:
module.exports = {
head: {
meta: [
{ name: 'viewport', content: 'width=device-width, initial-scale=1.0' }
],
script: [
{
src:
'https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0-rc.2/js/materialize.min.js',
body: true
}
],
link: [
{
rel: 'stylesheet',
href: 'https://fonts.googleapis.com/icon?family=Material+Icons'
},
{
rel: 'stylesheet',
href:
'https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0-rc.2/css/materialize.min.css'
}
]
},
loading: false,
build: {
vendor: ['axios']
},
env: {
baseUrl: process.env.BASE_URL || 'http://localhost:3000'
}
};Isso garantirá que todo o estilo e todas as fontes que utilizei também funcionem na sua versão. Estamos usando o Materialize CSS como nossa estrutura de interface do usuário; você pode conferir a documentação deles se precisar de mais informações sobre qualquer um dos elementos de layout.
Para obter mais informações sobre como configurar o Nuxt, confira o guia deles.
Em seguida, abra a layouts pasta e crie um novo arquivo chamado default.vue. Adicione a seguinte marcação a ele:
<template>
<div class="container">
<nuxt/>
</div>
</template>
Isso garantirá que todas as outras visualizações que criarmos sejam devidamente agrupadas e dispostas corretamente.
Este aplicativo utiliza algum tipo de gerenciamento de estado; portanto, dentro da store pasta, crie um arquivo chamado index.js e adicione o seguinte código:
import Vuex from 'vuex';
export const store = () => {
return new Vuex.Store({
state: {
token: null
},
mutations: {
SET_TOKEN(state, token) {
state.token = token || null;
},
INVALIDATE_TOKEN(state) {
state.token = null;
}
},
getters: {
isVerified(state) {
return state.token;
}
}
});
};
export default store;
Depois de ter tudo isso pronto, é hora de escrever um pouco de código!
Criar as páginas
O aplicativo que estamos desenvolvendo tem duas páginas. A primeira é uma página inicial com um formulário de login; a segunda é uma seção restrita à qual os usuários podem acessar após serem Verifyados.
Vamos trabalhar primeiro na página de destino. Dentro do pages diretório, crie um novo arquivo chamado index.vue.
A página de destino
Abra o index.vue arquivo no seu editor e adicione o seguinte código:
<template>
<div>
<div class="row">
<div class="col s12 center-align">
<h1><i class="medium material-icons">verified_user</i> Login</h1>
</div>
</div>
<div class="row" v-if="request.token === ''">
<form v-on:submit.prevent class="col s12">
<div class="row center-align">
<div class="input-field col s12">
<i class="material-icons prefix">phone</i>
<input type="text" id="phoneNumber" v-model="phoneNumber"/>
<label for="phoneNumber"> Phone Number</label>
</div>
<div class="row center-align">
<button v-on:click.stop.prevent="sendVerificationCode" type="submit" class="waves-effect waves-light btn"><i class="material-icons left">account_box</i>Send me a verifiation code</button>
</div>
</div>
</form>
</div>
<div class="row" v-else>
<form v-on:submit.prevent class="col s12">
<div class="row center-align">
<div class="input-field col s12">
<i class="material-icons prefix">sms</i>
<input type="text" id="verificationPin" v-model="request.verificationPin"/>
<label for="verificationPin"> Enter the pin you were sent</label>
</div>
<div class="row center-align">
<button v-on:click.stop.prevent="verifyPin" type="submit" class="waves-effect waves-light btn"><i class="material-icons left">account_box</i>Verify me</button>
</div>
</div>
</form>
</div>
</div>
</template>
<script>
import { mapMutations } from 'vuex';
import axios from 'axios';
export default {
data() {
return {
phoneNumber: '',
request: {
token: '',
verificationPin: ''
}
};
},
methods: {
displayMessage: function(type, message) {
if (type === 'error') {
M.toast({
html: `${message}`,
classes: 'rounded red accent-1',
displayTime: 3000
});
}
},
sendVerificationCode: async function() {
const { data } = await axios.post('/verification/send', {
phoneNumber: this.phoneNumber
});
if (!data.token) {
this.displayMessage('error', data.error_text || data.error);
} else {
const { token } = data;
this.request.token = token;
}
},
verifyPin: async function() {
const { data } = await axios.post('/verification/verify', {
token: this.request.token,
code: this.request.verificationPin
});
if (!data.token) {
this.displayMessage('error', data.error_text || data.error);
} else {
const { token } = data;
this.$store.commit('SET_TOKEN', token);
this.$nuxt.$router.replace({ path: '/secret' });
}
}
}
};
</script>
Este arquivo contém duas seções, <template> e <script>. A <template> seção contém o HTML do nosso formulário de login e a <script> seção contém todos os métodos de que precisamos para trabalhar com esse formulário.
Estamos apresentando três métodos aqui, em ordem; eis o que eles fazem:
displayMessage- Esta é apenas uma função auxiliar que exibirá quaisquer erros retornados pela API em uma mensagem de erro sobreposta. As classes que você vê ali vêm diretamente do Materialize CSS.sendVerificationCode- Recebe o número de telefone inserido pelo usuário e o encaminha para nossa API de middleware para integração com o Nexmo VerifyverifyPin- Quando o usuário receber seu código PIN, ele deverá inseri-lo aqui também; esse método o encaminha para o endpoint de verificação na API do nosso middleware.
Neste momento, você já pode executar o aplicativo:
Se tudo estiver certo, você poderá acessar https://localhost:3000 e ver o seguinte:

Infelizmente, clicar em “Enviar-me um código de verificação” não vai adiantar nada, porque nosso código está tentando passar o número para um endpoint chamado /verification/send que não existe.
É hora de passar para o lado do servidor.
Adicionar uma mini API de verificação
A Verify API do Nexmo Verify exige que você use uma chave e um segredo para autenticar as solicitações. Em Applications típicos renderizados pelo navegador, ou de página única, isso não seria algo que poderíamos fazer sem expor nossa chave secreta ao mundo — um desastre.
A maneira mais comum de resolver isso seria criar um script rápido em Node.js que exponha alguns endpoints do Express que você possa acessar para conseguir o que deseja, mantendo suas chaves em segredo.
A desvantagem é que, a menos que seu aplicativo realmente precise de uma API robusta, com muitos endpoints e funcionalidades do lado do servidor, encontrar um lugar para hospedar esse script além do seu aplicativo Nuxt é, na minha opinião, mais trabalho do que o necessário.
Você sabia que é possível executar o Express dentro do Nuxt?
É isso mesmo. Como o Nuxt já está renderizando os elementos no lado do servidor, tecnicamente o Node.js já está em ação, o que significa que você pode usar pacotes como Express como middleware do lado do servidor.
Isso significa que não precisamos de nenhum servidor extra para a nossa pequena API de verificação!
Um arquivo, dois pontos de extremidade, tudo no lado do servidor
Crie um novo arquivo dentro da node-scripts pasta chamado verification_api.js e abra-o no seu editor.
if (process.env.NODE_ENV !== 'production') {
require('dotenv').config();
}
const express = require('express');
const Nexmo = require('nexmo');
const jwt = require('jsonwebtoken');
const API_KEY = process.env.apiKey;
const API_SECRET = process.env.apiSecret;
const JWT_SECRET = process.env.jwtSecret;
const app = express();
app.use(express.json());
const nexmo = new Nexmo({ apiKey: API_KEY, apiSecret: API_SECRET });
app.post('/send', async (req, res) => {
// Get the phone number from the request body of our main app
let phoneNumber = req.body.phoneNumber;
nexmo.verify.request(
{
number: phoneNumber,
brand: 'MyApp',
code_length: 6,
pin_expiry: 60
},
async (err, result) => {
if (err || result.status !== '0') {
res.json({ error: err || result.error_text });
} else {
const accessToken = await jwt.sign(
{
phoneNumber,
request_id: result.request_id
},
JWT_SECRET
);
res.json({ token: accessToken });
}
}
);
});
app.post('/verify', async (req, res) => {
let { token, code } = req.body;
const tokenObject = await jwt.verify(token, JWT_SECRET);
nexmo.verify.check(
{ request_id: tokenObject.request_id, code: code },
async (err, result) => {
if (err || result.status !== '0') {
res.json({ error: err || result.error_text });
} else {
const tokenizeResult = await jwt.sign(result, JWT_SECRET);
res.json({ token: tokenizeResult });
}
}
);
});
app.post('/auth-check', async (req, res) => {
let { token } = req.body;
const tokenObject = await jwt.verify(token, JWT_SECRET);
const { request_id } = tokenObject;
nexmo.verify.search(request_id, async (err, result) => {
if (err) {
res.json({ error: err });
} else {
res.json(result);
}
});
});
module.exports = {
path: '/verification',
handler: app
};
Quem já está familiarizado com applications Node e Express vai se sentir em casa com esse código.
Existem três pontos finais, /send, /verify e /auth-check estão disponíveis. Veja a seguir o que eles fazem:
/send- Recebe o número de telefone do usuário do formulário e cria uma nova solicitação de verificação usando o Nexmo Node SDK. Orequest_iddesta nova solicitação é convertido em um JSON Web Token assinado e enviado de volta ao front-end./verify- Recebe o JWT e o código PIN do usuário. O JWT é decodificado e orequest_idé extraído e, em seguida, enviado junto com o PIN para a Verify API para verificar se eles coincidem. Se coincidirem, a verificação é bem-sucedida!/check-auth- É um método auxiliar usado para verificar se já existe uma autorização para orequest_id, para que os usuários não precisem digitar seu número todas as vezes.
Ao contrário de um aplicativo Express típico, no qual você definiria server.listen uma porta, vamos exportar todo o nosso script como um módulo para que o Nuxt possa referenciá-lo.
No entanto, antes que isso funcione, crie um arquivo chamado .env na raiz do seu diretório de trabalho e adicione o seguinte:
apiKey = "YOUR NEXMO API KEY GOES HERE"
apiSecret = "YOUR NEXMO API SECRET GOES HERE"
jwtSecret = "ANY RANDOM STRING OF LETTERS & NUMBERS GOES HERE"Observação: certifique-se de manter o arquivo .env fora de quaisquer repositórios do GitHub, adicionando também um .gitignore ao seu diretório de trabalho. Você pode copiar o que criei para este projeto.
Instalação de middleware do lado do servidor
Para tornar nossos novos endpoints acessíveis em nosso aplicativo Nuxt, é preciso seguir duas etapas.
O primeiro passo é registrar o script como middleware em nuxt.config.js. Para isso, adicione a seguinte linha:
serverMiddleware: ['~/node-scripts/verification_api'];Se você precisar verificar exatamente onde isso deve ser colocado, pode consultar o exemplo no GitHub.
O segundo passo é compilar o aplicativo para que essas novas rotas (/verification/send e /verification/verify) fiquem disponíveis. Para isso, execute este comando no seu terminal:
Assim que isso estiver concluído, reinicie o servidor de desenvolvimento:
Acesse https://localhost:3000 e insira seu número no formulário. Não se esqueça de inserir também o código do país.
Ao clicar em “Enviar-me um código de verificação”, você receberá um SMS com um código PIN de 6 dígitos. Você também notará que a tela mudou e agora está aguardando o código PIN.

A inserção do PIN concluirá a verificação e, se tudo correr bem, o aplicativo tentará redirecionar para uma rota chamada /secret.
...que ainda não existe. Vamos criá-lo e garantir que seja seguro usando algum middleware do Nuxt.
Protegendo nossa página secreta
Crie uma nova pasta dentro da pages pasta, nomeie-a secret e adicione um arquivo chamado index.vue a ela.
Abra index.vue no seu editor e adicione o seguinte código:
<template>
<div>
<div class="row center-align">
<h1>Secret Area</h1>
</div>
<div class="row center-align">
<button v-on:click.stop.prevent="logout" class="waves-effect waves-light btn red"><i class="material-icons left">account_box</i>Logout</button>
</div>
</div>
</template>
<script>
export default {
middleware: 'check_auth',
methods: {
// Clicking log out triggers this function that wipes out any pre-existing tokens
logout: function() {
this.$store.commit('INVALIDATE_TOKEN');
this.$nuxt.$router.replace({ path: '/' });
}
}
};
</script>Você vai notar que esse código faz referência a um middleware chamado check_auth. Esse middleware será chamado sempre que este arquivo for solicitado e, portanto, pode ser usado para proteger a página.
Criar o middleware de autenticação
Na middleware pasta, crie um novo arquivo chamado check_auth.js e abra-o no seu editor. Adicione o seguinte código:
import axios from 'axios';
export default function({ store, route, redirect }) {
if (store.getters.isVerified) {
const token = store.getters.isVerified;
axios
.post('/verification/auth-check', {
token
})
.then(res => {
const { data } = res;
if (!data.error_text && data.checks[0].status === 'VALID') {
console.log('valid, allowing access');
redirect('/secret');
} else {
console.log('invalid, redirecting...');
redirect('/');
}
})
.catch(err => console.log(err));
} else {
redirect('/');
}
}
Este arquivo será chamado sempre que /secret for solicitado como uma rota, mas antes que o HTML seja renderizado e enviado ao navegador. Nesse momento, ocorrem as seguintes etapas:
O store do VueX é verificado por meio de um método getter para verificar se um JSON Web Token já foi retornado pela nossa API de verificação
Se for o caso, nós o passamos para o
/auth-checkponto de extremidade para verificar se essa autenticação ainda está ativaSe não existir, ou se a autenticação não for válida, redirecionamos a solicitação de volta para o formulário de login.
Se o seu servidor de desenvolvimento ainda estiver em execução neste momento, reinicie-o para que o middleware seja registrado corretamente e repita o processo de login. Desta vez, se a verificação for bem-sucedida, você deverá ver a /secret página.
(A página secreta que criei para o aplicativo de exemplo é mais divertida do que esta. Você pode baixá-la aqui se quiser usá-la.)
Conclusão
O Nuxt nos permite pegar uma tecnologia que talvez já conheçamos, como o Vue, e adicionar elementos poderosos, como a autenticação por meio de middleware, sem precisar nos desviar para trabalhar com APIs separadas em servidores diferentes.
Obviamente, há limites para o quanto você gostaria de incluir no middleware do lado do servidor antes que a melhor opção seja, de fato, passar a desenvolver uma API externa. A decisão é sua, mas eu diria que qualquer coisa acima de 5 endpoints básicos já pode justificar o esforço. Se for menos do que isso, considere implementá-lo como middleware — especialmente se for apenas um proxy para chamadas a outra API, como neste exemplo.
Espero que isso tenha dado uma boa ideia do que pode ser feito, e saiba que essa abordagem funciona para muitas das APIs da Nexmo, não apenas para a Verify. Você poderia, com a mesma facilidade, fazer com que seu middleware do lado do servidor enviasse mensagens SMS em vez de verificar usuários.
Seja criativo e, se pensar em outros exemplos, fique à vontade para compartilhá-los conosco pelo nosso canal do Slack da Comunidade Nexmo.
Compartilhar:
Ex-diretor de Formação de Desenvolvedores da Vonage. Com experiência como desenvolvedor criativo, gerente de produto e organizador de hack days, Martyn atua como defensor da tecnologia desde 2012, tendo trabalhado anteriormente no setor de radiodifusão e em grandes gravadoras. Ele se dedica a capacitar e empoderar desenvolvedores em todo o mundo.