feat(api-keys): add API key authentication via FAB SecurityManager#37973
Conversation
Codecov Report❌ Patch coverage is ❌ Your project status has failed because the head coverage (99.85%) is below the target coverage (100.00%). You can increase the head coverage or adjust the target coverage. Additional details and impacted files@@ Coverage Diff @@
## master #37973 +/- ##
==========================================
- Coverage 65.54% 64.39% -1.15%
==========================================
Files 1820 2535 +715
Lines 72868 130442 +57574
Branches 23339 30215 +6876
==========================================
+ Hits 47758 84004 +36246
- Misses 25110 44972 +19862
- Partials 0 1466 +1466
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
Code Review Agent Run #b5c2de
Actionable Suggestions - 1
-
requirements/base.txt - 1
- Supply Chain Security Risk · Line 123-123
Additional Suggestions - 5
-
superset-frontend/src/features/apiKeys/ApiKeyCreateModal.tsx - 2
-
Logic Error in Close Handler · Line 71-78The handleClose function sets createdKey to null before checking its value, which prevents onSuccess from being called after successful API key creation. This means the parent component won't refresh the API key list as intended.
Code suggestion
@@ -71,8 +71,8 @@ - const handleClose = () => { - setCreatedKey(null); - setCopied(false); - onHide(); - if (createdKey) { - onSuccess(); - } - }; + const handleClose = () => { + if (createdKey) { + onSuccess(); + } + setCreatedKey(null); + setCopied(false); + onHide(); + };
-
Missing Error Handling for Clipboard · Line 63-69The clipboard write operation lacks error handling. If the Clipboard API fails (e.g., due to permissions or browser limitations), users won't know the copy failed, leading to confusion.
-
-
superset-frontend/src/features/apiKeys/ApiKeyList.tsx - 3
-
Truncate API key prefix display · Line 142-152The key_prefix render appends '...' to the full string, potentially showing long values like 'sk-1234567890...'. Truncate to a fixed length (e.g., 10 chars) for better UX.
Code suggestion
diff --git a/superset-frontend/src/features/apiKeys/ApiKeyList.tsx b/superset-frontend/src/features/apiKeys/ApiKeyList.tsx index 0000000..0000000 100644 --- a/superset-frontend/src/features/apiKeys/ApiKeyList.tsx +++ b/superset-frontend/src/features/apiKeys/ApiKeyList.tsx @@ -142,7 +142,7 @@ render: (prefix: string) => ( <code css={css` background: ${theme.colorFillSecondary}; padding: 2px 6px; border-radius: 3px; `} > - {prefix}... + {prefix.length > 10 ? prefix.substring(0, 10) + '...' : prefix} </code> ), },
-
Specify locale in date formatting · Line 91-91Using undefined locale may lead to inconsistent date formatting across browsers; specify 'en-US' for consistency.
-
Replace custom spans with antd Tags · Line 98-130Use antd Tag components for status badges to avoid custom CSS and follow best practices from AGENTS.md.
Code suggestion
diff --git a/superset-frontend/src/features/apiKeys/ApiKeyList.tsx b/superset-frontend/src/features/apiKeys/ApiKeyList.tsx index 0000000..0000000 100644 --- a/superset-frontend/src/features/apiKeys/ApiKeyList.tsx +++ b/superset-frontend/src/features/apiKeys/ApiKeyList.tsx @@ -1,7 +1,7 @@ import { useCallback, useEffect, useState } from 'react'; import { t, SupersetClient } from '@superset-ui/core'; import { css, useTheme } from '@apache-superset/core/ui'; -import { Button, Table, Modal, Tooltip } from '@superset-ui/core/components'; +import { Button, Table, Modal, Tooltip, Tag } from '@superset-ui/core/components'; import { useToasts } from 'src/components/MessageToasts/withToasts'; import { ApiKeyCreateModal } from './ApiKeyCreateModal'; @@ -99,29 +99,15 @@ const getStatusBadge = (key: ApiKey) => { if (key.revoked_on) { - return ( - <span - css={css` - color: ${theme.colorError}; - `} - > - {t('Revoked')} - </span> - ); + return <Tag color="error">{t('Revoked')}</Tag>; } if (key.expires_on && new Date(key.expires_on) < new Date()) { - return ( - <span - css={css` - color: ${theme.colorWarning}; - `} - > - {t('Expired')} - </span> - ); + return <Tag color="warning">{t('Expired')}</Tag>; } - return ( - <span - css={css` - color: ${theme.colorSuccess}; - `} - > - {t('Active')} - </span> - ); + return <Tag color="success">{t('Active')}</Tag>; }; const columns = [
-
Review Details
-
Files reviewed - 6 · Commit Range:
100c366..100c366- requirements/base.txt
- superset-frontend/src/features/apiKeys/ApiKeyCreateModal.tsx
- superset-frontend/src/features/apiKeys/ApiKeyList.tsx
- superset/config.py
- superset/mcp_service/auth.py
- superset/migrations/versions/2026-02-14_12-00_f1a2b3c4d5e6_add_fab_api_key_table.py
-
Files skipped - 0
-
Tools
- Whispers (Secret Scanner) - ✔︎ Successful
- Detect-secrets (Secret Scanner) - ✔︎ Successful
- MyPy (Static Code Analysis) - ✔︎ Successful
- Astral Ruff (Static Code Analysis) - ✔︎ Successful
Bito Usage Guide
Commands
Type the following command in the pull request comment and save the comment.
-
/review- Manually triggers a full AI review. -
/pause- Pauses automatic reviews on this pull request. -
/resume- Resumes automatic reviews. -
/resolve- Marks all Bito-posted review comments as resolved. -
/abort- Cancels all in-progress reviews.
Refer to the documentation for additional commands.
Configuration
This repository uses Superset You can customize the agent settings here or contact your Bito workspace admin at evan@preset.io.
Documentation & Help
Code Review Agent Run #bb1233Actionable Suggestions - 0Additional Suggestions - 1
Review Details
Bito Usage GuideCommands Type the following command in the pull request comment and save the comment.
Refer to the documentation for additional commands. Configuration This repository uses Documentation & Help |
|
Addressed all review feedback in d653271: Fixed:
Acknowledged (no change needed):
|
Code Review Agent Run #a32c1bActionable Suggestions - 0Review Details
Bito Usage GuideCommands Type the following command in the pull request comment and save the comment.
Refer to the documentation for additional commands. Configuration This repository uses Documentation & Help |
3f7711b to
ede054c
Compare
There was a problem hiding this comment.
Code Review Agent Run #d57b0d
Actionable Suggestions - 2
-
requirements/development.txt - 2
- Dependency from Personal Fork · Line 265-265
- Unpinned Git Dependency · Line 265-265
Additional Suggestions - 2
-
tests/unit_tests/security/api_test.py - 1
-
Incorrect test expectation · Line 32-32The test expects "ApiKeyApi" to be in the set of CSRF-exempt blueprints, but no code defines or exempts an "ApiKeyApi" blueprint. The WTF_CSRF_EXEMPT_LIST in config.py contains endpoint paths for individual views, not blueprint names. This will cause the test to fail. Additionally, the test name "test_csrf_not_exempt" and comment "Test that REST API is not exempt from CSRF" are misleading since the assertion checks the exempt set, which includes SecurityApi (a REST API).
-
-
superset-frontend/src/pages/UserInfo/index.tsx - 1
-
Missing translation for user-facing text · Line 211-211The new 'API Keys' header should use the translation function for consistency with other user-facing strings in the codebase, such as the button labels that already use t().
Code suggestion
@@ -211,1 +211,1 @@ - header={<DescriptionTitle>API Keys</DescriptionTitle>} + header={<DescriptionTitle>{t('API Keys')}</DescriptionTitle>}
-
Review Details
-
Files reviewed - 9 · Commit Range:
c789fbb..178bfac- requirements/base.txt
- requirements/development.txt
- superset-frontend/src/features/apiKeys/ApiKeyCreateModal.tsx
- superset-frontend/src/features/apiKeys/ApiKeyList.tsx
- superset-frontend/src/pages/UserInfo/index.tsx
- superset/config.py
- superset/mcp_service/auth.py
- superset/migrations/versions/2026-02-14_12-00_f1a2b3c4d5e6_add_fab_api_key_table.py
- tests/unit_tests/security/api_test.py
-
Files skipped - 0
-
Tools
- Whispers (Secret Scanner) - ✔︎ Successful
- Detect-secrets (Secret Scanner) - ✔︎ Successful
- MyPy (Static Code Analysis) - ✔︎ Successful
- Astral Ruff (Static Code Analysis) - ✔︎ Successful
Bito Usage Guide
Commands
Type the following command in the pull request comment and save the comment.
-
/review- Manually triggers a full AI review. -
/pause- Pauses automatic reviews on this pull request. -
/resume- Resumes automatic reviews. -
/resolve- Marks all Bito-posted review comments as resolved. -
/abort- Cancels all in-progress reviews.
Refer to the documentation for additional commands.
Configuration
This repository uses Superset You can customize the agent settings here or contact your Bito workspace admin at evan@preset.io.
Documentation & Help
msyavuz
left a comment
There was a problem hiding this comment.
Looks good aside from small nits. Great feature to have!
✅ Deploy Preview for superset-docs-preview ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
Code Review Agent Run #9ebbfeActionable Suggestions - 0Review Details
Bito Usage GuideCommands Type the following command in the pull request comment and save the comment.
Refer to the documentation for additional commands. Configuration This repository uses Documentation & Help |
|
Note on CI failures: The 2 red checks (
Both will resolve once the FAB PR is merged and released to PyPI. All other 68 checks pass. |
Code Review Agent Run #3665adActionable Suggestions - 1
Review Details
Bito Usage GuideCommands Type the following command in the pull request comment and save the comment.
Refer to the documentation for additional commands. Configuration This repository uses Documentation & Help |
Code Review Agent Run #433ea6Actionable Suggestions - 0Review Details
Bito Usage GuideCommands Type the following command in the pull request comment and save the comment.
Refer to the documentation for additional commands. Configuration This repository uses Documentation & Help |
Code Review Agent Run #6a6f44Actionable Suggestions - 0Review Details
Bito Usage GuideCommands Type the following command in the pull request comment and save the comment.
Refer to the documentation for additional commands. Configuration This repository uses Documentation & Help |
Code Review Agent Run #b35d42Actionable Suggestions - 1
Review Details
Bito Usage GuideCommands Type the following command in the pull request comment and save the comment.
Refer to the documentation for additional commands. Configuration This repository uses Documentation & Help |
Remove parentheses from @pytest.fixture() decorators and apply ruff format to fix pre-commit CI failure.
- Mock has_request_context in no-request-context test since CI's app fixture may implicitly provide a request context - Clear MCP_DEV_USERNAME in _disable_api_keys fixture to prevent fallthrough to dev user path - Fix ruff PT001 parentheses on pytest.fixture decorators
- Store copy-status timeout in useRef and clear on unmount to prevent state updates on unmounted component - Clear existing timer before creating new one on repeated clicks - Migration now creates indexes even when table already exists (e.g., from FAB's create_all()) by checking existing indexes first
The API key secret modal could be dismissed by clicking outside or pressing Escape, risking permanent loss of the key. Added maskClosable and closable props to force users to click the Done button.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Retrigger CI after playwright infra flake (server failed to start).
DatasetList.listview.test.tsx and FileHandler/index.test.tsx timed out - unrelated to API key changes. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Change ValueError to PermissionError for invalid/expired API key authentication failures so GlobalErrorHandlerMiddleware routes them through the "Permission denied" branch instead of "Invalid parameter" - Fix migration downgrade to only drop indexes (not the table) since upgrade handles pre-existing tables created by FAB's create_all() - Update tests to expect PermissionError
Code Review Agent Run #a5741dActionable Suggestions - 0Additional Suggestions - 2
Review Details
Bito Usage GuideCommands Type the following command in the pull request comment and save the comment.
Refer to the documentation for additional commands. Configuration This repository uses Documentation & Help |
Add runtime/downtime estimates and downgrade rationale to the module docstring per SIP-59 requirements. Give the user_id foreign key an explicit constraint name (fk_ab_api_key_user_id) for deterministic naming across database backends.
Code Review Agent Run #a002d3Actionable Suggestions - 0Review Details
Bito Usage GuideCommands Type the following command in the pull request comment and save the comment.
Refer to the documentation for additional commands. Configuration This repository uses Documentation & Help |
…ame" This reverts commit b04a415.
FAB's SecurityManager.create_db() already creates ab_api_key via Model.metadata.create_all(engine) during app initialization — before Alembic migrations run. No other ab_* table has an Alembic migration. The migration's `if not table_exists` check would almost always skip. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
Bito Automatic Review Skipped – PR Already Merged |
User description
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: dpgaspar/Flask-AppBuilder#2431 (FAB feature branch — all CI green)
Related SIP: #37971
How Authentication Works
Superset supports three authentication methods. API keys slot in alongside the existing ones:
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:#fffAPI Key Lifecycle
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 ForbiddenWhat's in this PR
Backend:
SecurityManager.validate_api_key()FAB_API_KEY_ENABLED = TrueandFAB_API_KEY_PREFIXES = ["sst_"]in default configcreate_db()creates theab_api_keytable automatically)Frontend:
/user_info/)ApiKeyList— table with name, prefix, status (Active/Revoked/Expired), last used, revoke buttonApiKeyCreateModal— create key with name, shows plaintext once with copy buttonSecurity:
werkzeug.generate_password_hash— no plaintext in DBlast_used_onupdated on each use for audit trailg._api_key_user = Trueflag distinguishes API key auth from session/JWTBEFORE/AFTER SCREENSHOTS OR ANIMATED GIF
| | Create API Key modal |

| | Key created — shown once with copy button |

| | Active key in list with Revoke button |

| | Revoked key showing status badge |

TESTING INSTRUCTIONS
Prerequisites
Test 1: Create an API 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
Expected: 200 with data on all endpoints.
Test 3: Auth Rejection Scenarios
Authorization: Bearer sst_invalidkeyAuthorization: Bearer some_random_jwtTest 4: API Key CRUD
Test 5: JWT Still Works
curl -H "Authorization: Bearer $JWT" http://localhost:8088/api/v1/chart/Expected: 200 — JWT and API key auth coexist.
Test 6: RBAC Respected
Test 7: UI
/user_info/→ see "API Keys" sectionADDITIONAL INFORMATION
SecurityManager.create_db())MCP Tool Testing Results
Tested against running Superset MCP service (localhost:5008) on 2026-03-24:
health_check{}{"status":"healthy","service":"Preset MCP Service"}get_instance_info{}list_dashboards{"page":1,"page_size":3}list_charts{"page":1,"page_size":3}list_datasets{"page":1,"page_size":3}get_chart_info{"identifier":4}execute_sql{"database_id":2,"sql":"SELECT 1 as test_col"}generate_explore_link{"dataset_id":19,"config":{...}}All tools executed successfully with proper auth context (Admin user).
CodeAnt-AI Description
Add API key management in user settings and let API keys sign in to MCP tools
What Changed
Impact
✅ Shorter API access setup✅ Fewer JWT refresh issues for automation✅ Clearer API key management for users💡 Usage Guide
Checking Your Pull Request
Every time you make a pull request, our system automatically looks through it. We check for security issues, mistakes in how you're setting up your infrastructure, and common code problems. We do this to make sure your changes are solid and won't cause any trouble later.
Talking to CodeAnt AI
Got a question or need a hand with something in your pull request? You can easily get in touch with CodeAnt AI right here. Just type the following in a comment on your pull request, and replace "Your question here" with whatever you want to ask:
This lets you have a chat with CodeAnt AI about your pull request, making it easier to understand and improve your code.
Example
Preserve Org Learnings with CodeAnt
You can record team preferences so CodeAnt AI applies them in future reviews. Reply directly to the specific CodeAnt AI suggestion (in the same thread) and replace "Your feedback here" with your input:
This helps CodeAnt AI learn and adapt to your team's coding style and standards.
Example
Retrigger review
Ask CodeAnt AI to review the PR again, by typing:
Check Your Repository Health
To analyze the health of your code repository, visit our dashboard at https://app.codeant.ai. This tool helps you identify potential issues and areas for improvement in your codebase, ensuring your repository maintains high standards of code health.