You can use this sample project to learn how to secure a simple Django API server using Auth0.
The starter
branch offers a working API server that exposes three public endpoints. Each endpoint returns a different type of message: public, protected, and admin.
The goal is to use Auth0 to only allow requests that contain a valid access token in their authorization header to access the protected and admin data. Additionally, only access tokens that contain a read:admin-messages
permission should access the admin data, which is referred to as Role-Based Access Control (RBAC).
Check out the add-authorization
branch to see authorization in action using Auth0.
Check out the add-rbac
branch to see authorization and Role-Based Access Control (RBAC) in action using Auth0.
Prerequisites:
- Python >= 3.7
Initialize a python virtual environment:
python3 -m venv venv
source ./venv/bin/activate
Install the project dependencies:
pip install -r requirements.txt
Setup virtual environments:
Copy the .env.example
file to .env
and edit it to populate its variables.
cp .env.example .env
Run the following command to generate a random secret key and add it to your .env
file.
python manage.py generate_secret
# .env
DJANGO_SECRET_KEY=<generated_key>
Run DB migrations:
python manage.py migrate
Run the project:
gunicorn
-
X-XSS-Protection
Default set to
0
.See the documentation for more details.
But we still need to explicitly add the header to our custom Auth0Middleware, otherwise Django will not add this header by default:
response['X-XSS-Protection'] = 0
-
HTTP Strict Transport Security (HSTS)
Disabled by default, so we need to add this configuration:
SECURE_HSTS_INCLUDE_SUBDOMAINS = True SECURE_HSTS_SECONDS = 31536000
Note that this will only take effect for HTTPS requests. For HTTP we need to add this header to the Auth0Middleware:
response['Strict-Transport-Security'] = 'max-age=31536000; includeSubDomains'
See the documentation for more details.
-
X-Frame-Options (XFO)
Default set to
DENY
.See the documentation for more details.
-
X-Content-Type-Options
Default set to
nosniff
.See the documentation for more details.
-
Content-Security-Policy (CSP)
Not enabled by default, we need to install the
django-csp
dependency and add it to theMIDDLEWARES
. It comes pre-configured with the directives:default-src: self
We need to manually set the configuration:
CSP_FRAME_ANCESTORS = "'none'"
See the documentation for more details.
-
Cache-Control
We need to add a custom middleware to call
add_never_cache_headers
on all responses. This will add the header:Cache-Control: max-age=0, no-cache, no-store, must-revalidate, private
We also need to add the
Expires = 0
header to the Auth0Middleware, otherwise Django will set it with the actual date:response['Expires'] = 0
See the documentation for more details.
-
Content-Type
By setting the default renderer to
JSONRenderer
, it will useutf-8
encoding by default.See the documentation for more details.
X-Powered-By
: Not added by Django.Server
: There is no easy way to remove this header since it's mostly the responsibility of the environment server. On development it doesn't matter, but on production its usuallyNGINX
,Apache
, etc. which handles this header.
Django doesn't have CORS built-in, so we need to install the django-cors-headers
dependency and add the configuration needed on the settings.
It comes pre-configured with:
Access-Control-Max-Age: 86400
See the documentation for more details.
The API server defines the following endpoints:
GET /api/messages/public
Status: 200 OK
{
"text": "This is a public message.",
"metadata" : {
"api": "simplex_api",
"branch": "basic-role-based-access-control"
}
}
You need to protect this endpoint using Auth0.
GET /api/messages/protected
Status: 200 OK
{
"text": "This is a protected message.",
"metadata" : {
"api": "simplex_api",
"branch": "basic-role-based-access-control"
}
}
You need to protect this endpoint using Auth0 and Role-Based Access Control (RBAC).
GET /api/messages/admin
Status: 200 OK
{
"text": "This is an admin message.",
"metadata" : {
"api": "simplex_api",
"branch": "basic-role-based-access-control"
}
}
Status: Corresponding 400 status code
{
"message": "Not Found"
}
Request without authorization header
curl localhost:6060/api/messages/admin
{
"message":"Authentication credentials were not provided.",
}
HTTP Status: 401
Request with malformed authorization header
curl localhost:6060/api/messages/admin --header "authorization: <valid_token>"
{
"message":"Authentication credentials were not provided.",
}
HTTP Status: 401
Request with wrong authorization scheme
curl localhost:6060/api/messages/admin --header "authorization: Basic <valid_token>"
{
"message":"Authentication credentials were not provided.",
}
HTTP Status: 401
Request without token
curl localhost:6060/api/messages/admin --header "authorization: Bearer"
{
"message":"Authorization header must contain two space-delimited values",
}
HTTP Status: 401
JWT validation error
curl localhost:6060/api/messages/admin --header "authorization: Bearer asdf123"
{
"message":"Given token not valid for any token type",
}
HTTP Status: 401
Token without required permissions
curl localhost:6060/api/messages/admin --header "authorization: Bearer <token_without_permissions>"
{
"error":"insufficient_permissions",
"error_description":"You do not have permission to perform this action.",
"message":"Permission denied"
}
HTTP Status: 403
Status: 500 Internal Server Error
{
"message": "Server Error"
}