Programador Leigo
API 11 min leitura 15 MAR 2026

Autenticação de APIs com tokens e JWT em Python

Proteja suas APIs com tokens de acesso e JWT. Autenticação stateless explicada na prática.


Por que APIs precisam de autenticação?

Uma API sem autenticação é como uma casa com a porta aberta. Qualquer pessoa pode acessar, modificar ou deletar dados. Em um cenário real, você precisa controlar:

  • Quem está acessando: identificar o usuário por trás da requisição
  • O que pode acessar: diferentes usuários podem ter diferentes permissões
  • Rastreabilidade: saber quem fez o quê e quando
  • Proteção contra abusos: limitar requisições por usuário

Sem autenticação, sua API fica vulnerável a acessos indevidos, vazamento de dados e ataques automatizados.


Tipos de autenticação para APIs

Existem várias formas de autenticar uma API. Cada uma tem seus prós e contras:

Tipo Como funciona Quando usar
API Key Chave fixa enviada em cada requisição APIs simples, integrações entre serviços
Token (Bearer) Token gerado após login, enviado no header APIs com login de usuários
JWT Token autocontido com dados do usuário APIs stateless, microsserviços
OAuth 2.0 Delegação de acesso via provedor externo Login com Google, GitHub, etc.
Basic Auth Usuário e senha codificados em Base64 Testes, APIs internas

Neste artigo, vamos focar em API Keys e JWT, que são as abordagens mais comuns para APIs Python.


Autenticação com API Key

A forma mais simples de proteger uma API. O cliente envia uma chave fixa no header de cada requisição.

from flask import Flask, jsonify, request
from functools import wraps

app = Flask(__name__)

# em producao, use variaveis de ambiente
API_KEYS = {
    "chave-cliente-a-123": "Cliente A",
    "chave-cliente-b-456": "Cliente B"
}


def requer_api_key(f):
    @wraps(f)
    def decorada(*args, **kwargs):
        api_key = request.headers.get("X-API-Key")

        if not api_key:
            return jsonify({"erro": "API key nao fornecida"}), 401

        if api_key not in API_KEYS:
            return jsonify({"erro": "API key invalida"}), 401

        return f(*args, **kwargs)
    return decorada


@app.route("/api/dados")
@requer_api_key
def dados_protegidos():
    api_key = request.headers.get("X-API-Key")
    cliente = API_KEYS[api_key]
    return jsonify({"mensagem": f"Acesso autorizado para {cliente}"})

Testando:

# sem api key (401)
curl http://127.0.0.1:5000/api/dados

# com api key valida (200)
curl http://127.0.0.1:5000/api/dados -H "X-API-Key: chave-cliente-a-123"

API Keys são simples, mas têm limitações: não expiram automaticamente, não carregam informações do usuário e, se vazarem, o acesso é comprometido até a troca manual.


O que é JWT?

JWT (JSON Web Token) é um padrão aberto (RFC 7519) para transmitir informações de forma segura entre duas partes. Um token JWT é composto por três partes separadas por pontos:

eyJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoxfQ.DGz5QTk_EaNILM7g19hNLQ
|_____________________||___________________||________________________|
       HEADER                PAYLOAD               ASSINATURA

Header

Contém o algoritmo de assinatura e o tipo do token:

{
    "alg": "HS256",
    "typ": "JWT"
}

Payload

Contém os dados (claims) que você quer transmitir:

{
    "user_id": 1,
    "email": "maria@email.com",
    "exp": 1711036800
}

Assinatura

É o hash do header + payload usando uma chave secreta. Garante que o token não foi alterado:

HMACSHA256(
    base64(header) + "." + base64(payload),
    chave_secreta
)

Importante: o payload do JWT é apenas codificado em Base64, não é criptografado. Qualquer pessoa pode ler o conteúdo. A assinatura garante apenas que o token não foi modificado. Nunca coloque senhas ou dados sensíveis no payload.


Instalando o PyJWT

pip install pyjwt

Gerando e verificando tokens:

import jwt
from datetime import datetime, timedelta, timezone

CHAVE_SECRETA = "minha-chave-super-secreta"

# gerar token
payload = {
    "user_id": 1,
    "email": "maria@email.com",
    "exp": datetime.now(timezone.utc) + timedelta(hours=1)
}
token = jwt.encode(payload, CHAVE_SECRETA, algorithm="HS256")
print(token)

# verificar token
try:
    dados = jwt.decode(token, CHAVE_SECRETA, algorithms=["HS256"])
    print(dados)  # {"user_id": 1, "email": "maria@email.com", "exp": ...}
except jwt.ExpiredSignatureError:
    print("Token expirado")
except jwt.InvalidTokenError:
    print("Token invalido")

O campo exp define quando o token expira. O PyJWT verifica automaticamente e levanta ExpiredSignatureError se o token estiver vencido.


Implementando JWT com Flask

Vamos criar uma API completa com registro, login e rotas protegidas.

Configuração inicial

from flask import Flask, jsonify, request
from functools import wraps
from datetime import datetime, timedelta, timezone
from werkzeug.security import generate_password_hash, check_password_hash
import jwt

app = Flask(__name__)
app.config["SECRET_KEY"] = "troque-por-uma-chave-segura"

# banco em memoria (em producao, use um banco real)
usuarios = []
proximo_id = 1

Rota de registro

@app.route("/api/registro", methods=["POST"])
def registrar():
    global proximo_id
    dados = request.get_json()

    if not dados or "email" not in dados or "senha" not in dados:
        return jsonify({"erro": "Email e senha sao obrigatorios"}), 400

    # verificar se email ja existe
    if any(u["email"] == dados["email"] for u in usuarios):
        return jsonify({"erro": "Email ja cadastrado"}), 409

    novo_usuario = {
        "id": proximo_id,
        "email": dados["email"],
        "senha_hash": generate_password_hash(dados["senha"]),
        "nome": dados.get("nome", "")
    }

    usuarios.append(novo_usuario)
    proximo_id += 1

    return jsonify({
        "mensagem": "Usuario criado com sucesso",
        "id": novo_usuario["id"]
    }), 201

Rota de login (gera o token)

@app.route("/api/login", methods=["POST"])
def login():
    dados = request.get_json()

    if not dados or "email" not in dados or "senha" not in dados:
        return jsonify({"erro": "Email e senha sao obrigatorios"}), 400

    usuario = next((u for u in usuarios if u["email"] == dados["email"]), None)

    if usuario is None or not check_password_hash(usuario["senha_hash"], dados["senha"]):
        return jsonify({"erro": "Email ou senha incorretos"}), 401

    # gerar token JWT
    token = jwt.encode(
        {
            "user_id": usuario["id"],
            "email": usuario["email"],
            "exp": datetime.now(timezone.utc) + timedelta(hours=24)
        },
        app.config["SECRET_KEY"],
        algorithm="HS256"
    )

    return jsonify({"token": token}), 200

Decorador para proteger rotas

def token_obrigatorio(f):
    @wraps(f)
    def decorada(*args, **kwargs):
        token = None

        # buscar token no header Authorization
        auth_header = request.headers.get("Authorization")
        if auth_header and auth_header.startswith("Bearer "):
            token = auth_header.split(" ")[1]

        if not token:
            return jsonify({"erro": "Token nao fornecido"}), 401

        try:
            dados = jwt.decode(
                token,
                app.config["SECRET_KEY"],
                algorithms=["HS256"]
            )
            usuario_atual = next(
                (u for u in usuarios if u["id"] == dados["user_id"]),
                None
            )
            if usuario_atual is None:
                return jsonify({"erro": "Usuario nao encontrado"}), 401

        except jwt.ExpiredSignatureError:
            return jsonify({"erro": "Token expirado"}), 401
        except jwt.InvalidTokenError:
            return jsonify({"erro": "Token invalido"}), 401

        return f(usuario_atual, *args, **kwargs)
    return decorada

Rotas protegidas

@app.route("/api/perfil")
@token_obrigatorio
def perfil(usuario_atual):
    return jsonify({
        "id": usuario_atual["id"],
        "email": usuario_atual["email"],
        "nome": usuario_atual["nome"]
    })


@app.route("/api/dados-secretos")
@token_obrigatorio
def dados_secretos(usuario_atual):
    return jsonify({
        "mensagem": f"Ola, {usuario_atual['nome']}! Estes sao seus dados secretos.",
        "dados": [1, 2, 3, 4, 5]
    })

Fluxo completo de autenticação

Testando o fluxo passo a passo:

# 1. registrar usuario
curl -X POST http://127.0.0.1:5000/api/registro \
  -H "Content-Type: application/json" \
  -d '{"email": "maria@email.com", "senha": "minhasenha123", "nome": "Maria"}'

# 2. fazer login (recebe o token)
curl -X POST http://127.0.0.1:5000/api/login \
  -H "Content-Type: application/json" \
  -d '{"email": "maria@email.com", "senha": "minhasenha123"}'

# 3. acessar rota protegida (substitua pelo token recebido)
curl http://127.0.0.1:5000/api/perfil \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6..."

# 4. sem token (recebe 401)
curl http://127.0.0.1:5000/api/perfil

Refresh Tokens

Tokens JWT com vida curta (15 minutos a 1 hora) são mais seguros, mas obrigam o usuário a fazer login frequentemente. Refresh tokens resolvem esse problema.

Como funciona

  1. No login, o servidor gera dois tokens: um access token (curta duração) e um refresh token (longa duração)
  2. O cliente usa o access token para acessar rotas protegidas
  3. Quando o access token expira, o cliente envia o refresh token para obter um novo access token
  4. O refresh token é armazenado de forma segura (httpOnly cookie ou storage seguro)
@app.route("/api/login", methods=["POST"])
def login_com_refresh():
    dados = request.get_json()
    usuario = next((u for u in usuarios if u["email"] == dados["email"]), None)

    if usuario is None or not check_password_hash(usuario["senha_hash"], dados["senha"]):
        return jsonify({"erro": "Credenciais invalidas"}), 401

    # access token: curta duracao
    access_token = jwt.encode(
        {
            "user_id": usuario["id"],
            "tipo": "access",
            "exp": datetime.now(timezone.utc) + timedelta(minutes=30)
        },
        app.config["SECRET_KEY"],
        algorithm="HS256"
    )

    # refresh token: longa duracao
    refresh_token = jwt.encode(
        {
            "user_id": usuario["id"],
            "tipo": "refresh",
            "exp": datetime.now(timezone.utc) + timedelta(days=30)
        },
        app.config["SECRET_KEY"],
        algorithm="HS256"
    )

    return jsonify({
        "access_token": access_token,
        "refresh_token": refresh_token
    }), 200


@app.route("/api/refresh", methods=["POST"])
def refresh():
    dados = request.get_json()
    refresh_token = dados.get("refresh_token")

    if not refresh_token:
        return jsonify({"erro": "Refresh token nao fornecido"}), 400

    try:
        payload = jwt.decode(
            refresh_token,
            app.config["SECRET_KEY"],
            algorithms=["HS256"]
        )

        if payload.get("tipo") != "refresh":
            return jsonify({"erro": "Token invalido"}), 401

        novo_access_token = jwt.encode(
            {
                "user_id": payload["user_id"],
                "tipo": "access",
                "exp": datetime.now(timezone.utc) + timedelta(minutes=30)
            },
            app.config["SECRET_KEY"],
            algorithm="HS256"
        )

        return jsonify({"access_token": novo_access_token}), 200

    except jwt.ExpiredSignatureError:
        return jsonify({"erro": "Refresh token expirado, faca login novamente"}), 401
    except jwt.InvalidTokenError:
        return jsonify({"erro": "Token invalido"}), 401

Boas práticas de segurança

1. Nunca exponha a chave secreta

import os

# ERRADO: chave fixa no codigo
app.config["SECRET_KEY"] = "minha-chave"

# CORRETO: variavel de ambiente
app.config["SECRET_KEY"] = os.environ.get("SECRET_KEY")

if not app.config["SECRET_KEY"]:
    raise RuntimeError("SECRET_KEY nao definida")

2. Use senhas fortes para a chave secreta

import secrets

# gerar uma chave segura
chave = secrets.token_hex(32)
print(chave)  # 64 caracteres hexadecimais

3. Defina expiração nos tokens

# ERRADO: token sem expiracao
token = jwt.encode({"user_id": 1}, chave, algorithm="HS256")

# CORRETO: token com expiracao
token = jwt.encode(
    {"user_id": 1, "exp": datetime.now(timezone.utc) + timedelta(hours=1)},
    chave,
    algorithm="HS256"
)

4. Use HTTPS em produção

Tokens trafegam pelo header HTTP. Sem HTTPS, qualquer intermediário pode interceptá-los. Em desenvolvimento, HTTP é aceitável, mas em produção, HTTPS é obrigatório.

5. Não armazene dados sensíveis no JWT

# ERRADO: senha no payload
payload = {"user_id": 1, "senha": "abc123"}

# CORRETO: apenas identificadores
payload = {"user_id": 1, "email": "maria@email.com"}

6. Valide todos os campos

# sempre verifique se o usuario ainda existe
# (pode ter sido deletado apos a emissao do token)
usuario = buscar_usuario_por_id(payload["user_id"])
if usuario is None:
    return jsonify({"erro": "Usuario nao encontrado"}), 401

Comparação: sessões vs JWT

Aspecto Sessões (cookies) JWT
Estado Stateful (servidor guarda sessão) Stateless (tudo está no token)
Armazenamento Servidor (memória/banco) Cliente (header)
Escalabilidade Requer sessão compartilhada entre servidores Escala naturalmente
Revogação Fácil (apaga a sessão) Difícil (precisa de blacklist)
Uso ideal Sites com login APIs, microsserviços, SPAs

Resumo

Conceito Descrição
API Key Chave fixa para autenticação simples
JWT Token autocontido com header, payload e assinatura
PyJWT Biblioteca Python para criar e verificar JWTs
Decorador Função que protege rotas verificando o token
Refresh Token Token de longa duração para renovar access tokens
Boas práticas Variáveis de ambiente, expiração, HTTPS, sem dados sensíveis

Com autenticação implementada, sua API está protegida. No próximo artigo, vamos aprender a testar e documentar APIs para que outros desenvolvedores consigam integrá-las facilmente.

Continue lendo

Compartilhar