DEV Community

Cover image for Criando um Sistema de OTP com Azure Serverless em TypeScript
Cláudio Filipe Lima Rapôso
Cláudio Filipe Lima Rapôso

Posted on

Criando um Sistema de OTP com Azure Serverless em TypeScript

A autenticação baseada em códigos de uso único (OTPs) é uma estratégia eficaz para reforçar a segurança das aplicações. Neste artigo, vamos desenvolver uma solução simples utilizando a infraestrutura serverless da Azure e o poder do TypeScript para gerar e validar OTPs. A proposta é mostrar, passo a passo, a criação das funções, a integração com serviços de envio de mensagens e o deploy na Azure.


1. Arquitetura da Solução

A solução é composta pelos seguintes componentes:

Diagrama de Sequência

  • Azure Functions (TypeScript): Responsáveis por gerar e validar os códigos via HTTP Trigger.
  • Azure Key Vault (ou variáveis de ambiente): Armazena de forma segura o segredo utilizado na criação dos OTPs.
  • Serviço de Envio de Mensagens: Utilizado para disparar o OTP por e-mail (por exemplo, via SendGrid).
  • Armazenamento (opcional): Pode ser usado para registrar tentativas e controlar expirações, com Azure Table Storage ou Cosmos DB.
  • Frontend (opcional): Uma interface simples para solicitar e validar o OTP.

Essa arquitetura permite a escalabilidade automática e o gerenciamento simplificado da infraestrutura.


2. Configuração do Ambiente de Desenvolvimento

Para iniciar, siga estes passos:

  1. Instale o Node.js e o Azure Functions Core Tools.
  2. Crie um novo projeto de Azure Functions com TypeScript:
   func init meu-projeto-otp --typescript
   cd meu-projeto-otp
   func new --name gerarOtp --template "HTTP trigger" --authlevel "anonymous"
   func new --name validarOtp --template "HTTP trigger" --authlevel "anonymous"
Enter fullscreen mode Exit fullscreen mode
  1. Instale as dependências necessárias:
   npm install otplib @sendgrid/mail
Enter fullscreen mode Exit fullscreen mode
  1. Configure as variáveis de ambiente (utilize o arquivo local.settings.json ou defina-as no portal da Azure). Por exemplo:
   {
     "IsEncrypted": false,
     "Values": {
       "AzureWebJobsStorage": "UseDevelopmentStorage=true",
       "FUNCTIONS_WORKER_RUNTIME": "node",
       "OTP_SECRET": "seuSegredoSuperSeguro",
       "SENDGRID_API_KEY": "suaChaveSendGrid"
     }
   }
Enter fullscreen mode Exit fullscreen mode

3. Função para Geração de OTP

Nesta função, a biblioteca otplib é utilizada para gerar um código OTP com validade de 5 minutos, enquanto o SendGrid é acionado para enviar o código por e-mail. O código foi escrito de forma clara e objetiva.

// src/gerarOtp/index.ts
import { AzureFunction, Context, HttpRequest } from "@azure/functions";
import { totp } from "otplib";
import sgMail from "@sendgrid/mail";

const gerarOtp: AzureFunction = async (context: Context, req: HttpRequest): Promise<void> => {
  const email: string | undefined = req.body?.email;
  if (!email) {
    context.res = { status: 400, body: "E-mail é obrigatório." };
    return;
  }

  // Recupera o segredo para geração do OTP
  const secret: string = process.env.OTP_SECRET || "";
  totp.options = { step: 300 }; // Código válido por 5 minutos
  const token: string = totp.generate(secret);

  // Configura o SendGrid com a API key
  sgMail.setApiKey(process.env.SENDGRID_API_KEY || "");
  const msg = {
    to: email,
    from: "nao-responda@seusistema.com",
    subject: "Seu código OTP",
    text: `Seu OTP é: ${token}\nExpira em 5 minutos.`
  };

  try {
    await sgMail.send(msg);
    context.res = { status: 200, body: "OTP enviado com sucesso." };
  } catch (error) {
    context.log("Erro ao enviar OTP:", error);
    context.res = { status: 500, body: `Erro ao enviar OTP: ${error}` };
  }
};

export default gerarOtp;
Enter fullscreen mode Exit fullscreen mode

4. Função para Validação de OTP

Nesta função de validação, o código informado pelo usuário é comparado com o código gerado com o mesmo segredo. A resposta é enviada de acordo com o resultado da validação.

// src/validarOtp/index.ts
import { AzureFunction, Context, HttpRequest } from "@azure/functions";
import { totp } from "otplib";

const validarOtp: AzureFunction = async (context: Context, req: HttpRequest): Promise<void> => {
  const { email, codigo } = req.body;
  if (!email || !codigo) {
    context.res = { status: 400, body: "E-mail e código são obrigatórios." };
    return;
  }

  const secret: string = process.env.OTP_SECRET || "";
  const isValid: boolean = totp.check(codigo, secret);

  if (isValid) {
    context.res = { status: 200, body: "Código validado com sucesso. Acesso autorizado!" };
  } else {
    context.res = { status: 401, body: "Código inválido ou expirado." };
  }
};

export default validarOtp;
Enter fullscreen mode Exit fullscreen mode

5. Exemplo de Frontend para Teste

A seguir, um exemplo simples de interface em HTML e TypeScript para testar as funções de geração e validação do OTP.

HTML (index.html)

<!DOCTYPE html>
<html lang="pt-BR">
<head>
  <meta charset="UTF-8" />
  <title>Teste OTP</title>
  <style>
    body { font-family: Arial, sans-serif; max-width: 600px; margin: 40px auto; }
    form { margin-bottom: 20px; }
    input { padding: 8px; margin: 4px 0; width: 100%; }
    button { padding: 10px 20px; }
  </style>
</head>
<body>
  <h2>Solicitar OTP</h2>
  <form id="otpForm">
    <input type="email" id="email" placeholder="Seu e-mail" required />
    <button type="submit">Enviar OTP</button>
  </form>

  <h2>Validar OTP</h2>
  <form id="validarForm">
    <input type="text" id="codigo" placeholder="Digite o OTP" required />
    <button type="submit">Validar</button>
  </form>

  <script type="module" src="main.js"></script>
</body>
</html>

Enter fullscreen mode Exit fullscreen mode

TypeScript (main.ts)

const requestOTP = async (email: string): Promise<void> => {
  const response = await fetch("/api/gerarOtp", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ email })
  });
  const result = await response.text();
  alert(result);
};

const validarOTP = async (email: string, codigo: string): Promise<void> => {
  const response = await fetch("/api/validarOtp", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ email, codigo })
  });
  const result = await response.text();
  alert(result);
};

document.getElementById("otpForm")?.addEventListener("submit", (event) => {
  event.preventDefault();
  const email = (document.getElementById("email") as HTMLInputElement).value;
  requestOTP(email);
});

document.getElementById("validarForm")?.addEventListener("submit", (event) => {
  event.preventDefault();
  const email = (document.getElementById("email") as HTMLInputElement).value;
  const codigo = (document.getElementById("codigo") as HTMLInputElement).value;
  validarOTP(email, codigo);
});
Enter fullscreen mode Exit fullscreen mode

Compile o arquivo TypeScript usando o compilador (tsc) para gerar o main.js que será referenciado pelo HTML.


6. Deploy com Azure CLI

Para realizar o deploy da aplicação, utilize os seguintes comandos com o Azure CLI:

# Cria o grupo de recursos
az group create --name otp-rg --location eastus

# Cria a conta de armazenamento (lembre-se de utilizar um nome único)
az storage account create --name otparmazem --location eastus --resource-group otp-rg --sku Standard_LRS

# Cria a Function App utilizando o runtime Node.js
az functionapp create \
  --resource-group otp-rg \
  --consumption-plan-location eastus \
  --runtime node \
  --functions-version 4 \
  --name otp-func-app \
  --storage-account otparmazem

# Configure as variáveis de ambiente
az functionapp config appsettings set \
  --name otp-func-app \
  --resource-group otp-rg \
  --settings OTP_SECRET=seuSegredo SENDGRID_API_KEY=suaChave
Enter fullscreen mode Exit fullscreen mode

Esses comandos automatizam a criação dos recursos e a configuração do ambiente de forma segura.


7. Considerações Finais

  • Utilize HTTPS em todas as comunicações.
  • Gerencie os segredos com cuidado, preferencialmente usando o Azure Key Vault.
  • Implemente limites para o número de tentativas de validação para evitar ataques de força bruta.
  • Considere integrar soluções de monitoramento, como o Application Insights, para acompanhar o desempenho e registrar eventuais falhas.

A implementação apresentada demonstra como um sistema de OTP pode ser construído de forma simples e segura utilizando Azure Functions com TypeScript. Essa abordagem permite a criação de soluções escaláveis sem sobrecarregar a infraestrutura. Experimente expandir este exemplo, integrando recursos adicionais conforme as necessidades do seu projeto.


Curtiu?

Se quiser trocar ideia sobre IA, cloud e arquitetura, me segue nas redes:

Publico conteúdos técnicos direto do campo de batalha. E quando descubro uma ferramenta que economiza tempo e resolve bem, como essa, você fica sabendo também.

Top comments (0)