Two Auth Contexts

Ananas GDS uses two distinct authentication mechanisms depending on who is making the request:

ContextWhoMechanismHeader
DashboardLogged-in users via the web appSession token (DRF Token + UserSession)Authorization: Token <token>
Partner APIExternal systems, tour operatorsAPI key in URL pathNo 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.

Login
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:

Authenticated Request
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.

FieldDescription
tokenThe DRF auth token this session is associated with.
ip_addressIP of the client at login time.
deviceUser-Agent string.
last_seenUpdated by the heartbeat endpoint.
is_activefalse 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:

Heartbeat
POST /api/auth/heartbeat/
Authorization: Token <token>

Logout

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).

Public v1 API — Token in URL
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.

settings.py — correct ordering
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

CodeMeaning
401 UnauthorizedToken missing, invalid, or session expired.
403 ForbiddenToken valid but lacks permission for the requested resource.
429 Too Many RequestsIP temporarily banned due to repeated failed login attempts.