Jwt Cookie Auth
Autenticação JWT via cookie httpOnly com djangorestframework-simplejwt — para APIs Django consumidas pela própria SPA do produto. Invoque ao criar endpoint de login/logout/refresh, ou ao configurar autenticação num projeto Django.
JWT via cookie httpOnly (SimpleJWT)
Usar djangorestframework-simplejwt, mas o par de tokens nunca volta no corpo da resposta para
consumo do browser — vai em cookies httpOnly + Secure + SameSite. Isso fecha a superfície de
XSS que rouba token de localStorage. O preço é CSRF, que se resolve com o padrão de cookie CSRF
não-httpOnly + header enviado pelo frontend.
Cliente non-browser (mobile, script server-to-server) usa o fluxo alternativo:
Authorization: Bearer <token>obtido de um endpoint que retorna o token no corpo em vez de cookie — documente explicitamente por que esse endpoint é a exceção quando ele existir.
# config/settings/base.py
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': [
'apps.auth.authentication.CookieJWTAuthentication',
],
'DEFAULT_PERMISSION_CLASSES': [
'rest_framework.permissions.IsAuthenticated',
],
}
SIMPLE_JWT = {
'ACCESS_TOKEN_LIFETIME': timedelta(minutes=15),
'REFRESH_TOKEN_LIFETIME': timedelta(days=7),
'ROTATE_REFRESH_TOKENS': True,
'BLACKLIST_AFTER_ROTATION': True,
}
AUTH_COOKIE_ACCESS = 'access_token'
AUTH_COOKIE_REFRESH = 'refresh_token'
AUTH_COOKIE_SECURE = not DEBUG # True em prod, permite http local
AUTH_COOKIE_SAMESITE = 'Lax' # 'None' só se frontend e API vivem em domínios diferentes (exige Secure=True)
CSRF_COOKIE_HTTPONLY = False # o frontend precisa ler este pra ecoar no header
CSRF_HEADER_NAME = 'HTTP_X_CSRFTOKEN'
CORS_ALLOW_CREDENTIALS = True # obrigatório para o browser enviar/receber os cookies de auth
Com auth via cookie httpOnly, CORS_ALLOWED_ORIGINS (ver backend/django-drf → env-vars) nunca pode
ser * — o browser recusa credenciais (cookies) em requests com origem coringa. Liste os domínios
exatos do frontend.
# apps/auth/authentication.py
from rest_framework_simplejwt.authentication import JWTAuthentication
from django.conf import settings
class CookieJWTAuthentication(JWTAuthentication):
"""Lê o access token do cookie httpOnly; cai pro header Bearer se o cookie não existir
(clientes non-browser: mobile, integração server-to-server)."""
def authenticate(self, request):
raw_token = request.COOKIES.get(settings.AUTH_COOKIE_ACCESS)
if raw_token is None:
return super().authenticate(request)
validated_token = self.get_validated_token(raw_token)
return self.get_user(validated_token), validated_token
# apps/auth/views.py
from django.conf import settings
from rest_framework_simplejwt.views import TokenObtainPairView, TokenRefreshView
from rest_framework_simplejwt.serializers import TokenRefreshSerializer
class CookieTokenObtainPairView(TokenObtainPairView):
def finalize_response(self, request, response, *args, **kwargs):
if response.status_code == 200:
access, refresh = response.data.pop('access'), response.data.pop('refresh')
_set_auth_cookies(response, access, refresh)
return super().finalize_response(request, response, *args, **kwargs)
class CookieTokenRefreshView(TokenRefreshView):
def post(self, request, *args, **kwargs):
request.data['refresh'] = request.COOKIES.get(settings.AUTH_COOKIE_REFRESH)
return super().post(request, *args, **kwargs)
def finalize_response(self, request, response, *args, **kwargs):
if response.status_code == 200:
access = response.data.pop('access')
refresh = response.data.pop('refresh', request.COOKIES.get(settings.AUTH_COOKIE_REFRESH))
_set_auth_cookies(response, access, refresh)
return super().finalize_response(request, response, *args, **kwargs)
def _set_auth_cookies(response, access, refresh):
common = dict(httponly=True, secure=settings.AUTH_COOKIE_SECURE, samesite=settings.AUTH_COOKIE_SAMESITE)
response.set_cookie(settings.AUTH_COOKIE_ACCESS, access, **common)
response.set_cookie(settings.AUTH_COOKIE_REFRESH, refresh, path='/auth/token/refresh/', **common)
class LogoutView(APIView):
def post(self, request):
response = Response(status=204)
response.delete_cookie(settings.AUTH_COOKIE_ACCESS)
response.delete_cookie(settings.AUTH_COOKIE_REFRESH, path='/auth/token/refresh/')
return response
# apps/auth/urls.py
from django.urls import path
from apps.auth.views import CookieTokenObtainPairView, CookieTokenRefreshView, LogoutView
urlpatterns = [
path('token/', CookieTokenObtainPairView.as_view()),
path('token/refresh/', CookieTokenRefreshView.as_view()),
path('logout/', LogoutView.as_view()),
]
Regras inegociáveis
- JWT em cookie httpOnly + Secure + SameSite, nunca em
localStorage/sessionStorage. HeaderBearersó para clientes non-browser — documente a exceção quando usada. - Todo endpoint
POST/PUT/PATCH/DELETEchamado pelo cookie exige o headerX-CSRFTokenno request — o frontend lê o valor do cookiecsrftoken(não-httpOnly) e ecoa no header. GETs não precisam. SameSite='None'só quando frontend e API estão em domínios diferentes de fato; nesse casoAUTH_COOKIE_SECUREé obrigatoriamenteTrue(browsers rejeitamNonesemSecure).
Skills relacionadas
- Models, serializers, views:
backend/django-drf