API Authentication, CORS, and Frontend Integration

Introduction

Single-page apps (Vue, React) on localhost:5173 calling Django on localhost:8000 hit CORS browser rules and need token-based auth instead of cookie-only session forms. This chapter configures django-cors-headers, compares Session vs Token vs JWT authentication for DRF, protects write endpoints, and outlines Nginx same-origin deployment for production.

Prerequisites

CORS Problem

Browsers block JavaScript from reading responses when:

  • Frontend origin: http://localhost:5173
  • API origin: http://127.0.0.1:8000

Different host or port = cross-origin. Server must send Access-Control-Allow-Origin.

Install django-cors-headers

bash
pip install django-cors-headers

settings/base.py:

Development:

python
CORS_ALLOWED_ORIGINS = [
    "http://localhost:5173",
    "http://127.0.0.1:5173",
]

Or permissive dev only:

python
# development.py only — never in production
CORS_ALLOW_ALL_ORIGINS = True

Production: explicit origins or same-domain Nginx proxy (no CORS needed).

python
CORS_ALLOWED_ORIGINS = [
    "https://app.example.com",
]
CORS_ALLOW_CREDENTIALS = True  # if using session cookies cross-subdomain

Authentication Options

MethodBest forHeader / cookie
SessionSame-site browser appSession cookie + CSRF
TokenMobile, simple APIsAuthorization: Token <key>
JWTSPAs, microservicesAuthorization: Bearer <jwt>

Session authentication (DRF built-in)

python
REST_FRAMEWORK = {
    "DEFAULT_AUTHENTICATION_CLASSES": [
        "rest_framework.authentication.SessionAuthentication",
        "rest_framework.authentication.BasicAuthentication",
    ],
}

SPA must send CSRF token with POST:

javascript
const csrftoken = document.cookie.match(/csrftoken=([^;]+)/)?.[1];
fetch("http://127.0.0.1:8000/api/v1/posts/", {
  method: "POST",
  credentials: "include",
  headers: {
    "Content-Type": "application/json",
    "X-CSRFToken": csrftoken,
  },
  body: JSON.stringify({ title: "Hi", slug: "hi", body: "x", published: true }),
});

Same-origin deploy avoids some CORS pain—see Nginx below.

Token Authentication

Enable authtoken app:

python
INSTALLED_APPS = [
    ...
    "rest_framework.authtoken",
]
bash
python manage.py migrate

REST_FRAMEWORK:

python
REST_FRAMEWORK = {
    "DEFAULT_AUTHENTICATION_CLASSES": [
        "rest_framework.authentication.TokenAuthentication",
        "rest_framework.authentication.SessionAuthentication",
    ],
    "DEFAULT_PERMISSION_CLASSES": [
        "rest_framework.permissions.IsAuthenticatedOrReadOnly",
    ],
}

Create token for user (shell or view):

python
from rest_framework.authtoken.models import Token
token = Token.objects.create(user=user)
print(token.key)

Obtain via API endpoint — add blog/urls_api.py:

python
from rest_framework.authtoken.views import obtain_auth_token
 
urlpatterns = [
    path("auth/token/", obtain_auth_token),
    path("", include(router.urls)),
]
bash
curl -X POST http://127.0.0.1:8000/api/v1/auth/token/ \
  -d "username=alice&password=pass12345"

Response: {"token":"abc123..."}

Authenticated request:

bash
curl http://127.0.0.1:8000/api/v1/posts/ \
  -H "Authorization: Token abc123..."

JWT with simplejwt (Concept)

For stateless SPAs:

bash
pip install djangorestframework-simplejwt
bash
curl -X POST http://127.0.0.1:8000/api/v1/auth/jwt/ \
  -H "Content-Type: application/json" \
  -d '{"username":"alice","password":"pass12345"}'

Use Authorization: Bearer <access>; refresh with refresh token before expiry.

Compare: Spring Security JWT, Flask JWT.

Permission Classes

blog/viewsets.py:

Import BasePermission from rest_framework.permissions.

Only authors edit/delete their posts; anyone reads published list (filter in get_queryset).

Frontend Fetch Example (Token)

javascript
const API = "http://127.0.0.1:8000/api/v1";
const token = localStorage.getItem("token");
 
async function loadPosts() {
  const res = await fetch(`${API}/posts/`, {
    headers: { Authorization: `Token ${token}` },
  });
  return res.json();
}

Vue/React axios interceptors attach the same header globally. Hello Code Vue track covers fetch, Vite proxy, and admin auth project with JWT guards.

Production: Same Origin via Nginx

Serve SPA and API under one domain—browser sees same origin, CORS optional.

Django URLs stay /api/v1/ — align Nginx location /api/ with your prefix.

See Nginx reverse proxy.

CSRF Exempt for Token APIs?

Token/JWT auth typically does not use CSRF—DRF Token auth bypasses CSRF when not using session. Do not @csrf_exempt session-based views carelessly.

Security Notes

TopicPractice
CORS_ALLOW_ALL_ORIGINSDev only
Token storagehttpOnly cookies vs localStorage XSS tradeoff
JWT expiryShort access token + refresh rotation
HTTPSRequired in production — Let's Encrypt

Test Authenticated API

Common Mistakes

MistakeSymptomFix
CORS middleware too lateBrowser CORS errorCorsMiddleware high in list
Missing Authorization header401Token/JWT on every write
Session API without CSRF403CSRF header or switch to Token
AllowAny on deleteOpen APIIsAuthenticated + object perms
Wildcard CORS + credentialsBrowser rejectsExplicit origins

Post-Chapter Checklist

  • django-cors-headers configured for dev frontend origin
  • Token or JWT auth protects POST/PUT/DELETE
  • IsAuthorOrReadOnly (or similar) on PostViewSet
  • curl/Postman can obtain token and create post
  • You understand same-origin Nginx vs CORS

FAQ## FAQ

Possible with SessionAuthentication + CSRF + credentials: "include"—same-site cookies preferred.

Revoke tokens?

Delete Token row or use JWT blocklist (simplejwt BLACKLIST app).

OAuth / social login?

django-allauth or python-social-auth—beyond this track.

Preflight OPTIONS?

corsheaders handles OPTIONS automatically.

GraphQL?

graphene-django—separate stack; REST track uses DRF.