Django Ninja és una llibreria per a fer APIs alternativa a la clàssica Django REST Framework (que podeu veure a l'article Django API) i fortament inspirada en la molt adoptada recentment FastAPI.
El principal avantatge és certa simplicitat per crear els endpoints.
Referències:
És relativament fàcil acostar-se a Django Ninja amb la seva documentació:
api.py: https://django-ninja.dev/tutorial/
Destaquem dos mètodes d'autenticació molt estandarditzats:
curl http://localhost:8000/api/token/ -u manolo:pass123
curl -H 'Authorization: Bearer 1d895b9062512a731b1e5667bd034da3' http://localhost:8000/api/llibres
Es poden implementar les dues amb aquest codi:
from django.contrib.auth import authenticate as django_authenticate from ninja import NinjaAPI, Schema from ninja.security import HttpBasicAuth, HttpBearer from typing import List, Optional from .models import * import secrets api = NinjaAPI() # Autenticació bàsica class BasicAuth(HttpBasicAuth): def authenticate(self, request, username, password): user = django_authenticate(username=username, password=password) if user: # Genera un token simple token = secrets.token_hex(16) user.auth_token = token user.save() return token return None # Autenticació per Token Bearer class AuthBearer(HttpBearer): def authenticate(self, request, token): try: user = Usuari.objects.get(auth_token=token) return user except Usuari.DoesNotExist: return None # Endpoint per obtenir un token, accés amb BasicAuth # amb o sense "trailing slash" @api.get("/token", auth=BasicAuth()) @api.get("/token/", auth=BasicAuth()) def obtenir_token(request): return {"token": request.auth} # Exemple d'endpoint per llistar els llibres, accés amb TokenAuth class LlibreOut(Schema): id: int titol: str editorial: Optional[str] @api.get("/llibres/", response=List[LlibreOut], auth=AuthBearer()) def get_llibres(request): qs = Llibre.objects.all() return qs
Per tal que la Token Auth funcioni, ens caldrà que l'usuari emmagatzemi els tokens en algun lloc.
Es pot fer de diverses maneres, una d'elles és afegir la columna auth_token al model Usuari que cal personalitzar, derivant de AbstractUser.
Ull, perquè aquest canvi tindrà implicacions profundes i ens obligarà a esborrar la base de dades i recomençar l'historial de migracions. Ho veurem mes tard.
Comencem per canviar el model d'usuari i el redefinim a models.py:
from django.contrib.auth.models import AbstractUser class Usuari(AbstractUser): auth_token = models.CharField(max_length=32,blank=True,null=True) # + altres atributs que es vulguin afegir...
I a settings.py (ULL, canviar <myapp> pel nom de la vostra app):
AUTH_USER_MODEL = '<myapp>.Usuari'
Per tal que els canvis es guardin a la base de dades caldrà crear i executar les migracions adequades:
(env) $ ./manage.py makemigrations (env) $ ./manage.py migrate
Com que hem modificat l'usuari del sistema, ara no es visualitzarà a l'admin panel. Per visualitzar-lo correctament, apliqueu aquest UsuariAdmin a admin.py:
from django.contrib import admin from django.contrib.auth.admin import UserAdmin class UsuariAdmin(UserAdmin): fieldsets = UserAdmin.fieldsets + ( ("Altres dades (API auth)", { 'fields': ('auth_token',), }), ) readonly_fields = ["auth_token",] admin.site.register(Usuari,UsuariAdmin)
Afegir canvis en el model d'usuari quan un projecte ja té dades de producció és problemàtic, com a mínim. Es recomana sempre crear un model d'usuari personalitzat (derivat d'AbstractUser) des del principi del projecte per poder afegir funcionalitats com aquesta (afegir token a l'usuari) quan sigui necessari.
Si no tenim dades de producció, el més fàcil és esborrar la base de dades i arxius de migracions, i tornar a crear-los.
(env) $ rm db.sqlite3 (env) $ rm -rf <myapp>/migrations (env) $ ./manage.py makemigrations <myapp> (env) $ ./manage.py migrate
Una possible alternativa, per tal de no haver de crear un model d'usuari personalitzat, és crear una taula de tokens a part, i relacionar-la amb el User estàndard de Django. Igualment es sol recomanar prioritàriament particularitzar el model d'usuari, ja que les situacions en les que interessarà ampliar-lo son molt comunes.
El típic exemple de request amb Basic Authentication seria:
curl -u admin:admin123 http://localhost:8000/api/users/
De vegades ens pot interessar una alternativa enviant les dades codificades en base64 en els headers:
curl -H 'Authorization:Basic YWRtaW46YWRtaW4xMjM=' http://localhost:8000/api/users/
Per obtenir el hash en base64 es pot fer:
echo -n "admin:admin123" | base64
Per aconseguir un pretty print del JSON es pot fer amb la utilitat jq
curl -u admin:admin123 http://localhost:8000/api/users/ | jq
Per instal·lar jq es pot fer:
sudo apt install jq
Passa sovint que el desplegament d'una API és independent del frontend, on es pot utilitzar altres frameworks com React o AngularJS.
Una mesura bàsica de seguretat és que el navegador web es negui a fer crides a APIs d'un altre dominis per evitar possibles injeccions de codi que treguin les nostres dades personals cap a dominis externs.
Disposem d'una eina Django CORS Headers amb la que podem avisar Django que hi ha determinats dominis que sí poden fer crides a l'API.
(env) $ pip install django-cors-headers
Bàsicament caldrà instal·lar el plugin django-cors-headers i ajustar les variables a settings.py:
INSTALLED_APPS : per activar el pluginMIDDLEWARE : per processar totes les peticions a DjangoCORS_ALLOWED_ORIGINS : llista de dominis que sí que poden accedir. Per ex. si utilitzem Vite per a React al frontend, hauriem de permetre http://localhost:5173 per treballar en development:CORS_ALLOWED_ORIGINS = [
"http://localhost:5173", # Exemple: React en desenvolupament amb Vite o CRA
"http://127.0.0.1:5173",
"https://el-teu-domini.com", # Quan el tinguis en producció
]