aminghadersohi opened a new pull request, #37973: URL: https://github.com/apache/superset/pull/37973
### SUMMARY Add API key authentication to Superset, enabling programmatic access to all API endpoints using long-lived API keys. No more JWT refresh headaches for CI/CD pipelines, MCP integrations, or automation scripts. API key auth is implemented at the **Flask-AppBuilder layer** so the `@protect()` decorator handles it automatically — zero changes needed in individual API views. **Depends on:** https://github.com/dpgaspar/Flask-AppBuilder/pull/2431 (FAB feature branch — all CI green) **Related SIP:** https://github.com/apache/superset/issues/37971 --- ### How Authentication Works Superset supports three authentication methods. API keys slot in alongside the existing ones: ```mermaid flowchart TD A["Incoming API Request Authorization: Bearer <token>"] --> B{"@protect() decorator"} B --> C{Is resource public?} C -->|Yes| D["✅ Allow"] C -->|No| E{"Token starts with configured prefix? (e.g. sst_)"} E -->|"Yes — API Key path"| F["Validate API Key 1. Lookup by prefix 2. Verify hash 3. Check RBAC"] F -->|Valid + access| G["✅ Allow"] F -->|Invalid or no access| H["❌ 403"] E -->|"No — JWT/Session path"| I{JWT Token?} I -->|Yes| J["Verify JWT + Check RBAC"] J -->|Valid + access| K["✅ Allow"] J -->|Invalid| L["❌ 401"] I -->|No| M{Session cookie?} M -->|Yes| N["Check RBAC"] N -->|Access| O["✅ Allow"] N -->|No access| P["❌ 403"] M -->|No| Q["❌ 401"] style D fill:#22c55e,color:#fff style G fill:#22c55e,color:#fff style K fill:#22c55e,color:#fff style O fill:#22c55e,color:#fff style H fill:#ef4444,color:#fff style L fill:#ef4444,color:#fff style P fill:#ef4444,color:#fff style Q fill:#ef4444,color:#fff ``` > **Key point:** When an `sst_` prefix is detected, it's a deterministic API key auth path — it never falls through to JWT. This avoids confusing error messages. ### API Key Lifecycle ```mermaid sequenceDiagram participant User participant Superset participant DB as Database Note over User,DB: 1. Create a key User->>Superset: POST /api/v1/security/api_keys/<br/>{ "name": "my-ci-key" } Superset->>Superset: Generate random key (secrets.token_urlsafe) Superset->>Superset: Hash key (werkzeug.generate_password_hash) Superset->>DB: Store hash, prefix, user_id, uuid Superset-->>User: { "key": "sst_abc123..." }<br/>⚠️ Shown only once! Note over User,DB: 2. Use the key User->>Superset: GET /api/v1/chart/<br/>Authorization: Bearer sst_abc123... Superset->>Superset: Detect "sst_" prefix Superset->>DB: Find keys by prefix Superset->>Superset: Verify hash (check_password_hash) Superset->>Superset: Check RBAC (has_access) Superset->>DB: Update last_used_on Superset-->>User: 200 OK + chart data Note over User,DB: 3. Revoke when done User->>Superset: DELETE /api/v1/security/api_keys/{uuid} Superset->>DB: Set revoked_on timestamp Superset-->>User: 200 OK Note over User,DB: 4. Revoked key rejected User->>Superset: GET /api/v1/chart/<br/>Authorization: Bearer sst_abc123... Superset->>DB: Find keys by prefix Superset->>Superset: Key is revoked → reject Superset-->>User: 403 Forbidden ``` ### What's in this PR **Backend:** - MCP auth updated to use FAB's `SecurityManager.validate_api_key()` - `FAB_API_KEY_ENABLED = True` and `FAB_API_KEY_PREFIXES = ["sst_"]` in default config - Migration for `ab_api_key` table - FAB pinned to feature branch with API key support **Frontend:** - API Keys section added to User Info page (`/user_info/`) - `ApiKeyList` — table with name, prefix, status (Active/Revoked/Expired), last used, revoke button - `ApiKeyCreateModal` — create key with name, shows plaintext once with copy button **Security:** - Keys hashed with `werkzeug.generate_password_hash` — no plaintext in DB - Prefix-based lookup avoids scanning all keys - Keys inherit user's RBAC permissions (Admin sees everything, Gamma sees limited) - `last_used_on` updated on each use for audit trail - `g._api_key_user = True` flag distinguishes API key auth from session/JWT ### BEFORE/AFTER SCREENSHOTS OR ANIMATED GIF <!-- TODO: Add screenshots of: 1. User Info page showing the API Keys collapsible section (empty state) 2. Create API Key modal (name input) 3. API Key created - showing the key with copy button and warning 4. API Keys list showing active key with status badge and revoke button 5. API Keys list after revoking - showing "Revoked" status badge --> | Screenshot | Description | |---|---| | <!-- screenshot 1 --> | API Keys section on User Info page | <img width="1107" height="996" alt="Screenshot 2026-02-17 at 6 18 48 PM" src="https://github.com/user-attachments/assets/62b20866-69a5-457f-900b-29897c72bf4c" /> | <!-- screenshot 2 --> | Create API Key modal | <img width="1106" height="1000" alt="Screenshot 2026-02-17 at 6 19 09 PM" src="https://github.com/user-attachments/assets/afbf00c4-f2da-4f60-92bf-3bba4445a4f6" /> | <!-- screenshot 3 --> | Key created — shown once with copy button | <img width="1108" height="994" alt="Screenshot 2026-02-17 at 6 19 33 PM" src="https://github.com/user-attachments/assets/1a642f26-2ff7-4d15-aae8-565e67ccf935" /> | <!-- screenshot 4 --> | Active key in list with Revoke button | <img width="1089" height="414" alt="Screenshot 2026-02-17 at 6 20 15 PM" src="https://github.com/user-attachments/assets/dbaf02ad-ce48-4c29-8333-5fe236dd56a5" /> | <!-- screenshot 5 --> | Revoked key showing status badge | <img width="1103" height="998" alt="Screenshot 2026-02-17 at 6 20 31 PM" src="https://github.com/user-attachments/assets/dd42f2d8-2e34-4f00-a5db-72d2a097492e" /> ### TESTING INSTRUCTIONS #### Prerequisites ```bash # Install FAB feature branch pip install git+https://github.com/aminghadersohi/Flask-AppBuilder@amin/ch99414/api-key-auth # Ensure config (already set in this PR's superset/config.py) # FAB_API_KEY_ENABLED = True # FAB_API_KEY_PREFIXES = ["sst_"] # Create table and register permissions superset db upgrade superset init ``` #### Test 1: Create an API Key ```bash # Get a JWT token JWT=$(curl -s -X POST http://localhost:8088/api/v1/security/login \ -H "Content-Type: application/json" \ -d '{"username":"admin","password":"admin","provider":"db","refresh":true}' \ | python3 -c "import sys,json; print(json.load(sys.stdin)['access_token'])") # Create an API key curl -s -X POST http://localhost:8088/api/v1/security/api_keys/ \ -H "Authorization: Bearer $JWT" \ -H "Content-Type: application/json" \ -d '{"name":"my-test-key"}' ``` **Expected:** 200 with `key` (e.g. `sst_abc123...`), `uuid`, `name`. Save the key — shown only once. #### Test 2: Use API Key on Protected Endpoints ```bash curl -H "Authorization: Bearer sst_<YOUR_KEY>" http://localhost:8088/api/v1/chart/ curl -H "Authorization: Bearer sst_<YOUR_KEY>" http://localhost:8088/api/v1/dashboard/ curl -H "Authorization: Bearer sst_<YOUR_KEY>" http://localhost:8088/api/v1/dataset/ ``` **Expected:** 200 with data on all endpoints. #### Test 3: Auth Rejection Scenarios | Scenario | Expected | |---|---| | No auth header | **401** | | `Authorization: Bearer sst_invalidkey` | **403** (prefix detected, validation fails) | | `Authorization: Bearer some_random_jwt` | **401 or 422** (not an API key, invalid JWT) | | Revoked API key | **403** | #### Test 4: API Key CRUD ```bash # List keys curl -H "Authorization: Bearer $JWT" http://localhost:8088/api/v1/security/api_keys/ # Get key info curl -H "Authorization: Bearer $JWT" http://localhost:8088/api/v1/security/api_keys/<UUID> # Revoke key curl -X DELETE -H "Authorization: Bearer $JWT" http://localhost:8088/api/v1/security/api_keys/<UUID> ``` #### Test 5: JWT Still Works ```bash curl -H "Authorization: Bearer $JWT" http://localhost:8088/api/v1/chart/ ``` **Expected:** 200 — JWT and API key auth coexist. #### Test 6: RBAC Respected 1. Create API key for a Gamma user → key inherits Gamma permissions 2. Access allowed endpoint → **200** 3. Access restricted endpoint → **403** #### Test 7: UI 1. Go to `/user_info/` → see "API Keys" section 2. Click "Create API Key" → enter name → key shown with copy button 3. Copy key → verify it works via curl 4. Click "Revoke" → confirm → key shows "Revoked" badge 5. Verify revoked key returns 403 ### ADDITIONAL INFORMATION - [x] Has associated issue: Related SIP: #37971 - [ ] Required feature flags: - [x] Changes UI - [x] Includes DB Migration (follow approval process in [SIP-59](https://github.com/apache/superset/issues/13351)) - [x] Migration is atomic, supports rollback & is backwards-compatible - [x] Confirm DB migration upgrade and downgrade tested - [x] Runtime estimates and downtime expectations provided - [x] Introduces new feature or API - [ ] Removes existing feature or API -- This is an automated message from the Apache Git Service. To respond to the message, please log on to GitHub and use the URL above to go to the specific comment. To unsubscribe, e-mail: [email protected] For queries about this service, please contact Infrastructure at: [email protected] --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
