Authentication & Authorization

Authentication and Authorization

SkySpy provides a comprehensive, enterprise-ready authentication and authorization system supporting multiple authentication methods, role-based access control (RBAC), and fine-grained feature permissions.


Quick Reference

TL;DR - Everything you need to get started in 30 seconds

WhatWhereExample
LoginPOST /api/v1/auth/login{"username": "...", "password": "..."}
Refresh TokenPOST /api/v1/auth/refresh{"refresh": "eyJ..."}
Use TokenAuthorization headerBearer eyJ0eXAiOiJKV1Q...
API KeyX-API-Key headersk_a1B2c3D4e5F6g7H8...
SSO/OIDCGET /api/v1/auth/oidc/authorizeRedirects to IdP
Current UserGET /api/v1/auth/profileReturns user + permissions
Auth MethodBest ForToken Lifetime
JWTWeb apps, SPAs60 min (access) / 2 days (refresh)
API KeyScripts, integrations, CI/CDCustom (up to years)
OIDC/SSOEnterprise, corporate IdPFollows IdP settings

Authentication Overview

SkySpy's authentication system is designed with flexibility and security in mind. It supports three operational modes and multiple authentication methods to accommodate various deployment scenarios.

Architecture Diagram

flowchart TB
    subgraph Frontend["Frontend"]
        WEB[Web App<br/>React]
        AUTH_CTX[AuthContext<br/>JWT + OIDC]
    end

    subgraph API["Backend API"]
        REST[REST API<br/>Django REST]
        WS[Socket.IO<br/>Real-time]
    end

    subgraph AuthLayer["Auth Layer"]
        JWT_AUTH[JWT Auth]
        API_KEY[API Key Auth]
        OIDC[OIDC/SSO]
    end

    subgraph Permissions["Authorization"]
        RBAC[Role-Based<br/>Access Control]
        FEATURE[Feature-Based<br/>Permissions]
    end

    WEB --> AUTH_CTX
    AUTH_CTX --> REST
    AUTH_CTX --> WS
    REST --> JWT_AUTH
    REST --> API_KEY
    REST --> OIDC
    WS --> JWT_AUTH
    WS --> API_KEY
    JWT_AUTH --> RBAC
    API_KEY --> RBAC
    OIDC --> RBAC
    RBAC --> FEATURE

Authentication Modes

SkySpy operates in one of three authentication modes, configured via the AUTH_MODE environment variable:

ModeDescriptionUse CaseSecurity Level
publicNo authentication requiredDevelopment, demos, public kiosksLow
privateAuthentication required for all endpointsEnterprise, security-sensitive deploymentsHigh
hybridPer-feature configuration (default)Most production deploymentsFlexible
# Environment variable configuration
AUTH_MODE=hybrid  # Options: public, private, hybrid

Authentication Methods

Method Comparison

Which method should I use?

Web Applications - JWT tokens with automatic refresh Scripts & Automation - API keys with scoped access Enterprise/Corporate - OIDC/SSO with your identity provider

FeatureJWTAPI KeyOIDC
StatelessYesYesYes
Auto RefreshYesNoYes
Scoped AccessNoYesYes
User ContextYesYesYes
External IdPNoNoYes
Socket.IO SupportYesYesYes

1. JWT Token Authentication

SkySpy uses JSON Web Tokens (JWT) for stateless authentication. The implementation is built on djangorestframework-simplejwt.

Token Structure Visual

+-----------------------------------------------------------------------------+
|                              JWT ACCESS TOKEN                                |
+-----------------------------------------------------------------------------+
|  HEADER              |  PAYLOAD                    |  SIGNATURE              |
|  -------             |  --------                   |  ----------             |
|  {                   |  {                          |                         |
|    "typ": "JWT",     |    "token_type": "access",  |  HMACSHA256(            |
|    "alg": "HS256"    |    "exp": 1704067200,       |    base64(header) +     |
|  }                   |    "user_id": 42,           |    base64(payload),     |
|                      |    "jti": "unique-id"       |    secret               |
|                      |  }                          |  )                       |
+-----------------------------------------------------------------------------+
|  eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0b2tlbl90eXBlIjoiYWNjZXNzIi4uLn0   |
|  --------------------------------------------------------------------------- |
|  Header (Base64)    Payload (Base64)              Signature                  |
+-----------------------------------------------------------------------------+

Configuration

# JWT Settings (environment variables)
JWT_SECRET_KEY=your-secret-key          # Separate from Django SECRET_KEY recommended
JWT_ACCESS_TOKEN_LIFETIME_MINUTES=60    # Access token validity (default: 60 min)
JWT_REFRESH_TOKEN_LIFETIME_DAYS=2       # Refresh token validity (default: 2 days)
JWT_AUTH_COOKIE=false                   # Enable httpOnly cookie storage

Token Endpoints

EndpointMethodDescription
/api/v1/auth/loginPOSTObtain access and refresh tokens
/api/v1/auth/refreshPOSTRefresh access token
/api/v1/auth/logoutPOSTBlacklist refresh token

JWT Authentication Flow

sequenceDiagram
    autonumber
    participant User as User
    participant App as Frontend
    participant API as SkySpy API
    participant DB as Database

    User->>App: Enter credentials
    App->>API: POST /auth/login<br/>{username, password}
    API->>DB: Validate credentials
    DB-->>API: User verified
    API-->>App: {access_token, refresh_token, user}
    App->>App: Store tokens in localStorage

    Note over User,DB: Making Authenticated Requests

    App->>API: GET /aircraft<br/>Authorization: Bearer {token}
    API->>API: Validate JWT signature
    API-->>App: Aircraft data

    Note over User,DB: Token Refresh (before expiry)

    App->>API: POST /auth/refresh<br/>{refresh_token}
    API->>DB: Validate & rotate token
    DB-->>API: New tokens generated
    API-->>App: {new_access_token, new_refresh_token}

Code Examples

cURL

# Login Request
curl -X POST https://your-skyspy-instance/api/v1/auth/login \
  -H "Content-Type: application/json" \
  -d '{
    "username": "operator",
    "password": "secure-password"
  }'

JavaScript

// Login Request
const response = await fetch('https://your-skyspy-instance/api/v1/auth/login', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    username: 'operator',
    password: 'secure-password'
  })
});

const { access, refresh, user } = await response.json();
localStorage.setItem('skyspy_access_token', access);
localStorage.setItem('skyspy_refresh_token', refresh);

Python

import requests

# Login Request
response = requests.post(
    'https://your-skyspy-instance/api/v1/auth/login',
    json={
        'username': 'operator',
        'password': 'secure-password'
    }
)

tokens = response.json()
access_token = tokens['access']
refresh_token = tokens['refresh']

Login Response

{
  "access": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9...",
  "refresh": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9...",
  "user": {
    "id": 42,
    "username": "operator",
    "email": "[email protected]",
    "display_name": "John Operator",
    "permissions": ["aircraft.view", "alerts.create", "alerts.edit"],
    "roles": ["operator"]
  }
}

Using JWT Tokens

cURL

# Include the access token in the Authorization header
curl https://your-skyspy-instance/api/v1/aircraft \
  -H "Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9..."

JavaScript

// Using the token in fetch requests
const token = localStorage.getItem('skyspy_access_token');

const response = await fetch('https://your-skyspy-instance/api/v1/aircraft', {
  headers: {
    'Authorization': `Bearer ${token}`
  }
});

Python

import requests

headers = {
    'Authorization': f'Bearer {access_token}'
}

response = requests.get(
    'https://your-skyspy-instance/api/v1/aircraft',
    headers=headers
)

Security Note

Refresh tokens are rotated on use and the old token is blacklisted. This provides protection against token theft and replay attacks.


2. API Key Authentication

API keys provide programmatic access for integrations, scripts, and third-party applications.

Key Format

sk_a1B2c3D4e5F6g7H8i9J0k1L2m3N4o5P6q7R8s9T0
|  |
|  +-- Random characters
|
+-- Prefix identifier

Warning

The full API key is only returned once at creation time. Store it securely immediately - you cannot retrieve it later!

Creating API Keys

cURL

curl -X POST https://your-skyspy-instance/api/v1/auth/api-keys \
  -H "Authorization: Bearer <access-token>" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "CI/CD Pipeline",
    "scopes": ["aircraft", "alerts"],
    "expires_at": "2025-12-31T23:59:59Z"
  }'

JavaScript

const response = await authFetch('/api/v1/auth/api-keys', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    name: 'CI/CD Pipeline',
    scopes: ['aircraft', 'alerts'],
    expires_at: '2025-12-31T23:59:59Z'
  })
});

const { key } = await response.json();
// Store this immediately - shown only once!
console.log('API Key:', key);

Django

from skyspy.models import APIKey
from django.utils import timezone
from datetime import timedelta

api_key = APIKey.objects.create(
    user=user,
    name="Read-only Aircraft Data",
    scopes=["aircraft", "history"],
    expires_at=timezone.now() + timedelta(days=90)
)

Using API Keys

cURL

# Using Authorization header (recommended)
curl https://your-skyspy-instance/api/v1/aircraft \
  -H "Authorization: ApiKey sk_a1B2c3D4e5F6g7H8i9J0..."

# Using X-API-Key header
curl https://your-skyspy-instance/api/v1/aircraft \
  -H "X-API-Key: sk_a1B2c3D4e5F6g7H8i9J0..."

JavaScript

const API_KEY = 'sk_a1B2c3D4e5F6g7H8i9J0...';

// Using Authorization header
const response = await fetch('https://your-skyspy-instance/api/v1/aircraft', {
  headers: {
    'Authorization': `ApiKey ${API_KEY}`
  }
});

// Or using X-API-Key header
const response2 = await fetch('https://your-skyspy-instance/api/v1/aircraft', {
  headers: {
    'X-API-Key': API_KEY
  }
});

Scope-Based Access

ScopeAccess GrantedExample Endpoints
aircraftAircraft tracking data/api/v1/aircraft/*
alertsAlert rules and history/api/v1/alerts/*
safetySafety event data/api/v1/safety/*
audioAudio transmissions/api/v1/audio/*
acarsACARS messages/api/v1/acars/*
historyHistorical data/api/v1/history/*
systemSystem status and metrics/api/v1/system/*

3. OIDC/SSO Authentication

SkySpy supports OpenID Connect (OIDC) for enterprise single sign-on integration with identity providers like Okta, Auth0, Azure AD, and Keycloak.

Configuration

# OIDC Settings
OIDC_ENABLED=true
OIDC_PROVIDER_URL=https://your-idp.example.com
OIDC_CLIENT_ID=skyspy-client-id
OIDC_CLIENT_SECRET=your-client-secret
OIDC_PROVIDER_NAME=Corporate SSO       # Display name for UI
OIDC_SCOPES=openid profile email groups
OIDC_DEFAULT_ROLE=viewer               # Role for new OIDC users

OIDC Authentication Flow

sequenceDiagram
    autonumber
    participant User as User
    participant App as SkySpy Frontend
    participant API as SkySpy Backend
    participant IdP as Identity Provider

    User->>App: Click "Login with SSO"
    App->>API: GET /auth/oidc/authorize
    API-->>App: Authorization URL + state
    App->>IdP: Redirect to IdP login

    Note over IdP: User authenticates<br/>with corporate credentials

    IdP->>User: Login form
    User->>IdP: Enter credentials
    IdP-->>App: Redirect with auth code
    App->>API: GET /auth/oidc/callback?code=xxx

    API->>IdP: Exchange code for tokens
    IdP-->>API: ID token + access token
    API->>IdP: Fetch user info
    IdP-->>API: User claims (email, groups, etc.)

    API->>API: Create/update user<br/>Map claims to roles
    API-->>App: SkySpy JWT tokens + user
    App->>App: Store tokens, redirect to dashboard

Claim-Based Role Mapping

Map your IdP groups to SkySpy roles automatically:

{
  "name": "Admin Group Mapping",
  "claim_name": "groups",
  "match_type": "exact",
  "claim_value": "skyspy-admins",
  "role": "admin",
  "priority": 10,
  "is_active": true
}
Match TypeDescriptionExample
exactClaim value must match exactly"skyspy-admins" matches "skyspy-admins"
containsClaim must contain the string"admin" matches "skyspy-admins"
regexClaim must match the pattern"skyspy-.*" matches "skyspy-operators"

Security Warning

Email linking (OIDC_ALLOW_EMAIL_LINKING) is disabled by default. Enabling it allows existing accounts to be linked to OIDC based on matching email addresses.

Risk: An attacker who controls an OIDC provider with matching email addresses could gain access to existing accounts.


4. Session-Based Authentication (Admin Only)

Django admin uses session-based authentication. This is separate from the API authentication system.

# settings.py - Session configuration
SESSION_COOKIE_HTTPONLY = True
SESSION_COOKIE_SAMESITE = 'Lax'
SESSION_COOKIE_SECURE = True  # In production

User Roles and Permissions

Role-Based Access Control (RBAC)

SkySpy implements RBAC with the following default roles:

RolePriorityDescriptionTypical User
viewer10Read-only access to allowed featuresPublic dashboards, guests
operator20Create/manage own alerts, acknowledge safety eventsDaily users, shift workers
analyst30Extended access with export and transcriptionData analysts, researchers
admin40Full feature access with limited user managementTeam leads, managers
superadmin100Full access including user and role managementSystem administrators

Permission Matrix

Permissions follow the format feature.action:

aircraft.view           # View aircraft data
aircraft.view_military  # View military aircraft
alerts.create          # Create alert rules
alerts.delete          # Delete alert rules
alerts.manage_all      # Manage all users' alerts
safety.acknowledge     # Acknowledge safety events
users.create           # Create new users
roles.edit             # Modify roles

Role Permission Comparison

PermissionViewerOperatorAnalystAdminSuper
aircraft.viewYesYesYesYesYes
aircraft.view_militaryNoNoYesYesYes
alerts.viewYesYesYesYesYes
alerts.createNoYesYesYesYes
alerts.manage_allNoNoNoYesYes
safety.acknowledgeNoYesYesYesYes
audio.transcribeNoNoYesYesYes
history.exportNoNoYesYesYes
users.viewNoNoNoYesYes
users.createNoNoNoNoYes
roles.editNoNoNoNoYes

Creating Custom Roles

cURL

curl -X POST https://your-skyspy-instance/api/v1/roles \
  -H "Authorization: Bearer <admin-token>" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "shift_supervisor",
    "display_name": "Shift Supervisor",
    "description": "Can manage alerts and acknowledge safety events",
    "permissions": [
      "aircraft.view",
      "aircraft.view_details",
      "alerts.view",
      "alerts.create",
      "alerts.edit",
      "alerts.delete",
      "safety.view",
      "safety.acknowledge",
      "safety.manage"
    ],
    "priority": 25
  }'

JavaScript

const newRole = await authFetch('/api/v1/roles', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    name: 'shift_supervisor',
    display_name: 'Shift Supervisor',
    description: 'Can manage alerts and acknowledge safety events',
    permissions: [
      'aircraft.view',
      'aircraft.view_details',
      'alerts.view',
      'alerts.create',
      'alerts.edit',
      'alerts.delete',
      'safety.view',
      'safety.acknowledge',
      'safety.manage'
    ],
    priority: 25
  })
});

Role Assignment with Expiration

Roles can be assigned with optional expiration for temporary access:

curl -X POST https://your-skyspy-instance/api/v1/user-roles \
  -H "Authorization: Bearer <admin-token>" \
  -H "Content-Type: application/json" \
  -d '{
    "user": 42,
    "role": 3,
    "expires_at": "2024-02-01T00:00:00Z"
  }'

Tip: Temporary Access

Use role expiration for:

  • Contractors with limited engagement periods
  • Trainees who need elevated access during onboarding
  • Incident response requiring temporary admin privileges

Feature-Based Access Control

Each feature can be configured with independent access levels:

Access Levels

LevelDescription
publicNo authentication required
authenticatedAny logged-in user
permissionSpecific permission required

Configuration Example

curl -X PATCH https://your-skyspy-instance/api/v1/feature-access/aircraft \
  -H "Authorization: Bearer <admin-token>" \
  -H "Content-Type: application/json" \
  -d '{
    "read_access": "public",
    "write_access": "permission",
    "is_enabled": true
  }'

Hybrid Mode Setup Example

{
  "aircraft": {
    "read_access": "public",
    "write_access": "permission",
    "is_enabled": true
  },
  "alerts": {
    "read_access": "authenticated",
    "write_access": "permission",
    "is_enabled": true
  },
  "safety": {
    "read_access": "authenticated",
    "write_access": "permission",
    "is_enabled": true
  },
  "users": {
    "read_access": "permission",
    "write_access": "permission",
    "is_enabled": true
  }
}

Socket.IO Authentication

Socket.IO connections support both JWT tokens and API keys for real-time data streaming.

Socket.IO Authentication Flow

sequenceDiagram
    autonumber
    participant Client as Client
    participant SIO as Socket.IO Server
    participant Auth as Auth Middleware
    participant Handler as Event Handler

    Client->>SIO: Connect with token<br/>(auth parameter)
    SIO->>Auth: Validate token

    alt Token Valid
        Auth-->>SIO: User authenticated
        SIO->>Handler: Accept connection
        Handler-->>Client: Connection accepted

        loop Real-time Updates
            Handler->>Client: Aircraft data
            Handler->>Client: Alert notifications
        end
    else Token Invalid
        Auth-->>SIO: Authentication failed
        SIO-->>Client: Disconnect (4001)
    end

Authentication Methods

Recommended: Auth Parameter

import { io } from 'socket.io-client';
const socket = io('https://your-skyspy', {
  path: '/socket.io/',
  auth: {
    token: accessToken
  }
});

Warning: Not Recommended - Query String

const socket = io('https://your-skyspy?token=eyJ...', {
  path: '/socket.io/'
});

Why? Tokens may appear in server logs and browser history.

Topic-Based Permissions

TopicRequired PermissionDescription
aircraftaircraft.viewLive aircraft positions
militaryaircraft.view_militaryMilitary aircraft data
alertsalerts.viewAlert notifications
safetysafety.viewSafety events
acarsacars.viewACARS messages
audioaudio.viewAudio stream notifications
systemsystem.view_statusSystem status updates

Handling Connection Rejection

import { io } from 'socket.io-client';

const socket = io('https://your-skyspy', {
  path: '/socket.io/',
  auth: {
    token: token
  }
});

socket.on('connect_error', (error) => {
  if (error.message === 'Authentication failed') {
    console.error('Authentication failed - invalid or expired token');
    // Trigger re-authentication
  } else if (error.message === 'Permission denied') {
    console.error('Permission denied - insufficient access');
  } else {
    console.error('Connection error:', error.message);
  }
});

Frontend Authentication Flow

The React frontend uses the AuthContext provider for authentication state management.

AuthContext API

import { useAuth } from '../contexts/AuthContext';

function MyComponent() {
  const {
    // State
    status,           // 'loading' | 'anonymous' | 'authenticated'
    user,             // Current user object
    config,           // Auth configuration
    error,            // Last error message
    isLoading,        // Boolean shorthand
    isAuthenticated,  // Boolean shorthand
    isAnonymous,      // Boolean shorthand

    // Actions
    login,            // (username, password) => Promise
    logout,           // () => Promise
    loginWithOIDC,    // () => Promise
    refreshAccessToken, // () => Promise<boolean>
    authFetch,        // Authenticated fetch wrapper

    // Permission checks
    hasPermission,    // (permission) => boolean
    hasAnyPermission, // ([permissions]) => boolean
    hasAllPermissions,// ([permissions]) => boolean
    canAccessFeature, // (feature, action?) => boolean

    // Token access
    getAccessToken,   // () => string | null

    // Error handling
    clearError,       // () => void
  } = useAuth();
}

Frontend Login Flow

flowchart LR
    subgraph Login["Login"]
        A[User enters credentials] --> B[Call login]
        B --> C{Success?}
        C -->|Yes| D[Store tokens]
        C -->|No| E[Show error]
        D --> F[Redirect to dashboard]
    end

Standard Login

const { login, error } = useAuth();

async function handleLogin(username, password) {
  const result = await login(username, password);
  if (result.success) {
    navigate('/dashboard');
  } else {
    console.error(result.error);
  }
}

OIDC/SSO Login

const { loginWithOIDC, config } = useAuth();

async function handleOIDCLogin() {
  try {
    const result = await loginWithOIDC();
    if (result.success) {
      navigate('/dashboard');
    }
  } catch (err) {
    // User closed popup or login timed out
    console.error(err.message);
  }
}

// Show OIDC button if enabled
{config.oidcEnabled && (
  <button onClick={handleOIDCLogin}>
    Login with {config.oidcProviderName}
  </button>
)}

Permission Checking

const { hasPermission, canAccessFeature } = useAuth();

// Check specific permission
if (hasPermission('alerts.create')) {
  return <CreateAlertButton />;
}

// Check feature access
if (canAccessFeature('safety', 'write')) {
  return <AcknowledgeButton />;
}

// Check multiple permissions
if (hasAllPermissions(['alerts.view', 'alerts.edit'])) {
  return <AlertManagement />;
}

Automatic Token Refresh

+--------------------------------------------------------------------+
|                    TOKEN REFRESH TIMELINE                          |
+--------------------------------------------------------------------+
|                                                                    |
|  Token Created        Refresh Scheduled       Token Expires        |
|       |                      |                      |              |
|       v                      v                      v              |
|  ------------------------------------------------------>  Time     |
|  |                          |                      |               |
|  +-------- 59 min 30s ------+                      |               |
|                             +------ 30s buffer ----+               |
|                                                                    |
|  New token obtained before expiry = seamless user experience       |
+--------------------------------------------------------------------+

Token Storage

KeyValueDescription
skyspy_access_tokenJWT access tokenUsed for API requests
skyspy_refresh_tokenJWT refresh tokenUsed to obtain new access tokens
skyspy_userSerialized user objectCached user info

Security Best Practices

Security Checklist

Production Security Checklist

Before going to production, verify these settings:

Environment & Secrets

  • DEBUG=false
  • Strong, unique DJANGO_SECRET_KEY
  • Separate JWT_SECRET_KEY from Django secret
  • Secrets not committed to version control

Authentication

  • AUTH_MODE=hybrid or private
  • Rate limiting enabled on auth endpoints
  • JWT token lifetimes appropriate for use case

Cookies & Transport

  • SESSION_COOKIE_SECURE=true
  • CSRF_COOKIE_SECURE=true
  • HTTPS enforced
  • CORS restricted to known origins

API Keys

  • Scoped to minimum required permissions
  • Expiration dates set
  • Query parameter auth disabled

Environment Variables

# Production settings
DEBUG=false
DJANGO_SECRET_KEY=<strong-random-key>
JWT_SECRET_KEY=<different-strong-key>
AUTH_MODE=hybrid

# Enable secure cookies
SESSION_COOKIE_SECURE=true
CSRF_COOKIE_SECURE=true
JWT_AUTH_COOKIE=true

Rate Limiting

EndpointRate LimitPurpose
/api/v1/auth/login5/minutePrevent brute force attacks
/api/v1/auth/refresh5/minutePrevent token abuse
Anonymous requests100/minuteGeneral protection
Authenticated requests1000/minuteFair usage

Token Security Best Practices

PracticeImplementationWhy It Matters
Separate JWT SecretUse different JWT_SECRET_KEY than DJANGO_SECRET_KEYLimits blast radius if one key is compromised
Short Access TokensDefault 60 minutes, adjust as neededLimits window for stolen tokens
Token RotationRefresh tokens blacklisted on usePrevents replay attacks
Secure StorageUse httpOnly cookies (JWT_AUTH_COOKIE=true)Prevents XSS token theft

API Key Security

Warning: API Key Best Practices

  1. No Query Parameters - API keys cannot be passed in URLs (prevents logging/leakage)
  2. Hashed Storage - Only SHA-256 hash stored in database
  3. Scoped Access - Limit API keys to required features only
  4. Expiration - Always set expiration dates on API keys
  5. Rotation - Rotate keys periodically and after team changes

CORS Configuration

CORS_ALLOW_ALL_ORIGINS=false
CORS_ALLOW_CREDENTIALS=true
CORS_ALLOWED_ORIGINS=https://your-frontend-domain.com

Configuration Reference

Authentication Settings

SettingDefaultDescription
AUTH_MODEhybridAuthentication mode (public/private/hybrid)
LOCAL_AUTH_ENABLEDtrueEnable username/password login
API_KEY_ENABLEDtrueEnable API key authentication
JWT_SECRET_KEYSECRET_KEYKey for signing JWTs
JWT_ACCESS_TOKEN_LIFETIME_MINUTES60Access token validity
JWT_REFRESH_TOKEN_LIFETIME_DAYS2Refresh token validity
JWT_AUTH_COOKIEfalseStore tokens in httpOnly cookies

OIDC Settings

SettingDefaultDescription
OIDC_ENABLEDfalseEnable OIDC authentication
OIDC_PROVIDER_URL-Base URL of the OIDC provider
OIDC_CLIENT_ID-OAuth client ID
OIDC_CLIENT_SECRET-OAuth client secret
OIDC_PROVIDER_NAMESSODisplay name for UI
OIDC_SCOPESopenid profile email groupsOAuth scopes to request
OIDC_DEFAULT_ROLEviewerDefault role for new OIDC users
OIDC_ALLOW_EMAIL_LINKINGfalseAllow linking by email (security risk)

Rate Limiting Settings

SettingDefaultDescription
DEFAULT_THROTTLE_RATES.anon100/minuteAnonymous user rate limit
DEFAULT_THROTTLE_RATES.user1000/minuteAuthenticated user rate limit
Auth endpoints5/minuteLogin/refresh rate limit

API Endpoints Reference

Authentication Endpoints

EndpointMethodAuthDescription
/api/v1/auth/configGETNoGet auth configuration
/api/v1/auth/loginPOSTNoLogin with credentials
/api/v1/auth/logoutPOSTYesLogout and blacklist token
/api/v1/auth/refreshPOSTNoRefresh access token
/api/v1/auth/profileGETYesGet current user profile
/api/v1/auth/profilePATCHYesUpdate current user profile
/api/v1/auth/passwordPOSTYesChange password
/api/v1/auth/oidc/authorizeGETNoGet OIDC authorization URL
/api/v1/auth/oidc/callbackGETNoOIDC callback handler
/api/v1/auth/permissionsGETNoList all permissions
/api/v1/auth/my-permissionsGETYesGet current user permissions

User Management Endpoints

EndpointMethodPermissionDescription
/api/v1/usersGETusers.viewList users
/api/v1/usersPOSTusers.createCreate user
/api/v1/users/{id}GETusers.viewGet user details
/api/v1/users/{id}PATCHusers.editUpdate user
/api/v1/users/{id}DELETEusers.deleteDelete user

Role Management Endpoints

EndpointMethodPermissionDescription
/api/v1/rolesGETroles.viewList roles
/api/v1/rolesPOSTroles.createCreate role
/api/v1/roles/{id}GETroles.viewGet role details
/api/v1/roles/{id}PATCHroles.editUpdate role
/api/v1/roles/{id}DELETEroles.deleteDelete role

API Key Management Endpoints

EndpointMethodAuthDescription
/api/v1/api-keysGETYesList user's API keys
/api/v1/api-keysPOSTYesCreate API key
/api/v1/api-keys/{id}DELETEYesDelete API key

Troubleshooting

Common Issues

401 Unauthorized

Possible causes:

  • Token is expired
  • Invalid Authorization header format (should be Bearer <token>)
  • User account is deactivated

Solutions:

  1. Check token expiration with a JWT decoder
  2. Verify header format: Authorization: Bearer eyJ...
  3. Confirm user is_active=True in database

Warning: 403 Forbidden

Possible causes:

  • User lacks required permission
  • Feature is disabled
  • API key scope doesn't include the feature

Solutions:

  1. Check user permissions via /api/v1/auth/my-permissions
  2. Verify feature is enabled in admin
  3. Check API key scopes if using API key auth

OIDC Login Failed

Possible causes:

  • Incorrect OIDC_CLIENT_SECRET
  • Redirect URIs not configured in IdP
  • Scopes not allowed by IdP

Solutions:

  1. Double-check client secret in environment
  2. Add callback URL to IdP allowed redirects: https://your-domain/api/v1/auth/oidc/callback
  3. Verify scopes are enabled in IdP application settings

Socket.IO Connection Rejected

Possible causes:

  • Token is invalid or expired
  • Using query string instead of auth parameter
  • Token format incorrect

Solutions:

  1. Refresh token before connecting
  2. Use auth parameter: io(url, { auth: { token: token } })
  3. Check Socket.IO authentication middleware configuration

Debug Logging

Enable debug logging for authentication issues:

LOGGING = {
    'loggers': {
        'skyspy.auth': {
            'level': 'DEBUG',
            'handlers': ['console'],
        },
    },
}

Next Steps

Ready to integrate?

  • Quick Start: Try the /api/v1/auth/login endpoint with your credentials
  • API Keys: Create a scoped API key for your integration
  • Enterprise: Configure OIDC with your identity provider
  • Support: Check our GitHub issues or contact support