This project serves as an example of our styleguide
The structure is inspired by cookiecutter-django and modified based on our experience with Django.
Few important things:
- Linux / Ubuntu is our primary OS and things are tested for that. It will mostly not work on Mac & certainly not work on Windows.
- It uses Postgres as primary database.
- It comes with GitHub Actions support, based on that article
- It comes with
whitenoise
setup. - It can be easily deployed to Heroku.
- It comes with an example list API, that uses
django-filter
for filtering & pagination from DRF.
The project is running django-cors-headers
with the following general configuration:
CORS_ALLOW_CREDENTIALS = True
CORS_ALLOW_ALL_ORIGINS = True
For production.py
, we have the following:
CORS_ALLOW_ALL_ORIGINS = False
CORS_ORIGIN_WHITELIST = env.list('DJANGO_CORS_ORIGIN_WHITELIST', default=[])
We have removed the default authentication classes, since they were causing trouble.
This project is using the already existing cookie-based session authentication in Django:
- On successful authentication, Django returns the
sessionid
cookie:
sessionid=5yic8rov868prmfoin2vhtg4vx35h71p; expires=Tue, 13 Apr 2021 11:17:58 GMT; HttpOnly; Max-Age=1209600; Path=/; SameSite=Lax
- When making calls from the frontend, don't forget to include credentials. For example, when using
axios
:
axios.get(url, { withCredentials: true });
axios.post(url, data, { withCredentials: true });
-
For convenience,
CSRF_USE_SESSIONS
is set toTrue
-
Check
config/settings/sessions.py
for all configuration that's related to sessions.
Since the default implementation of SessionAuthentication
enforces CSRF check, which is not the desired behavior for our APIs, we've done the following:
from rest_framework.authentication import SessionAuthentication
class CsrfExemptedSessionAuthentication(SessionAuthentication):
"""
DRF SessionAuthentication is enforcing CSRF, which may be problematic.
That's why we want to make sure we are exempting any kind of CSRF checks for APIs.
"""
def enforce_csrf(self, request):
return
Which is then used to construct an ApiAuthMixin
, which marks an API that requires authentication:
from rest_framework.permissions import IsAuthenticated
class ApiAuthMixin:
authentication_classes = (CsrfExemptedSessionAuthentication, )
permission_classes = (IsAuthenticated, )
By default, all APIs are public, unless you add the ApiAuthMixin
We have the following general cases:
- The current configuration works out of the box for
localhost
development. - If the backend is located on
*.domain.com
and the frontend is located on*.domain.com
, the configuration is going to work out of the box. - If the backend is located on
somedomain.com
and the frontend is located onanotherdomain.com
, then you'll need to setSESSION_COOKIE_SAMESITE = 'None'
andSESSION_COOKIE_SECURE = True
Since cookies can be somewhat elusive, check the following urls:
- https://docs.djangoproject.com/en/3.1/ref/settings/#sessions - It's a good idea to just read every description for
SESSION_*
- https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies - It's a good idea to read everything, several times.
POST
http://localhost:8000/api/auth/login/ requires JSON body withemail
andpassword
.GET
http://localhost:8000/api/auth/me/ returns the current user information, if the request is authenticated (has the correspondingsessionid
cookie)GET
orPOST
http://localhost:8000/api/auth/logout/ will remove thesessionid
cookie, effectively logging you out.
The current implementation of /auth/login
does 2 things:
- Sets a
HTTP Only
cookie with the session id. - Returns the actual session id from the JSON payload.
The second thing is required, because Safari is not respecting the SameSite = None
option for cookies.
More on the issue here - https://www.chromium.org/updates/same-site/incompatible-clients
You can find the UserListApi
in styleguide_example/users/apis.py
List API is located at:
http://localhost:8000/api/users/
The API can be filtered:
- http://localhost:8000/api/users/?is_admin=True
- http://localhost:8000/api/users/?id=1
- http://localhost:8000/api/users/?email=radorado@hacksoft.io
Example data structure:
{
"limit": 1,
"offset": 0,
"count": 4,
"next": "http://localhost:8000/api/users/?limit=1&offset=1",
"previous": null,
"results": [
{
"id": 1,
"email": "radorado@hacksoft.io",
"is_admin": false
}
]
}
To create Postgres database:
sudo -u postgres createdb -O your_postgres_user_here database_name_here
If you want to recreate your database, you can use the bootstrap script:
./scripts/bootstrap.sh your_postgres_user_here
To start Celery:
celery --without-gossip --without-mingle --without-heartbeat worker -A styleguide_example.tasks -l info
To start Celery Beat:
celery -A styleguide_example.tasks beat -l info --scheduler django_celery_beat.schedulers:DatabaseScheduler
The project is ready to be deployed on Heroku. There's a current deployment that can be found - https://hacksoft-styleguide-example.herokuapp.com/