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
| What | Where | Example |
|---|---|---|
| Login | POST /api/v1/auth/login | {"username": "...", "password": "..."} |
| Refresh Token | POST /api/v1/auth/refresh | {"refresh": "eyJ..."} |
| Use Token | Authorization header | Bearer eyJ0eXAiOiJKV1Q... |
| API Key | X-API-Key header | sk_a1B2c3D4e5F6g7H8... |
| SSO/OIDC | GET /api/v1/auth/oidc/authorize | Redirects to IdP |
| Current User | GET /api/v1/auth/profile | Returns user + permissions |
| Auth Method | Best For | Token Lifetime |
|---|---|---|
| JWT | Web apps, SPAs | 60 min (access) / 2 days (refresh) |
| API Key | Scripts, integrations, CI/CD | Custom (up to years) |
| OIDC/SSO | Enterprise, corporate IdP | Follows 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:
| Mode | Description | Use Case | Security Level |
|---|---|---|---|
public | No authentication required | Development, demos, public kiosks | Low |
private | Authentication required for all endpoints | Enterprise, security-sensitive deployments | High |
hybrid | Per-feature configuration (default) | Most production deployments | Flexible |
# Environment variable configuration
AUTH_MODE=hybrid # Options: public, private, hybridAuthentication 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
| Feature | JWT | API Key | OIDC |
|---|---|---|---|
| Stateless | Yes | Yes | Yes |
| Auto Refresh | Yes | No | Yes |
| Scoped Access | No | Yes | Yes |
| User Context | Yes | Yes | Yes |
| External IdP | No | No | Yes |
| Socket.IO Support | Yes | Yes | Yes |
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 storageToken Endpoints
| Endpoint | Method | Description |
|---|---|---|
/api/v1/auth/login | POST | Obtain access and refresh tokens |
/api/v1/auth/refresh | POST | Refresh access token |
/api/v1/auth/logout | POST | Blacklist 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
| Scope | Access Granted | Example Endpoints |
|---|---|---|
aircraft | Aircraft tracking data | /api/v1/aircraft/* |
alerts | Alert rules and history | /api/v1/alerts/* |
safety | Safety event data | /api/v1/safety/* |
audio | Audio transmissions | /api/v1/audio/* |
acars | ACARS messages | /api/v1/acars/* |
history | Historical data | /api/v1/history/* |
system | System 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 usersOIDC 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 Type | Description | Example |
|---|---|---|
exact | Claim value must match exactly | "skyspy-admins" matches "skyspy-admins" |
contains | Claim must contain the string | "admin" matches "skyspy-admins" |
regex | Claim 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 productionUser Roles and Permissions
Role-Based Access Control (RBAC)
SkySpy implements RBAC with the following default roles:
| Role | Priority | Description | Typical User |
|---|---|---|---|
viewer | 10 | Read-only access to allowed features | Public dashboards, guests |
operator | 20 | Create/manage own alerts, acknowledge safety events | Daily users, shift workers |
analyst | 30 | Extended access with export and transcription | Data analysts, researchers |
admin | 40 | Full feature access with limited user management | Team leads, managers |
superadmin | 100 | Full access including user and role management | System 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
| Permission | Viewer | Operator | Analyst | Admin | Super |
|---|---|---|---|---|---|
| aircraft.view | Yes | Yes | Yes | Yes | Yes |
| aircraft.view_military | No | No | Yes | Yes | Yes |
| alerts.view | Yes | Yes | Yes | Yes | Yes |
| alerts.create | No | Yes | Yes | Yes | Yes |
| alerts.manage_all | No | No | No | Yes | Yes |
| safety.acknowledge | No | Yes | Yes | Yes | Yes |
| audio.transcribe | No | No | Yes | Yes | Yes |
| history.export | No | No | Yes | Yes | Yes |
| users.view | No | No | No | Yes | Yes |
| users.create | No | No | No | No | Yes |
| roles.edit | No | No | No | No | Yes |
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
| Level | Description |
|---|---|
public | No authentication required |
authenticated | Any logged-in user |
permission | Specific 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
| Topic | Required Permission | Description |
|---|---|---|
aircraft | aircraft.view | Live aircraft positions |
military | aircraft.view_military | Military aircraft data |
alerts | alerts.view | Alert notifications |
safety | safety.view | Safety events |
acars | acars.view | ACARS messages |
audio | audio.view | Audio stream notifications |
system | system.view_status | System 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
| Key | Value | Description |
|---|---|---|
skyspy_access_token | JWT access token | Used for API requests |
skyspy_refresh_token | JWT refresh token | Used to obtain new access tokens |
skyspy_user | Serialized user object | Cached 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_KEYfrom Django secret- Secrets not committed to version control
Authentication
AUTH_MODE=hybridorprivate- Rate limiting enabled on auth endpoints
- JWT token lifetimes appropriate for use case
Cookies & Transport
SESSION_COOKIE_SECURE=trueCSRF_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=trueRate Limiting
| Endpoint | Rate Limit | Purpose |
|---|---|---|
/api/v1/auth/login | 5/minute | Prevent brute force attacks |
/api/v1/auth/refresh | 5/minute | Prevent token abuse |
| Anonymous requests | 100/minute | General protection |
| Authenticated requests | 1000/minute | Fair usage |
Token Security Best Practices
| Practice | Implementation | Why It Matters |
|---|---|---|
| Separate JWT Secret | Use different JWT_SECRET_KEY than DJANGO_SECRET_KEY | Limits blast radius if one key is compromised |
| Short Access Tokens | Default 60 minutes, adjust as needed | Limits window for stolen tokens |
| Token Rotation | Refresh tokens blacklisted on use | Prevents replay attacks |
| Secure Storage | Use httpOnly cookies (JWT_AUTH_COOKIE=true) | Prevents XSS token theft |
API Key Security
Warning: API Key Best Practices
- No Query Parameters - API keys cannot be passed in URLs (prevents logging/leakage)
- Hashed Storage - Only SHA-256 hash stored in database
- Scoped Access - Limit API keys to required features only
- Expiration - Always set expiration dates on API keys
- 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.comConfiguration Reference
Authentication Settings
| Setting | Default | Description |
|---|---|---|
AUTH_MODE | hybrid | Authentication mode (public/private/hybrid) |
LOCAL_AUTH_ENABLED | true | Enable username/password login |
API_KEY_ENABLED | true | Enable API key authentication |
JWT_SECRET_KEY | SECRET_KEY | Key for signing JWTs |
JWT_ACCESS_TOKEN_LIFETIME_MINUTES | 60 | Access token validity |
JWT_REFRESH_TOKEN_LIFETIME_DAYS | 2 | Refresh token validity |
JWT_AUTH_COOKIE | false | Store tokens in httpOnly cookies |
OIDC Settings
| Setting | Default | Description |
|---|---|---|
OIDC_ENABLED | false | Enable 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_NAME | SSO | Display name for UI |
OIDC_SCOPES | openid profile email groups | OAuth scopes to request |
OIDC_DEFAULT_ROLE | viewer | Default role for new OIDC users |
OIDC_ALLOW_EMAIL_LINKING | false | Allow linking by email (security risk) |
Rate Limiting Settings
| Setting | Default | Description |
|---|---|---|
DEFAULT_THROTTLE_RATES.anon | 100/minute | Anonymous user rate limit |
DEFAULT_THROTTLE_RATES.user | 1000/minute | Authenticated user rate limit |
| Auth endpoints | 5/minute | Login/refresh rate limit |
API Endpoints Reference
Authentication Endpoints
| Endpoint | Method | Auth | Description |
|---|---|---|---|
/api/v1/auth/config | GET | No | Get auth configuration |
/api/v1/auth/login | POST | No | Login with credentials |
/api/v1/auth/logout | POST | Yes | Logout and blacklist token |
/api/v1/auth/refresh | POST | No | Refresh access token |
/api/v1/auth/profile | GET | Yes | Get current user profile |
/api/v1/auth/profile | PATCH | Yes | Update current user profile |
/api/v1/auth/password | POST | Yes | Change password |
/api/v1/auth/oidc/authorize | GET | No | Get OIDC authorization URL |
/api/v1/auth/oidc/callback | GET | No | OIDC callback handler |
/api/v1/auth/permissions | GET | No | List all permissions |
/api/v1/auth/my-permissions | GET | Yes | Get current user permissions |
User Management Endpoints
| Endpoint | Method | Permission | Description |
|---|---|---|---|
/api/v1/users | GET | users.view | List users |
/api/v1/users | POST | users.create | Create user |
/api/v1/users/{id} | GET | users.view | Get user details |
/api/v1/users/{id} | PATCH | users.edit | Update user |
/api/v1/users/{id} | DELETE | users.delete | Delete user |
Role Management Endpoints
| Endpoint | Method | Permission | Description |
|---|---|---|---|
/api/v1/roles | GET | roles.view | List roles |
/api/v1/roles | POST | roles.create | Create role |
/api/v1/roles/{id} | GET | roles.view | Get role details |
/api/v1/roles/{id} | PATCH | roles.edit | Update role |
/api/v1/roles/{id} | DELETE | roles.delete | Delete role |
API Key Management Endpoints
| Endpoint | Method | Auth | Description |
|---|---|---|---|
/api/v1/api-keys | GET | Yes | List user's API keys |
/api/v1/api-keys | POST | Yes | Create API key |
/api/v1/api-keys/{id} | DELETE | Yes | Delete 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:
- Check token expiration with a JWT decoder
- Verify header format:
Authorization: Bearer eyJ...- Confirm user
is_active=Truein database
Warning: 403 Forbidden
Possible causes:
- User lacks required permission
- Feature is disabled
- API key scope doesn't include the feature
Solutions:
- Check user permissions via
/api/v1/auth/my-permissions- Verify feature is enabled in admin
- 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:
- Double-check client secret in environment
- Add callback URL to IdP allowed redirects:
https://your-domain/api/v1/auth/oidc/callback- 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:
- Refresh token before connecting
- Use
authparameter:io(url, { auth: { token: token } })- 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/loginendpoint 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
Updated 27 days ago