Integração de Autenticação OAuth2 em Aplicações Django Utilizando Redis

Configuração de Cache com Redis no Django

Para implementar o fluxo OAuth2 de forma segura, é fundamental armazenar o parâmetro state em um mecanismo de cache rápido e confiável. O Redis é a escolha ideal para essa tarefa no ecossistema Django.

Instalação e Configuração

Instale a biblioteca de integração através do pip:

pip install django-redis

Em seguida, adicione a configuração de cache ao arquivo settings.py do seu projeto:

CACHES = {
    "default": {
        "BACKEND": "django_redis.cache.RedisCache",
        "LOCATION": "redis://127.0.0.1:6379/1",
        "OPTIONS": {
            "CLIENT_CLASS": "django_redis.client.DefaultClient",
        },
    }
}

Certifique-se de que o serviço do Redis esteja ativo no seu servidor:

sudo systemctl start redis-server

Fluxo de Autenticação OAuth2

O processo de login de terceiros (neste caso, utilizando o AcWing como provedor de identidade) segue o padrão OAuth2 Authorization Code Grant. O fluxo envolve três entidades: o Navegador (Client), a Aplicação Django (Web) e o Servidor de Autorização (AcWing).

  1. O usuário clica no botão de login e o frontend solicita à aplicação Django a URL de autorização.
  2. O Django gera um state aleatório, armazena no Redis e retorna a URL completa com client_id, redirect_uri, scope e state.
  3. O frontend redireciona o usuário para o servidor de autorização.
  4. Após o consantimento do usuário, o servidor de autorização redireciona de volta para a redirect_uri com um code temporário e o state.
  5. A aplicação Django valida o state e troca o code por um access_token e um identificador único (openid).
  6. Com o access_token, o Django consulta a API do provedor para obter os dados do perfil (nome de usuário e avatar).
  7. A aplicação cria ou recupera o usuário no banco de dados local e inicia a sessão.

Atualização do Modelo de Dados

Para vincular usuários locais às suas identidades no provedor externo, precisamos adicionar um campo para o identificador único (provider_uid ou openid) no modelo de perfil.

from django.db import models
from django.contrib.auth.models import User

class Player(models.Model):
    user = models.OneToOneField(User, on_delete=models.CASCADE)
    avatar_url = models.URLField(max_length=256, blank=True)
    provider_uid = models.CharField(max_length=50, blank=True, null=True, unique=True)

    def __str__(self):
        return self.user.username

Aplique as migrações para atualizar o esquema do banco de dados:

python manage.py makemigrations
python manage.py migrate

Geração da URL de Autorização

Criaremos um endpoint para gerar a URL de reidrecionamento. Utilizaremos o módulo secrets para gerar tokens criptograficamente seguros e urllib.parse para construir a URL de forma adequada.

Configuração de Rotas

from django.urls import path
from . import views

urlpatterns = [
    path("request/", views.request_authorization, name="oauth_request"),
    path("callback/", views.handle_oauth_callback, name="oauth_callback"),
]

View de Solicitação

import secrets
from urllib.parse import urlencode
from django.http import JsonResponse
from django.core.cache import cache

def request_authorization(request):
    client_id = "SEU_APP_ID"
    redirect_uri = "https://seudominio.com/auth/callback/"
    scope = "userinfo"
    
    # Gera um state hex de 8 caracteres
    state = secrets.token_hex(4)
    cache.set(f"oauth_state_{state}", True, timeout=7200)

    auth_endpoint = "https://www.acwing.com/third_party/api/oauth2/web/authorize/"
    query_params = urlencode({
        "appid": client_id,
        "redirect_uri": redirect_uri,
        "scope": scope,
        "state": state
    })

    return JsonResponse({
        "status": "ok",
        "auth_url": f"{auth_endpoint}?{query_params}"
    })

Integração com Frontend

No lado do cliente, utilizaremos a API fetch para obter a URL e redirecionar o navegador:

function initiateOAuthLogin() {
    fetch('/auth/request/')
        .then(response => response.json())
        .then(data => {
            if (data.status === 'ok') {
                window.location.href = data.auth_url;
            } else {
                console.error("Falha ao obter URL de autorização");
            }
        })
        .catch(error => console.error("Erro na requisição:", error));
}

Processamento do Calblack e Troca de Tokens

Quando o provedor redireciona o usuário de volta, precisamos validar o state, trocar o código pelo token de acesso e buscar as informações do usuário.

import requests
import secrets
from django.shortcuts import redirect
from django.core.cache import cache
from django.contrib.auth import login
from django.contrib.auth.models import User
from .models import Player

def handle_oauth_callback(request):
    code = request.GET.get("code")
    state = request.GET.get("state")

    # Validação do state para prevenir ataques CSRF
    cache_key = f"oauth_state_{state}"
    if not cache.get(cache_key):
        return redirect("home")
    
    cache.delete(cache_key)

    # Troca do código pelo access_token
    token_endpoint = "https://www.acwing.com/third_party/api/oauth2/access_token/"
    token_payload = {
        "appid": "SEU_APP_ID",
        "secret": "SEU_APP_SECRET",
        "code": code
    }

    token_response = requests.get(token_endpoint, params=token_payload).json()
    access_token = token_response.get("access_token")
    provider_uid = token_response.get("openid")

    # Verifica se o usuário já existe
    try:
        player = Player.objects.get(provider_uid=provider_uid)
        login(request, player.user)
        return redirect("home")
    except Player.DoesNotExist:
        pass

    # Busca informações do perfil
    info_endpoint = "https://www.acwing.com/third_party/api/meta/identity/getinfo/"
    info_payload = {
        "access_token": access_token,
        "openid": provider_uid
    }
    user_info = requests.get(info_endpoint, params=info_payload).json()
    
    base_username = user_info.get("username")
    avatar = user_info.get("photo")

    # Tratamento de colisão de nomes de usuário
    final_username = base_username
    while User.objects.filter(username=final_username).exists():
        final_username = f"{base_username}_{secrets.randbelow(1000)}"

    # Criação do novo usuário e perfil
    new_user = User.objects.create_user(username=final_username)
    Player.objects.create(user=new_user, avatar_url=avatar, provider_uid=provider_uid)

    login(request, new_user)
    return redirect("home")

Com a implementação das views, modelos e rotas, o fluxo de autenticação de terceiros está completo e pronto para ser reiniciado no servidor de aplicação (como uWSGI ou Gunicorn).

Tags: Django OAuth2 Redis Python javascript

Publicado em 10-25 20:36