Skip to main content
Take a close look at possible CSRF / XSRF vulnerabilities on your own websites. They’re the worst kind of vulnerability — very easy to exploit by attackers, yet not so intuitively easy to understand for software developers, at least until you’ve been bitten by one.Jeff Atwood
When building JavaScript clients that interact with your Web API, you need to consider authentication policies, CSRF token handling, and CORS configuration.

JavaScript Clients

The authentication approach you choose depends on whether your JavaScript client runs in the same context as your API.

Same-Origin Requests

AJAX requests made within the same context as the API typically use SessionAuthentication:
// Assuming user is already logged in via Django session
fetch('/api/users/', {
  method: 'GET',
  credentials: 'same-origin',  // Include session cookie
  headers: {
    'Accept': 'application/json'
  }
})
.then(response => response.json())
.then(data => console.log(data));
Once a user has logged in, any AJAX requests can be authenticated using the same session-based authentication that’s used for the rest of the website.

Cross-Origin Requests

AJAX requests made from a different site typically need non-session-based authentication:
// Using token authentication from a different domain
fetch('https://api.example.com/users/', {
  method: 'GET',
  headers: {
    'Authorization': 'Token 9944b09199c62bcf9418ad846dd0e4bbdfc6ee4b',
    'Accept': 'application/json'
  }
})
.then(response => response.json())
.then(data => console.log(data));

CSRF Protection

Cross-Site Request Forgery (CSRF) is an attack that occurs when a malicious site can cause a user’s browser to perform unwanted actions on a trusted site where the user is authenticated.

How CSRF Attacks Work

  1. User logs into trusted-site.com
  2. User visits malicious-site.com (without logging out)
  3. Malicious site triggers a request to trusted-site.com
  4. Request includes the user’s session cookie automatically
  5. Trusted site processes the request as if the user intended it

Guarding Against CSRF

To protect against CSRF attacks, follow these guidelines:
Ensure that ‘safe’ HTTP operations cannot alter server-side state:
  • GET - Read data only
  • HEAD - Read metadata only
  • OPTIONS - Read available options only
class UserViewSet(viewsets.ModelViewSet):
    # Safe methods (GET, HEAD, OPTIONS) don't modify data
    # Unsafe methods (POST, PUT, PATCH, DELETE) require CSRF token
    queryset = User.objects.all()
    serializer_class = UserSerializer
Any operation that modifies state must include a valid CSRF token:
  • POST - Create new resources
  • PUT - Update entire resources
  • PATCH - Partially update resources
  • DELETE - Delete resources

Using SessionAuthentication

When using SessionAuthentication, you must include valid CSRF tokens for unsafe HTTP operations.
# settings.py
REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': [
        'rest_framework.authentication.SessionAuthentication',
        'rest_framework.authentication.BasicAuthentication',
    ],
}

Including CSRF Tokens in AJAX Requests

Django requires CSRF tokens to be included in the HTTP header for AJAX requests. See Django’s CSRF documentation for details. Getting the CSRF Token:
// Get CSRF token from cookie
function getCookie(name) {
  let cookieValue = null;
  if (document.cookie && document.cookie !== '') {
    const cookies = document.cookie.split(';');
    for (let i = 0; i < cookies.length; i++) {
      const cookie = cookies[i].trim();
      if (cookie.substring(0, name.length + 1) === (name + '=')) {
        cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
        break;
      }
    }
  }
  return cookieValue;
}

const csrftoken = getCookie('csrftoken');
Using Fetch API:
fetch('/api/users/', {
  method: 'POST',
  credentials: 'same-origin',
  headers: {
    'Content-Type': 'application/json',
    'X-CSRFToken': csrftoken
  },
  body: JSON.stringify({
    username: 'newuser',
    email: '[email protected]'
  })
})
.then(response => response.json())
.then(data => console.log('Success:', data))
.catch(error => console.error('Error:', error));
Using Axios: Axios can be configured to automatically include CSRF tokens:
import axios from 'axios';

axios.defaults.xsrfCookieName = 'csrftoken';
axios.defaults.xsrfHeaderName = 'X-CSRFToken';

// Now all requests automatically include the CSRF token
axios.post('/api/users/', {
  username: 'newuser',
  email: '[email protected]'
})
.then(response => console.log(response.data))
.catch(error => console.error(error));
Using jQuery:
function csrfSafeMethod(method) {
  // These HTTP methods do not require CSRF protection
  return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method));
}

$.ajaxSetup({
  beforeSend: function(xhr, settings) {
    if (!csrfSafeMethod(settings.type) && !this.crossDomain) {
      xhr.setRequestHeader("X-CSRFToken", csrftoken);
    }
  }
});

// Now make your AJAX requests
$.ajax({
  url: '/api/users/',
  method: 'POST',
  data: JSON.stringify({username: 'newuser'}),
  contentType: 'application/json',
  success: function(data) {
    console.log('Success:', data);
  }
});

CSRF Token in Templates

When rendering forms in Django templates, use the csrf_token template tag:
<form method="post" action="/api/users/">
  {%% csrf_token %%}
  <input type="text" name="username">
  <button type="submit">Submit</button>
</form>

Exempting Views from CSRF Protection

Only exempt views from CSRF protection if they use alternative authentication methods (like token authentication) that are not vulnerable to CSRF attacks.
from django.views.decorators.csrf import csrf_exempt
from django.utils.decorators import method_decorator

@method_decorator(csrf_exempt, name='dispatch')
class PublicAPIView(APIView):
    authentication_classes = []  # No authentication
    permission_classes = [AllowAny]
Better approach - Use TokenAuthentication:
REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': [
        'rest_framework.authentication.TokenAuthentication',
    ],
}

class UserViewSet(viewsets.ModelViewSet):
    # Token authentication is not vulnerable to CSRF
    # No CSRF token required
    authentication_classes = [TokenAuthentication]
    permission_classes = [IsAuthenticated]

CORS (Cross-Origin Resource Sharing)

CORS is a mechanism that allows clients to interact with APIs hosted on a different domain.

How CORS Works

Browsers enforce the same-origin policy by default. CORS allows servers to specify which origins can access their resources:
1. Browser makes OPTIONS request (preflight)
   Origin: https://app.example.com

2. Server responds with allowed origins
   Access-Control-Allow-Origin: https://app.example.com
   Access-Control-Allow-Methods: GET, POST, PUT, DELETE
   Access-Control-Allow-Headers: Content-Type, Authorization

3. Browser makes actual request if allowed

Implementing CORS

The best way to handle CORS in REST framework is to add the required response headers in middleware.
Using middleware ensures CORS is supported transparently without changing any behavior in your views.

Using django-cors-headers

django-cors-headers is the recommended package for handling CORS in Django REST Framework. Installation:
pip install django-cors-headers
Configuration:
# settings.py
INSTALLED_APPS = [
    ...,
    'corsheaders',
    ...,
]

MIDDLEWARE = [
    ...,
    'corsheaders.middleware.CorsMiddleware',
    'django.middleware.common.CommonMiddleware',
    ...,
]

# Allow all origins (development only)
CORS_ALLOW_ALL_ORIGINS = True

# Production: Specify allowed origins
CORS_ALLOWED_ORIGINS = [
    "https://app.example.com",
    "https://www.example.com",
]

# Allow credentials (cookies, authorization headers)
CORS_ALLOW_CREDENTIALS = True

# Allowed methods
CORS_ALLOW_METHODS = [
    'DELETE',
    'GET',
    'OPTIONS',
    'PATCH',
    'POST',
    'PUT',
]

# Allowed headers
CORS_ALLOW_HEADERS = [
    'accept',
    'accept-encoding',
    'authorization',
    'content-type',
    'dnt',
    'origin',
    'user-agent',
    'x-csrftoken',
    'x-requested-with',
]
Configuration Options:
List of origins authorized to make cross-site HTTP requests:
CORS_ALLOWED_ORIGINS = [
    "https://example.com",
    "https://sub.example.com",
]
List of regex strings matching origins that are authorized:
CORS_ALLOWED_ORIGIN_REGEXES = [
    r"^https://\w+\.example\.com$",
]
Allow cookies to be included in cross-site HTTP requests:
CORS_ALLOW_CREDENTIALS = True
When True, you cannot use CORS_ALLOW_ALL_ORIGINS = True. You must specify exact origins.
Number of seconds a preflight request can be cached:
CORS_PREFLIGHT_MAX_AGE = 86400  # 24 hours

Testing CORS

Using cURL:
# Preflight request
curl -X OPTIONS https://api.example.com/users/ \
  -H "Origin: https://app.example.com" \
  -H "Access-Control-Request-Method: POST" \
  -H "Access-Control-Request-Headers: Content-Type" \
  -i

# Expected response includes:
# Access-Control-Allow-Origin: https://app.example.com
# Access-Control-Allow-Methods: POST, GET, OPTIONS
# Access-Control-Allow-Headers: Content-Type
Using JavaScript:
// This will trigger a CORS preflight if needed
fetch('https://api.example.com/users/', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Token abc123'
  },
  body: JSON.stringify({username: 'newuser'})
})
.then(response => {
  if (!response.ok) {
    throw new Error('CORS error or other HTTP error');
  }
  return response.json();
})
.then(data => console.log('Success:', data))
.catch(error => console.error('Error:', error));

Best Practices

Token authentication is immune to CSRF attacks and works well with CORS:
REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': [
        'rest_framework.authentication.TokenAuthentication',
    ],
}
Session authentication provides a seamless experience when your frontend and backend share the same origin:
REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': [
        'rest_framework.authentication.SessionAuthentication',
    ],
}
Always specify exact origins in production:
# Development
CORS_ALLOW_ALL_ORIGINS = True  # OK for dev

# Production
CORS_ALLOWED_ORIGINS = [
    "https://app.example.com",
]  # Specific origins only
Always include CSRF tokens when using session authentication:
headers: {
  'X-CSRFToken': csrftoken,
  'Content-Type': 'application/json'
}
Test with actual cross-origin requests to ensure your configuration works:
  • Test preflight OPTIONS requests
  • Test actual POST/PUT/DELETE requests
  • Test with credentials
  • Test with custom headers

Build docs developers (and LLMs) love