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]

Reply via email to