API Authentication
Two authentication contexts — session tokens for the app dashboard, API keys for partner data access.
Two Auth Contexts
Ananas GDS uses two distinct authentication mechanisms depending on who is making the request:
| Context | Who | Mechanism | Header |
|---|---|---|---|
| Dashboard | Logged-in users via the web app | Session token (DRF Token + UserSession) | Authorization: Token <token> |
| Partner API | External systems, tour operators | API key in URL path | No header — token in URL |
Session Token Authentication
When a user logs in via the dashboard, the backend creates a DRF Token and a UserSession record. The token is stored in a secure HTTP-only cookie by the frontend. All subsequent requests include the token via the Authorization header.
POST /api/auth/login/
Content-Type: application/json
{
"email": "you@example.com",
"password": "yourpassword"
}
HTTP/1.1 200 OK
{
"auth_token": "9944b09199c62bcf9418ad846dd0e4bbdfc6ee4b",
"user": { ... }
}
Use the returned token in all subsequent requests:
GET /api/settings/company/
Authorization: Token 9944b09199c62bcf9418ad846dd0e4bbdfc6ee4b
UserSession Model
Each login creates a UserSession record alongside the token. Sessions track IP address, device fingerprint, and last-seen timestamp via a heartbeat endpoint. Concurrent session limits are enforced per account — creating a new session beyond the limit invalidates the oldest session.
| Field | Description |
|---|---|
token | The DRF auth token this session is associated with. |
ip_address | IP of the client at login time. |
device | User-Agent string. |
last_seen | Updated by the heartbeat endpoint. |
is_active | false after logout or session revocation. |
Heartbeat
The frontend calls the heartbeat endpoint at a regular interval to keep the session alive and update the online-user indicator:
POST /api/auth/heartbeat/
Authorization: Token <token>
Logout
POST /api/auth/logout/
Authorization: Token <token>
# Session is invalidated; token is deleted.
API Key Authentication (Partner Access)
External systems access the public v1 data API using an API key token embedded directly in the URL path. No Authorization header is needed. This design allows the token to be used in environments where setting custom headers is not practical (iframes, legacy integrations, some PMS systems).
GET https://app.ananas-gds.com/api/v1/facts/{token}/
GET https://app.ananas-gds.com/api/v1/photos/{token}/
GET https://app.ananas-gds.com/api/v1/stop-sale/{token}/
The token is validated against the ApiKey model. Each key carries permission flags controlling which data types are accessible (fact_sheet, stop_sale, hotel_photos). A request to an endpoint the key does not have permission for returns HTTP 403.
Token exposure
Because the token appears in the URL, it will be logged in server access logs. Rotate tokens immediately if you suspect exposure. Tokens are also domain-restricted via the Whitelist model.
DRF Authentication Class Ordering
In DEFAULT_AUTHENTICATION_CLASSES, TokenAuthentication must appear before SessionAuthentication. Reversing this order causes 401 errors on all token-authenticated endpoints because DRF evaluates classes in order and the session class rejects the token format first.
REST_FRAMEWORK = {
"DEFAULT_AUTHENTICATION_CLASSES": [
"authapp.authentication.SessionTokenAuthentication", # token first
"rest_framework.authentication.SessionAuthentication",
],
...
}
IP Ban System
Failed login attempts beyond the configured threshold result in a temporary IP ban stored in the Django cache backend. During the ban period all requests from the IP receive HTTP 429 Too Many Requests. Cache-based bans are reset on a server restart — consider a persistent cache backend (Redis) for production.
Authentication Error Codes
| Code | Meaning |
|---|---|
401 Unauthorized | Token missing, invalid, or session expired. |
403 Forbidden | Token valid but lacks permission for the requested resource. |
429 Too Many Requests | IP temporarily banned due to repeated failed login attempts. |