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
User logs into trusted-site.com
User visits malicious-site.com (without logging out)
Malicious site triggers a request to trusted-site.com
Request includes the user’s session cookie automatically
Trusted site processes the request as if the user intended it
Guarding Against CSRF
To protect against CSRF attacks, follow these guidelines:
1. Keep 'Safe' Methods Safe
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
2. Require CSRF Tokens for Unsafe Methods
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.
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" ,
]
CORS_ALLOWED_ORIGIN_REGEXES
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
Use Token Authentication for Cross-Origin Requests
Token authentication is immune to CSRF attacks and works well with CORS: REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES' : [
'rest_framework.authentication.TokenAuthentication' ,
],
}
Use Session Authentication for Same-Origin Requests
Session authentication provides a seamless experience when your frontend and backend share the same origin: REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES' : [
'rest_framework.authentication.SessionAuthentication' ,
],
}
Never Use CORS_ALLOW_ALL_ORIGINS in Production
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
Include CSRF Tokens in Same-Origin AJAX
Always include CSRF tokens when using session authentication: headers : {
'X-CSRFToken' : csrftoken ,
'Content-Type' : 'application/json'
}
Test CORS Configuration Thoroughly
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