Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
144 changes: 142 additions & 2 deletions docs/security.rst
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ Supported Authentication Types
It's the web server responsibility to authenticate the user, useful for intranet sites, when the server (Apache, Nginx)
is configured to use kerberos, no need for the user to login with username and password on F.A.B.
:OAUTH: Authentication using OAUTH (v1 or v2). You need to install authlib.
:SAML: Authentication using SAML 2.0 (e.g., Microsoft Entra ID, Okta, OneLogin). You need to install python3-saml.

.. note::
**Deprecated Authentication Types (Removed in Flask-AppBuilder 5.0+)**
Expand All @@ -31,15 +32,16 @@ The session is preserved and encrypted using Flask-Login.
Authentication Methods
----------------------

You can choose one from 4 authentication methods. Configure the method to be used
You can choose one from 5 authentication methods. Configure the method to be used
on the **config.py** (when using the create-app, or following the proposed app structure). First the
configuration imports the constants for the authentication methods::

from flask_appbuilder.security.manager import (
AUTH_DB,
AUTH_LDAP,
AUTH_OAUTH,
AUTH_REMOTE_USER
AUTH_REMOTE_USER,
AUTH_SAML,
)

Next you will use the **AUTH_TYPE** key to choose the type::
Expand Down Expand Up @@ -437,6 +439,138 @@ Therefore, you can send tweets, post on the users Facebook, retrieve the user's
Take a look at the `example <https://github.com/dpgaspar/Flask-AppBuilder/tree/master/examples/oauth>`_
to get an idea of a simple use for this.

Authentication: SAML
--------------------

This method will authenticate users via SAML 2.0 identity providers such as
Microsoft Entra ID (formerly Azure AD), Okta, OneLogin, etc.

.. note:: To use SAML you need to install `python3-saml <https://github.com/SAML-Toolkits/python3-saml>`_:
``pip install flask-appbuilder[saml]``

Configure your SAML providers and SP settings in **config.py**::

AUTH_TYPE = AUTH_SAML

# registration configs
AUTH_USER_REGISTRATION = True
AUTH_USER_REGISTRATION_ROLE = "Public"

# Sync roles at login from SAML assertion
AUTH_ROLES_SYNC_AT_LOGIN = True

# Map SAML group names to FAB roles
AUTH_ROLES_MAPPING = {
"admins": ["Admin"],
"users": ["Public"],
}

# SAML Identity Providers
SAML_PROVIDERS = [
{
"name": "entra_id",
"icon": "fa-microsoft",
"idp": {
"entityId": "https://sts.windows.net/<TENANT_ID>/",
"singleSignOnService": {
"url": "https://login.microsoftonline.com/<TENANT_ID>/saml2",
"binding": "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect",
},
"singleLogoutService": {
"url": "https://login.microsoftonline.com/<TENANT_ID>/saml2",
"binding": "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect",
},
"x509cert": "<IDP_CERTIFICATE_BASE64>",
},
"attribute_mapping": {
"http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress": "email",
"http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname": "first_name",
"http://schemas.xmlsoap.org/ws/2005/05/identity/claims/surname": "last_name",
"http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name": "username",
"http://schemas.microsoft.com/ws/2008/06/identity/claims/groups": "role_keys",
},
},
]

# Global SAML Service Provider configuration
SAML_CONFIG = {
"strict": True,
"debug": False,
"sp": {
"entityId": "https://myapp.example.com/saml/metadata/",
"assertionConsumerService": {
"url": "https://myapp.example.com/saml/acs/",
"binding": "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST",
},
"singleLogoutService": {
"url": "https://myapp.example.com/saml/slo/",
"binding": "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect",
},
"NameIDFormat": "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress",
"x509cert": "",
# "privateKey": "",
},
"security": {
"nameIdEncrypted": False,
"authnRequestsSigned": False,
"logoutRequestSigned": False,
"logoutResponseSigned": False,
"signMetadata": False,
"wantMessagesSigned": False,
"wantAssertionsSigned": True,
"wantAssertionsEncrypted": False,
"wantNameId": True,
"wantNameIdEncrypted": False,
"wantAttributeStatement": True,
},
}

Each SAML provider entry has the following keys:

:name: A unique name for the identity provider.
:icon: A Font Awesome icon class for the login button.
:idp: The IdP SAML metadata (entityId, SSO/SLO URLs, and signing certificate).
:attribute_mapping: Maps SAML assertion attribute names (left) to FAB user fields (right).
Supported FAB fields: ``username``, ``email``, ``first_name``, ``last_name``, ``role_keys``.

The ``SAML_CONFIG`` dict holds the global Service Provider settings. The ``sp`` section defines
your application's SAML endpoints. These URLs must match what you configure on the IdP side.

SAML Endpoints
~~~~~~~~~~~~~~

The following endpoints are automatically registered:

- ``/login/`` — Login page with IdP selection (or auto-redirect for single IdP)
- ``/login/<idp>`` — Initiate SSO with a specific IdP
- ``/saml/acs/`` — Assertion Consumer Service (receives SAML responses)
- ``/saml/slo/`` — Single Logout endpoint
- ``/saml/metadata/`` — SP metadata XML (configure this URL on your IdP)

SAML Role Mapping
~~~~~~~~~~~~~~~~~

You can map SAML group claims to FAB roles, just like with OAuth and LDAP::

AUTH_ROLES_MAPPING = {
"admins": ["Admin"],
"users": ["User"],
}

AUTH_ROLES_SYNC_AT_LOGIN = True

PERMANENT_SESSION_LIFETIME = 1800

The ``role_keys`` field in ``attribute_mapping`` defines which SAML attribute contains the
user's group memberships.

You can also use JMESPath expressions for dynamic role assignment::

AUTH_USER_REGISTRATION_ROLE_JMESPATH = "role_keys[0]"

Take a look at the `SAML example <https://github.com/dpgaspar/Flask-AppBuilder/tree/master/examples/saml>`_


Authentication: Rate limiting
-----------------------------

Expand Down Expand Up @@ -848,6 +982,9 @@ F.A.B. uses a different user view for each authentication method

:UserDBModelView: For database auth method
:UserLDAPModelView: For LDAP auth method
:UserOAuthModelView: For OAuth auth method
:UserRemoteUserModelView: For Remote User auth method
:UserSAMLModelView: For SAML auth method

You can extend or create from scratch your own, and then tell F.A.B. to use them instead, by overriding their
correspondent lower case properties on **SecurityManager** (just like on the given example).
Expand Down Expand Up @@ -881,6 +1018,7 @@ If you're using:
:AUTH_LDAP: Extend UserLDAPModelView
:AUTH_REMOTE_USER: Extend UserRemoteUserModelView
:AUTH_OAUTH: Extend UserOAuthModelView
:AUTH_SAML: Extend UserSAMLModelView

So using AUTH_DB::

Expand Down Expand Up @@ -955,6 +1093,8 @@ Note that this is for AUTH_DB, so if you're using:
:AUTH_DB: Override userdbmodelview
:AUTH_LDAP: Override userldapmodelview
:AUTH_REMOTE_USER: Override userremoteusermodelview
:AUTH_OAUTH: Override useroauthmodelview
:AUTH_SAML: Override usersamlmodelview

Finally (as shown on the previous example) tell F.A.B. to use your SecurityManager class, so when initializing
**AppBuilder** (on __init__.py)::
Expand Down
8 changes: 8 additions & 0 deletions examples/saml/app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
"""Example Flask-AppBuilder application with SAML / Entra ID authentication."""

from app import create_app

app = create_app()

if __name__ == "__main__":
app.run(host="0.0.0.0", port=5000, debug=True)
19 changes: 19 additions & 0 deletions examples/saml/app/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import logging

from flask import Flask

from .extensions import appbuilder, db


logging.basicConfig(format="%(asctime)s:%(levelname)s:%(name)s:%(message)s")
logging.getLogger().setLevel(logging.INFO)


def create_app() -> Flask:
app = Flask(__name__)
app.config.from_object("config")
with app.app_context():
db.init_app(app)
appbuilder.init_app(app, db.session)
db.create_all()
return app
5 changes: 5 additions & 0 deletions examples/saml/app/extensions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
from flask_appbuilder import AppBuilder
from flask_appbuilder.utils.legacy import get_sqla_class

db = get_sqla_class()()
appbuilder = AppBuilder()
116 changes: 116 additions & 0 deletions examples/saml/config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
"""Example configuration for SAML authentication with Flask-AppBuilder.

Demonstrates configuring FAB with SAML / Microsoft Entra ID authentication.
Adjust the IdP settings to match your identity provider.

Required environment variables:
SAML_TENANT_ID - Microsoft Entra ID tenant ID
SAML_IDP_CERT - IdP signing certificate (base64 content, no PEM headers)
"""

import os

from flask_appbuilder.const import AUTH_SAML

basedir = os.path.abspath(os.path.dirname(__file__))

# Entra ID tenant ID and IdP certificate from environment
SAML_TENANT_ID = os.environ.get("SAML_TENANT_ID", "<YOUR_TENANT_ID>")
SAML_IDP_CERT = os.environ.get("SAML_IDP_CERT", "<YOUR_IDP_CERTIFICATE>")

# Flask secret key
SECRET_KEY = "\2\1thisismyscretkey\1\2\e\y\y\h"

# Database connection
SQLALCHEMY_DATABASE_URI = "sqlite:///" + os.path.join(basedir, "app.db")

# Flask-AppBuilder Security
AUTH_TYPE = AUTH_SAML

# Set to True to allow user self registration via SAML
AUTH_USER_REGISTRATION = True

# Default role for self-registered users
AUTH_USER_REGISTRATION_ROLE = "Admin"

# Sync roles at login (maps SAML groups/roles to FAB roles)
AUTH_ROLES_SYNC_AT_LOGIN = True

# Role mapping from SAML group names to FAB role names
AUTH_ROLES_MAPPING = {
"admins": ["Admin"],
"users": ["Public"],
}

# -------------------------------------------------------
# SAML Configuration
# -------------------------------------------------------

# List of SAML Identity Providers
# Replace <TENANT_ID> with your Microsoft Entra ID (formerly Azure AD) tenant ID.
# You can find these values in the Azure Portal under:
# Entra ID > Enterprise Applications > Your App > Single sign-on
SAML_PROVIDERS = [
{
"name": "entra_id",
"icon": "fa-microsoft",
# IdP configuration from Entra ID SAML metadata:
# https://login.microsoftonline.com/<TENANT_ID>/federationmetadata/2007-06/federationmetadata.xml
"idp": {
"entityId": f"https://sts.windows.net/{SAML_TENANT_ID}/",
"singleSignOnService": {
"url": f"https://login.microsoftonline.com/{SAML_TENANT_ID}/saml2",
"binding": "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect",
},
"singleLogoutService": {
"url": f"https://login.microsoftonline.com/{SAML_TENANT_ID}/saml2",
"binding": "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect",
},
"x509cert": SAML_IDP_CERT,
},
# Map SAML assertion attributes to FAB user fields.
# Left side: SAML attribute name (Entra ID claim URIs).
# Right side: FAB user field name.
"attribute_mapping": {
"http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress": "email",
"http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname": "first_name", # noqa: E501
"http://schemas.xmlsoap.org/ws/2005/05/identity/claims/surname": "last_name",
"http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name": "username",
"http://schemas.microsoft.com/ws/2008/06/identity/claims/groups": "role_keys",
},
},
]

# Global SAML Service Provider configuration
SAML_CONFIG = {
"strict": False,
"debug": True,
"sp": {
"entityId": "http://localhost:9000/saml/metadata/",
"assertionConsumerService": {
"url": "http://localhost:9000/saml/acs/",
"binding": "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST",
},
"singleLogoutService": {
"url": "http://localhost:9000/saml/slo/",
"binding": "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect",
},
"NameIDFormat": "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress",
# SP certificate (optional, needed for signed requests)
"x509cert": "",
# "privateKey": "",
},
"security": {
"nameIdEncrypted": False,
"authnRequestsSigned": False,
"logoutRequestSigned": False,
"logoutResponseSigned": False,
"signMetadata": False,
"wantMessagesSigned": False,
"wantAssertionsSigned": True,
"wantAssertionsEncrypted": False,
"wantNameId": True,
"wantNameIdEncrypted": False,
"wantAttributeStatement": True,
},
}
1 change: 1 addition & 0 deletions flask_appbuilder/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,7 @@
AUTH_LDAP = 2
AUTH_REMOTE_USER = 3
AUTH_OAUTH = 4
AUTH_SAML = 5
""" Constants for supported authentication types """

# -----------------------------------
Expand Down
Loading
Loading