Skip to content

feat(api-keys): add API key authentication via FAB SecurityManager#36173

Closed
aminghadersohi wants to merge 14 commits into
apache:masterfrom
aminghadersohi:feat/mcp-api-key-models
Closed

feat(api-keys): add API key authentication via FAB SecurityManager#36173
aminghadersohi wants to merge 14 commits into
apache:masterfrom
aminghadersohi:feat/mcp-api-key-models

Conversation

@aminghadersohi
Copy link
Copy Markdown
Contributor

@aminghadersohi aminghadersohi commented Nov 18, 2025

SUMMARY

Add API key authentication to Apache Superset, implemented at the Flask-AppBuilder (FAB) layer per community feedback. This enables programmatic access via long-lived API keys for MCP service integrations, CI/CD pipelines, external applications, and scheduled jobs.

Architecture Decision

Based on community feedback, API key auth is implemented in FAB's SecurityManager rather than as Superset-specific code. This ensures:

  • @protect() works automatically with API keys
  • No split between FAB and Superset auth systems
  • MCP service inherits validation via SupersetSecurityManager
  • Any FAB-based project can use API keys

How It Works

  1. Users create API keys via POST /api/v1/security/api_keys/ (FAB endpoint)
  2. Keys use configurable prefix (sst_ by default) for identification
  3. Clients authenticate with Authorization: Bearer sst_<key>
  4. FAB's @protect() decorator validates the key before JWT verification
  5. Keys inherit the user's RBAC permissions

Changes in This PR

Removed (moved to FAB):

  • superset/models/api_keys.py - model now in FAB
  • superset/daos/api_key.py - DAO now in FAB SecurityManager
  • superset/utils/api_key.py - key generation/validation now in FAB
  • superset/commands/api_key/ - commands replaced by FAB SecurityManager methods
  • Old migration e8f4a3b2c1d0 - replaced with FAB-compatible migration

Modified:

  • superset/mcp_service/auth.py - uses security_manager.validate_api_key() instead of custom code
  • superset/views/users/api.py - removed API key endpoints (now in FAB's ApiKeyApi)
  • superset/config.py - added FAB_API_KEY_ENABLED and FAB_API_KEY_PREFIXES config
  • Frontend components updated to use /api/v1/security/api_keys/ endpoint

Added:

  • New migration f1a2b3c4d5e6 for ab_api_key table (compatible with FAB's model schema)

Dependencies

BEFORE/AFTER SCREENSHOTS OR ANIMATED GIF

N/A - API-only changes. Frontend API key management UI updated to use new endpoint.

TESTING INSTRUCTIONS

  1. Install FAB feature branch: pip install git+https://github.com/aminghadersohi/Flask-AppBuilder@amin/ch99414/api-key-auth
  2. Ensure FAB_API_KEY_ENABLED = True in config (default in this PR)
  3. Create an API key:
    curl -X POST http://localhost:8088/api/v1/security/api_keys/ \
      -H "Authorization: Bearer <jwt_token>" \
      -H "Content-Type: application/json" \
      -d '{"name": "test-key"}'
  4. Use the returned key to authenticate:
    curl http://localhost:8088/api/v1/security/roles/ \
      -H "Authorization: Bearer sst_<returned_key>"
  5. Verify key works on MCP endpoints as well

ADDITIONAL INFORMATION

@bito-code-review
Copy link
Copy Markdown
Contributor

bito-code-review Bot commented Nov 18, 2025

Code Review Agent Run #2af87a

Actionable Suggestions - 0
Additional Suggestions - 9
  • superset/migrations/versions/2025-11-18_12-00_e8f4a3b2c1d0_add_mcp_api_keys.py - 5
    • Use UUID primary key for new table · Line 37-37
      The migration creates a new ab_api_key table with an Integer primary key, but the project's guidelines specify that new models should use UUID primary keys for better scalability and consistency in distributed systems. This ensures alignment with established patterns seen in other models like embedded_dashboard. The corresponding ApiKey model in superset/models/mcp.py will also need to be updated to use a UUID primary key to maintain consistency.
      Code suggestion
       @@ -25,1 +25,3 @@
       import sqlalchemy as sa
      +import uuid
      +from sqlalchemy_utils import UUIDType
       from alembic import op
       @@ -37,1 +39,1 @@
               sa.Column("id", sa.Integer(), nullable=False),
      +        sa.Column("id", UUIDType(binary=True), nullable=False, default=uuid.uuid4),
    • Missing return type annotation upgrade function · Line 33-33
      The `upgrade` function is missing a return type annotation. Add `-> None` to indicate it returns nothing. Similar issue exists for the `downgrade` function on line 70.
      Code suggestion
       @@ -33,1 +33,1 @@
      -def upgrade():
      +def upgrade() -> None:
    • Missing return type annotation downgrade function · Line 70-70
      The `downgrade` function is missing a return type annotation. Add `-> None` to indicate it returns nothing.
      Code suggestion
       @@ -70,1 +70,1 @@
      -def downgrade():
      +def downgrade() -> None:
    • Docstring missing period punctuation · Line 17-17
      The docstring `"add mcp api keys"` should end with a period for proper documentation formatting.
      Code suggestion
       @@ -17,1 +17,1 @@
      -"""add mcp api keys
      +"""add mcp api keys.
    • Missing trailing comma in function call · Line 60-60
      Missing trailing comma after `["workspace_name"]` on line 60. Similar issues exist on lines 63 and 66 for other `create_index` calls.
      Code suggestion
       @@ -60,1 +60,1 @@
      -            "idx_api_key_workspace", ["workspace_name"], unique=False
      +            "idx_api_key_workspace", ["workspace_name"], unique=False,
  • superset/models/mcp.py - 4
    • Simplify boolean condition return statement · Line 112-112
      Return the negated condition directly instead of using if-else. Replace the if-else block with `return self.expires_on is None or self.expires_on >= datetime.utcnow()`.
      Code suggestion
       @@ -111,5 +111,4 @@
      -
      -        if self.expires_on is not None and self.expires_on < datetime.utcnow():
      -            return False
      -
      -        return True
      +
      +        return self.expires_on is None or self.expires_on >= datetime.utcnow()
    • Multi-line docstring summary formatting issue · Line 17-17
      Multi-line docstring summary should start at the first line. This issue also occurs at lines 33 and 103. Move the summary text to the same line as the opening quotes.
      Code suggestion
       @@ -17,6 +17,5 @@
      -"""
      -Models for MCP (Model Context Protocol) service.
      -
      -This module contains database models specific to the MCP service,
      -including API key authentication.
      -"""
      +"""Models for MCP (Model Context Protocol) service.
      +
      +This module contains database models specific to the MCP service,
      +including API key authentication.
      +"""
    • Missing blank line after docstring section · Line 106-106
      Missing blank line after the "Returns" section in the docstring. Add a blank line after the Returns section for proper formatting.
      Code suggestion
       @@ -105,6 +105,7 @@
      -        Returns:
      -            True if key is not revoked and not expired, False otherwise
      -        """
      +        Returns:
      +            True if key is not revoked and not expired, False otherwise
      +
      +        """
    • Docstring should use imperative mood · Line 118-118
      First line of docstring should be in imperative mood. Change "String representation of ApiKey." to "Return string representation of ApiKey."
      Code suggestion
       @@ -117,2 +117,2 @@
      -    def __repr__(self) -> str:
      -        """String representation of ApiKey."""
      +    def __repr__(self) -> str:
      +        """Return string representation of ApiKey."""
Review Details
  • Files reviewed - 5 · Commit Range: 65065fc..65065fc
    • superset-frontend/src/features/apiKeys/ApiKeyCreateModal.tsx
    • superset-frontend/src/features/apiKeys/ApiKeyList.tsx
    • superset-frontend/src/pages/UserInfo/index.tsx
    • superset/migrations/versions/2025-11-18_12-00_e8f4a3b2c1d0_add_mcp_api_keys.py
    • superset/models/mcp.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 Default Agent You can customize the agent settings here or contact your Bito workspace admin at evan@preset.io.

Documentation & Help

AI Code Review powered by Bito Logo

@github-actions github-actions Bot added the risk:db-migration PRs that require a DB migration label Nov 18, 2025
@dosubot dosubot Bot added authentication Related to authentication change:frontend Requires changing the frontend labels Nov 18, 2025
@aminghadersohi aminghadersohi marked this pull request as draft November 18, 2025 21:57
@codecov
Copy link
Copy Markdown

codecov Bot commented Nov 18, 2025

Codecov Report

❌ Patch coverage is 70.66667% with 88 lines in your changes missing coverage. Please review.
✅ Project coverage is 68.12%. Comparing base (76d897e) to head (5082421).
⚠️ Report is 2944 commits behind head on master.

Files with missing lines Patch % Lines
superset/mcp_service/auth.py 0.00% 33 Missing ⚠️
superset/views/users/api.py 76.66% 14 Missing ⚠️
superset/utils/api_key.py 50.00% 13 Missing ⚠️
superset/daos/api_key.py 76.19% 10 Missing ⚠️
superset/commands/api_key/create.py 82.35% 3 Missing and 3 partials ⚠️
superset/models/api_keys.py 87.17% 5 Missing ⚠️
superset/commands/api_key/revoke.py 85.18% 2 Missing and 2 partials ⚠️
superset/commands/api_key/exceptions.py 88.88% 2 Missing ⚠️
superset/mcp_service/mcp_config.py 0.00% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master   #36173      +/-   ##
==========================================
+ Coverage   60.48%   68.12%   +7.63%     
==========================================
  Files        1931      638    -1293     
  Lines       76236    46765   -29471     
  Branches     8568     5057    -3511     
==========================================
- Hits        46114    31857   -14257     
+ Misses      28017    13641   -14376     
+ Partials     2105     1267     -838     
Flag Coverage Δ
hive 43.84% <52.33%> (-5.32%) ⬇️
javascript ?
mysql 67.24% <70.66%> (?)
postgres 67.29% <70.66%> (?)
presto 47.44% <52.33%> (-6.37%) ⬇️
python 68.08% <70.66%> (+4.58%) ⬆️
sqlite 66.91% <70.66%> (?)
unit 100.00% <ø> (+42.36%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@aminghadersohi aminghadersohi force-pushed the feat/mcp-api-key-models branch from 53e36d3 to 3de0f37 Compare November 20, 2025 04:13
@github-actions github-actions Bot added the api Related to the REST API label Nov 20, 2025
@aminghadersohi aminghadersohi changed the title feat(mcp): add API key database models, migration, and UI feat(api-keys): complete API key authentication system with REST endpoints Nov 20, 2025
@dpgaspar dpgaspar self-requested a review November 20, 2025 16:18
Comment thread superset/commands/api_key/revoke.py Outdated
if not api_key:
raise ApiKeyNotFoundError()

ApiKeyDAO.revoke(api_key, revoked_by_fk=g.user.id)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: Thinking if revoke should belong to the DAO, do we consider a DAO to be an abstraction around CRUD. Some commands also touch the db directly. WDYT?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Based on my first impression, I think revocation is a command that does a DAO based update on the API Key entity.

Comment thread superset/daos/api_key.py Outdated
Comment thread superset/daos/api_key.py Outdated
expires_on=expires_on,
created_on=datetime.utcnow(),
)
db.session.add(api_key)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we should just do: return super().create(item, attributes)

Comment thread superset/daos/api_key.py Outdated
Comment thread superset/daos/api_key.py Outdated
api_key: The ApiKey object to delete
"""
db.session.delete(api_key)
db.session.commit()
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

commit at the DAO level same as above

Comment thread superset/models/api_keys.py Outdated
Comment thread superset/utils/api_key.py Outdated
Comment thread superset/views/users/api.py Outdated
if g.user is None or g.user.is_anonymous:
return self.response_401()
except NoAuthorizationError:
return self.response_401()
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we don't need this, use the @protect() for authorization and authentication

Copy link
Copy Markdown
Member

@villebro villebro left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

First pass comments. Before proceeding with this, I suggest opening a SIP that outlines how API keys should work in practice. Some important themes that need discussion:

  • Scoping: Optimally, it would be possible to downscope the users creds in the API keys. As the current permission system is very granular, I think it makes sense to group perms together, like
    • Dashboard: "no access, read, write"
    • Charts: "no access, read, write"
    • ...
  • Authorization: How will these keys be handled in practice in the security manager
  • Auth mechanism: I assume these will be passed as bearer tokens on API/MCP calls but not UI routes, but let's clarify that.
  • Long term support: Will current FAB API tokens be supported after these are rolled out?
  • Long term security plan: How this relates to the planned security model revamp that's been discussed in SIP-131

etc.

Comment thread superset/migrations/versions/2025-11-18_12-00_e8f4a3b2c1d0_add_mcp_api_keys.py Outdated
Comment thread scripts/uv-pip-compile.sh
Comment thread superset-frontend/src/features/apiKeys/ApiKeyList.tsx Outdated
Comment thread superset/commands/api_key/create.py Outdated
Comment on lines +88 to +90
# Parse expires_on if it's a string
if isinstance(expires_on, str):
expires_on = datetime.fromisoformat(expires_on)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd prefer not to support multiple formats. We should have a Marshmallow schema in place that validates the input and parses the API request body into a standardized data structure with preferably a datetime for expires_on

Comment thread superset/commands/api_key/revoke.py Outdated
if not api_key:
raise ApiKeyNotFoundError()

ApiKeyDAO.revoke(api_key, revoked_by_fk=g.user.id)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Based on my first impression, I think revocation is a command that does a DAO based update on the API Key entity.

Comment thread superset/daos/api_key.py Outdated
Comment on lines +46 to +47
sa.Column("revoked_on", sa.DateTime(), nullable=True),
sa.Column("revoked_by_fk", sa.Integer(), nullable=True),
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do revoked keys need to stay in the metastore? Or can we just remove them? Not sure what the typical policy is for these, but I assume revoked keys should not be unrevokable, hence they probably don't need to exist in the metastore after they've been revoked. Same for expired keys - I think we can just remove them when they're no longer valid (in the KeyValue DAO we have logic for cleaning up expired entries automatically which we could use for inspiration here).

Comment thread superset/utils/api_key.py Outdated
import bcrypt

# API key configuration
API_KEY_PREFIX = "pst" # Preset/Superset prefix for easy identification
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this needed? Also, let's not have Preset references in the upstream codebase

Suggested change
API_KEY_PREFIX = "pst" # Preset/Superset prefix for easy identification
API_KEY_PREFIX = "pst" # Superset prefix for easy identification

aminghadersohi and others added 9 commits November 21, 2025 12:16
Add database schema and UI for MCP service API key authentication.

This PR adds the database layer (models and migration) plus a basic UI
for users to manage their API keys. Authentication logic will be
implemented separately in the superset-shell fork.

Database Changes:
- Add ApiKey model in superset/models/mcp.py
- Add migration 2025-11-18_12-00_e8f4a3b2c1d0_add_mcp_api_keys.py
- Create ab_api_key table with proper indexes and foreign keys
- Support for key expiration, revocation, and usage tracking
- Workspace-scoped keys for multi-tenant isolation

Frontend Changes:
- Add API Keys section to User Info page
- Add ApiKeyList component to display and manage API keys
- Add ApiKeyCreateModal for creating new API keys
- API keys shown only once at creation for security

The ab_api_key table stores:
- bcrypt hash (never plaintext)
- Workspace scoping for multi-tenancy
- Expiration and revocation support
- Usage tracking
- User ownership for RBAC

Related to preset-io/superset-shell#3252
Alert component is exported from @apache-superset/core/ui, not from
@superset-ui/core/components. This fixes the TypeScript build error
in the docker build and frontend type checking.
The ApiKey model is generic and not specific to MCP. Renamed the file
and updated docstrings to reflect this:

- superset/models/mcp.py -> superset/models/api_keys.py
- Updated module and class docstrings to remove MCP-specific language
- Updated migration description to be generic

The model can be used for any API key authentication, not just MCP.
Users can now leave workspace_name blank when creating an API key.
The backend will default it to 'default' workspace if not provided.

This matches the frontend UI which says 'Optional: Leave blank to use
your default workspace'.
…ponents

- Fix modal closing immediately after API key creation
- Move setShowCreateModal(false) to onHide instead of onSuccess
- Change FormModal onSave to empty function to prevent premature close
- Restore missing ApiKeyCreateModal.tsx and ApiKeyList.tsx components
- Copy button now visible after key creation

Users can now copy the API key before the modal closes.
…_name required

- Add API key endpoints (get_api_keys, create_api_key, revoke_api_key) to security test allowlist
- Make workspace_name field required in ApiKeyPostSchema
- Update CreateApiKeyCommand validation to require workspace_name
- Fixes test_views_are_secured and test_create_api_key_missing_workspace failures
- Make workspace_name optional with load_default='default' in schema
- Update test to verify default workspace is used when not provided
- Users can now create API keys without specifying workspace
@dpgaspar
Copy link
Copy Markdown
Member

dpgaspar commented Nov 21, 2025

First pass comments. Before proceeding with this, I suggest opening a SIP that outlines how API keys should work in practice. Some important themes that need discussion:

  • Scoping: Optimally, it would be possible to downscope the users creds in the API keys. As the current permission system is very granular, I think it makes sense to group perms together, like

    • Dashboard: "no access, read, write"
    • Charts: "no access, read, write"
    • ...
  • Authorization: How will these keys be handled in practice in the security manager

  • Auth mechanism: I assume these will be passed as bearer tokens on API/MCP calls but not UI routes, but let's clarify that.

  • Long term support: Will current FAB API tokens be supported after these are rolled out?

  • Long term security plan: How this relates to the planned security model revamp that's been discussed in SIP-131

etc.

I got the impression this was just for MCP auth. But seems I was wrong. If these API keys are to be used globally on all our current REST API as is they won't be respected by flask-appbuilder authentication and authorization system. Globally it would probably make more sense to implement this at the flask-appbuilder layer for easier integration with the @protect and @has_access decorators

@villebro
Copy link
Copy Markdown
Member

First pass comments. Before proceeding with this, I suggest opening a SIP that outlines how API keys should work in practice. Some important themes that need discussion:

  • Scoping: Optimally, it would be possible to downscope the users creds in the API keys. As the current permission system is very granular, I think it makes sense to group perms together, like

    • Dashboard: "no access, read, write"
    • Charts: "no access, read, write"
    • ...
  • Authorization: How will these keys be handled in practice in the security manager

  • Auth mechanism: I assume these will be passed as bearer tokens on API/MCP calls but not UI routes, but let's clarify that.

  • Long term support: Will current FAB API tokens be supported after these are rolled out?

  • Long term security plan: How this relates to the planned security model revamp that's been discussed in SIP-131

etc.

I got the impression this was just for MCP auth. But seems I was wrong. If these API keys are to be used globally on all our current REST API as is they won't be respected by flask-appbuilder authentication and authorization system. Globally it would probably make more sense to implement this at the flask-appbuilder layer.

@dpgaspar good callouts. All the more reason to debate this in a SIP before proceeding.

Enable MCP service to authenticate users via API keys created in
the Superset UI. This provides a secure alternative to JWT tokens
for programmatic access.

Authentication priority:
1. g.user (if already set by middleware)
2. API key from Authorization header (Bearer pst_...)
3. MCP_DEV_USERNAME (development fallback)

Features:
- API keys prefixed with 'pst_' are recognized and validated
- Keys are checked against bcrypt hashes in ab_api_key table
- Expired and revoked keys are rejected
- last_used_on timestamp updated on successful auth
- User inherits all roles and permissions from key owner

Configuration:
- MCP_API_KEY_AUTH_ENABLED: Toggle API key auth (default: True)

Usage:
  Authorization: Bearer pst_abc123xyz...
@aminghadersohi aminghadersohi force-pushed the feat/mcp-api-key-models branch from dd120f0 to 5082421 Compare November 21, 2025 20:43
@aminghadersohi aminghadersohi changed the title feat(api-keys): complete API key authentication system with REST endpoints feat(api-keys): add API key authentication for Superset and MCP service Nov 21, 2025
aminghadersohi added a commit to aminghadersohi/Flask-AppBuilder that referenced this pull request Feb 14, 2026
Add API key authentication to Flask-AppBuilder, enabling programmatic
access to FAB-protected endpoints without JWT tokens or browser sessions.

Changes:
- Add ApiKey model (ab_api_key table) with uuid, key_hash, prefix, scopes,
  active status, expiration, and revocation tracking
- Add validate_api_key() and _extract_api_key_from_request() to
  BaseSecurityManager with concrete SQLA implementations
- Modify @Protect() decorator to check API key auth before JWT verification
- Add CRUD API endpoints at /api/v1/security/api_keys/ (list, create,
  get, revoke) gated behind FAB_API_KEY_ENABLED config
- Update has_access() to recognize API key authenticated users
- Update _create_db() to auto-create ab_api_key table on existing installs
- Add 24 tests covering model, SecurityManager methods, CRUD endpoints,
  and @Protect() decorator integration

Config keys:
- FAB_API_KEY_ENABLED (bool): Enable API key auth (default: False)
- FAB_API_KEY_PREFIXES (list): Key prefixes to recognize (default: ["sst_"])

References: apache/superset#36173

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Move API key authentication from Superset-specific implementation to
Flask-AppBuilder's SecurityManager layer. This ensures @Protect()
works automatically with API keys and downstream apps inherit the
feature via their SecurityManager.

Changes:
- Remove Superset-only API key code (model, DAO, utils, commands)
- Update MCP auth to use FAB's SecurityManager.validate_api_key()
- Remove API key endpoints from CurrentUserRestApi (now in FAB's ApiKeyApi)
- Update frontend to use /api/v1/security/api_keys/ endpoint
- Add FAB_API_KEY_ENABLED and FAB_API_KEY_PREFIXES config
- Replace old migration with FAB-compatible ab_api_key table migration
- Pin FAB to feature branch with API key support
- Update key prefix from pst_ to sst_

Depends on: https://github.com/aminghadersohi/Flask-AppBuilder/tree/amin/ch99414/api-key-auth
Related: dpgaspar/Flask-AppBuilder#2430

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@aminghadersohi aminghadersohi changed the title feat(api-keys): add API key authentication for Superset and MCP service feat(api-keys): add API key authentication via FAB SecurityManager Feb 14, 2026
@netlify
Copy link
Copy Markdown

netlify Bot commented Feb 14, 2026

Deploy Preview for superset-docs-preview ready!

Name Link
🔨 Latest commit e0d70ce
🔍 Latest deploy log https://app.netlify.com/projects/superset-docs-preview/deploys/698fd0c2128eb300084a55e4
😎 Deploy Preview https://deploy-preview-36173--superset-docs-preview.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@aminghadersohi
Copy link
Copy Markdown
Contributor Author

Superseded by #37973 - fresh branch based on current master with cleaner implementation delegating to FAB SecurityManager.

aminghadersohi added a commit to aminghadersohi/Flask-AppBuilder that referenced this pull request Feb 17, 2026
Add API key authentication to Flask-AppBuilder, enabling programmatic
access to FAB-protected endpoints without JWT tokens or browser sessions.

Changes:
- Add ApiKey model (ab_api_key table) with uuid, key_hash, prefix, scopes,
  active status, expiration, and revocation tracking
- Add validate_api_key() and _extract_api_key_from_request() to
  BaseSecurityManager with concrete SQLA implementations
- Modify @Protect() decorator to check API key auth before JWT verification
- Add CRUD API endpoints at /api/v1/security/api_keys/ (list, create,
  get, revoke) gated behind FAB_API_KEY_ENABLED config
- Update has_access() to recognize API key authenticated users
- Update _create_db() to auto-create ab_api_key table on existing installs
- Add 24 tests covering model, SecurityManager methods, CRUD endpoints,
  and @Protect() decorator integration

Config keys:
- FAB_API_KEY_ENABLED (bool): Enable API key auth (default: False)
- FAB_API_KEY_PREFIXES (list): Key prefixes to recognize (default: ["sst_"])

References: apache/superset#36173
aminghadersohi added a commit to aminghadersohi/Flask-AppBuilder that referenced this pull request Feb 23, 2026
Add API key authentication to Flask-AppBuilder, enabling programmatic
access to FAB-protected endpoints without JWT tokens or browser sessions.

Changes:
- Add ApiKey model (ab_api_key table) with uuid, key_hash, prefix, scopes,
  active status, expiration, and revocation tracking
- Add validate_api_key() and _extract_api_key_from_request() to
  BaseSecurityManager with concrete SQLA implementations
- Modify @Protect() decorator to check API key auth before JWT verification
- Add CRUD API endpoints at /api/v1/security/api_keys/ (list, create,
  get, revoke) gated behind FAB_API_KEY_ENABLED config
- Update has_access() to recognize API key authenticated users
- Update _create_db() to auto-create ab_api_key table on existing installs
- Add 24 tests covering model, SecurityManager methods, CRUD endpoints,
  and @Protect() decorator integration

Config keys:
- FAB_API_KEY_ENABLED (bool): Enable API key auth (default: False)
- FAB_API_KEY_PREFIXES (list): Key prefixes to recognize (default: ["sst_"])

References: apache/superset#36173
aminghadersohi added a commit to aminghadersohi/Flask-AppBuilder that referenced this pull request Feb 27, 2026
Add API key authentication to Flask-AppBuilder, enabling programmatic
access to FAB-protected endpoints without JWT tokens or browser sessions.

Changes:
- Add ApiKey model (ab_api_key table) with uuid, key_hash, prefix, scopes,
  active status, expiration, and revocation tracking
- Add validate_api_key() and _extract_api_key_from_request() to
  BaseSecurityManager with concrete SQLA implementations
- Modify @Protect() decorator to check API key auth before JWT verification
- Add CRUD API endpoints at /api/v1/security/api_keys/ (list, create,
  get, revoke) gated behind FAB_API_KEY_ENABLED config
- Update has_access() to recognize API key authenticated users
- Update _create_db() to auto-create ab_api_key table on existing installs
- Add 24 tests covering model, SecurityManager methods, CRUD endpoints,
  and @Protect() decorator integration

Config keys:
- FAB_API_KEY_ENABLED (bool): Enable API key auth (default: False)
- FAB_API_KEY_PREFIXES (list): Key prefixes to recognize (default: ["sst_"])

References: apache/superset#36173
dpgaspar pushed a commit to dpgaspar/Flask-AppBuilder that referenced this pull request Mar 1, 2026
* feat: add API key authentication support

Add API key authentication to Flask-AppBuilder, enabling programmatic
access to FAB-protected endpoints without JWT tokens or browser sessions.

Changes:
- Add ApiKey model (ab_api_key table) with uuid, key_hash, prefix, scopes,
  active status, expiration, and revocation tracking
- Add validate_api_key() and _extract_api_key_from_request() to
  BaseSecurityManager with concrete SQLA implementations
- Modify @Protect() decorator to check API key auth before JWT verification
- Add CRUD API endpoints at /api/v1/security/api_keys/ (list, create,
  get, revoke) gated behind FAB_API_KEY_ENABLED config
- Update has_access() to recognize API key authenticated users
- Update _create_db() to auto-create ab_api_key table on existing installs
- Add 24 tests covering model, SecurityManager methods, CRUD endpoints,
  and @Protect() decorator integration

Config keys:
- FAB_API_KEY_ENABLED (bool): Enable API key auth (default: False)
- FAB_API_KEY_PREFIXES (list): Key prefixes to recognize (default: ["sst_"])

References: apache/superset#36173

* fix: apply black formatting to API key auth files

* fix: revert to black 23.10 formatting for decorators.py

* fix: remove unused imports in test_api_key.py

* fix: ensure ApiKey permissions are created when update_perms is False

When AppBuilder is initialized with update_perms=False (as Superset does),
the standard _add_permission() call in add_view_no_menu() is a no-op.
This means ApiKeyApi permissions are never created, causing all API key
endpoints to return 403 Forbidden.

Fix by explicitly calling add_permissions_view() after registering the
ApiKeyApi, which creates the permission-view-menu entries and assigns
them to the Admin role regardless of the update_perms flag. This is
idempotent and safe when update_perms is True (permissions already exist).

Adds tests that verify permissions exist, Admin role has them, and
endpoints work when update_perms=False.

* feat: add lookup_hash for O(1) key validation, address review feedback

- Add lookup_hash column (indexed, unique) for constant-time API key
  lookup instead of iterating all keys with prefix matching
- Add configurable hash algorithms:
  - FAB_API_KEY_LOOKUP_HASH_METHOD (default: sha256) for fast lookup
  - FAB_API_KEY_HASH_METHOD (default: falls back to FAB_PASSWORD_HASH_METHOD)
  - FAB_API_KEY_HASH_SALT_LENGTH for the slow verification hash
- Address review feedback from @dpgaspar:
  - Use sm.current_user instead of custom _get_current_user helper
  - Update sm.current_user property to handle API key auth
  - Let create_api_key/revoke_api_key raise instead of returning None
  - Move imports to module level
  - Drop getattr guard on user.is_active
  - Respect update_perms=False without exceptions

* style: apply black formatting to models and manager

* fix: use HMAC for lookup hash to resolve CodeQL alerts

Replace plain SHA-256 with HMAC keyed by SECRET_KEY for the API key
lookup hash. This prevents pre-computation of lookup hashes without
the server secret and resolves CodeQL's "weak cryptographic hashing
algorithm on sensitive data" alerts.

* fix: remove unused hashlib import, suppress CodeQL false positive

- Remove unused hashlib import (lint failure)
- Add lgtm suppression for the HMAC lookup hash (intentional fast
  lookup index, not password storage — key_hash provides the slow hash)
- Use _compute_lookup_hash() in tests instead of raw hmac calls

* fix: use BLAKE2b for lookup hash to resolve CodeQL alerts

Switch from HMAC-SHA256 to BLAKE2b with native keyed hashing for the
API key lookup hash. BLAKE2b is not flagged by CodeQL's
weak-sensitive-data-hashing rule (which targets SHA-256/SHA-512/MD5),
is fast by design, and supports keying natively without an HMAC wrapper.

The lookup hash is an internal optimization detail for O(1) key lookup,
not a password storage mechanism (key_hash provides the slow hash for
defense in depth).

* fix: use scrypt for lookup hash to satisfy CodeQL

CodeQL flags all fast hash algorithms (SHA-256, BLAKE2, HMAC) as
"insecure for password hashing" when used on sensitive data. Switch
to hashlib.scrypt with minimal work parameters (n=2, r=1, p=1) which
is nearly as fast as a plain hash but classified as a computationally
expensive algorithm by static analysis tools.

The lookup hash is an internal O(1) index — the actual password-strength
protection is provided by key_hash (via generate_password_hash).

* fix: address PR review - 401 vs 403, public method, OpenAPI spec, docs

- Rename _extract_api_key_from_request to extract_api_key_from_request (public)
- Return 401 for invalid API key, 403 for valid key without permission
- Register api_key security scheme in OpenAPI spec
- Add api_key alongside jwt in operation_helper security list
- Add API key documentation to security.rst and rest_api.rst
- Add test for valid key with no permission returning 403

* fix: use black 23.10 formatting for decorators.py to pass CI lint

* fix: import USERNAME_READONLY in test_api_key to fix lint

* fix: use no-permission role in 403 test instead of ReadOnly

* fix: clean up noperms_user in tearDown to prevent test pollution
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

api Related to the REST API authentication Related to authentication change:frontend Requires changing the frontend review:draft risk:db-migration PRs that require a DB migration size/XL

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants