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:
- 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:
- Instale o Node.js e o Azure Functions Core Tools.
- 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"
- Instale as dependências necessárias:
npm install otplib @sendgrid/mail
- 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"
}
}
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;
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;
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>
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);
});
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
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.