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

Cláudio Filipe Lima Rapôso @sertaoseracloud

About: At NTT DATA Europe & Latam, my role as a Systems Architect harnesses the power of Typescript, Java and Phyton to creating robust and scalable solutions.

Location:
Brazil
Joined:
Jan 10, 2025

Criando um Sistema de OTP com Azure Serverless em TypeScript

Publish Date: Jun 23
1 0

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.

Comments 0 total

    Add comment