Skip to main content

Django SAML2 Authentication For Multi IdP Setups Made Easy.

Project description

Django SAML2 Authentication Multi

PyPI

This plugin provides a simple way to integrate SAML2 Authentication into your Django-powered app. SAML SSO is a standard, so practically any SAML2 based SSO identity provider is supported.

This project is a fork of django-saml2-auth and adds support for configuring multiple IDPs with separate configuration parameters. It should be compatible with existing deployments and configurations; nevertheless, unless you need additional features in this package (also which may not compatible with your use case), it is highly suggested to use base package.

This plugin supports both identity provider and service provider-initiated SSO:

  • For IdP-initiated SSO, the user should sign in to their identity provider platform, e.g., Okta, and click on the application that authorizes and redirects the user to the service provider, that is your platform.
  • For SP-initiated SSO, the user should first exist on your platform, either by signing in via the first method (IdP-initiated SSO) or any other custom solution. It can be configured to be redirected to the correct application on the identity provider platform.

For IdP-initiated SSO, the user will be created if it doesn't exist. Still, for SP-initiated SSO, the user should exist in your platform for the code to detect and redirect them to the correct application on the identity provider platform.

Installation

You can install this plugin via pip. Make sure you update pip to be able to install from git:

pip install django-saml2-auth-multi

xmlsec is also required by pysaml2, so it must be installed:

// RPM-based distributions
# yum install xmlsec1
// DEB-based distributions
# apt-get install xmlsec1
// macOS
# brew install xmlsec1

Windows binaries are also available.

How to use?

  1. Once you have the library installed or in your requirements.txt, import the views module in your root urls.py:

    import django_saml2_auth_multi.views
    
  2. Override the default login page in the root urls.py file, by adding these lines BEFORE any urlpatterns:

    # These are the SAML2 related URLs. You can change "^saml2_auth/" regex to
    # any path you want, like "^sso/", "^sso_auth/", "^sso_login/", etc. (required)
    url(r'^sso/', include('django_saml2_auth_multi.urls')),
    
    # The following line will replace the default user login with SAML2 (optional)
    # If you want to specific the after-login-redirect-URL, use parameter "?next=/the/path/you/want"
    # with this view.
    url(r'^accounts/login/$', django_saml2_auth_multi.views.signin),
    
    # The following line will replace the admin login with SAML2 (optional)
    # If you want to specific the after-login-redirect-URL, use parameter "?next=/the/path/you/want"
    # with this view.
    url(r'^admin/login/$', django_saml2_auth_multi.views.signin),
    
  3. Add 'django_saml2_auth_multi' to INSTALLED_APPS in your django settings.py:

    INSTALLED_APPS = [
        '...',
        'django_saml2_auth_multi',
    ]
    
  4. In settings.py, add the SAML2 related configuration:

    SAML2_AUTH configuration variable can be a dictionary (for configuring single IDP) or a list (for configuring list of IDPs).

    Please note, the only required setting is METADATA_AUTO_CONF_URL or the existence of a GET_METADATA_AUTO_CONF_URLS trigger function. The following block shows all required and optional configuration settings and their default values.

    SAML2_AUTH = [
        {
            # Set this to entity ID of IdP. Used when selecting redirection URL in login view and determining issuer IdP in ACS endpoint.
            # Should be same with Issuer ID in assertions.
            # Required when a list is provided for SAML2_AUTH (i.e. multiple IDPs are configured). 
            'IDP_ID': '<Entity ID of IdP>',
    
            # Metadata is required, choose either remote url or local file path
            'METADATA_AUTO_CONF_URL': '[The auto(dynamic) metadata configuration URL of SAML2]',
            'METADATA_LOCAL_FILE_PATH': '[The metadata configuration file path]',
            'KEY_FILE': '[The key file path]',
            'CERT_FILE': '[The certificate file path]',
    
            # Optional settings below
            'DEFAULT_NEXT_URL': '/admin',  # Custom target redirect URL after the user get logged in. Default to /admin if not set. This setting will be overwritten if you have parameter ?next= specificed in the login URL.
            'CREATE_USER': True,  # Create a new Django user when a new user logs in. Defaults to True.
            'NEW_USER_PROFILE': {
                'USER_GROUPS': [],  # The default group name when a new user logs in
                'ACTIVE_STATUS': True,  # The default active status for new users
                'STAFF_STATUS': False,  # The staff status for new users
                'SUPERUSER_STATUS': False,  # The superuser status for new users
            },
            'ATTRIBUTES_MAP': {  # Change Email/UserName/FirstName/LastName to corresponding SAML2 userprofile attributes.
                'email': 'user.email',
                'username': 'user.username',
                'first_name': 'user.first_name',
                'last_name': 'user.last_name',
                'token': 'Token',  # Mandatory, can be unrequired if TOKEN_REQUIRED is False
                'groups': 'Groups',  # Optional
            },
            'GROUPS_MAP': {  # Optionally allow mapping SAML2 Groups to Django Groups
                'SAML Group Name': 'Django Group Name',
            },
            'TRIGGER': {
                # Optional: needs to return a User Model instance or None
                'GET_USER': 'path.to.your.get.user.hook.method',
                'CREATE_USER': 'path.to.your.new.user.hook.method',
                'BEFORE_LOGIN': 'path.to.your.login.hook.method',
                'AFTER_LOGIN': 'path.to.your.after.login.hook.method',
                # Optional. This is executed right before METADATA_AUTO_CONF_URL.
                # For systems with many metadata files registered allows to narrow the search scope.
                'GET_USER_ID_FROM_SAML_RESPONSE': 'path.to.your.get.user.from.saml.hook.method',
                # This can override the METADATA_AUTO_CONF_URL to enumerate all existing metadata autoconf URLs
                'GET_METADATA_AUTO_CONF_URLS': 'path.to.your.get.metadata.conf.hook.method',
            },
            'ASSERTION_URL': 'https://mysite.com',  # Custom URL to validate incoming SAML requests against
            'ENTITY_ID': 'https://mysite.com/saml2_auth/acs/',  # Populates the Issuer element in authn request
            'NAME_ID_FORMAT': FormatString,  # Sets the Format property of authn NameIDPolicy element, e.g. 'user.email'
            'USE_JWT': True,  # Set this to True if you are running a Single Page Application (SPA) with Django Rest Framework (DRF), and are using JWT authentication to authorize client users
            'JWT_ALGORITHM': 'HS256',  # JWT algorithm to sign the message with
            'JWT_SECRET': 'your.jwt.secret',  # JWT secret to sign the message with
            'JWT_PRIVATE_KEY': '--- YOUR PRIVATE KEY ---',  # Private key to sign the message with. The algorithm should be set to RSA256 or a more secure alternative.
            'JWT_PRIVATE_KEY_PASSPHRASE': 'your.passphrase',  # If your private key is encrypted, you might need to provide a passphrase for decryption
            'JWT_PUBLIC_KEY': '--- YOUR PUBLIC KEY ---',  # Public key to decode the signed JWT token
            'JWT_EXP': 60,  # JWT expiry time in seconds
            'FRONTEND_URL': 'https://myfrontendclient.com',  # Redirect URL for the client if you are using JWT auth with DRF. See explanation below
            'LOGIN_CASE_SENSITIVE': True,  # whether of not to get the user in case_sentive mode
            'AUTHN_REQUESTS_SIGNED': True, # Require each authentication request to be signed
            'LOGOUT_REQUESTS_SIGNED': True,  # Require each logout request to be signed
            'WANT_ASSERTIONS_SIGNED': True,  # Require each assertion to be signed
            'WANT_RESPONSE_SIGNED': True,  # Require response to be signed
            'ACCEPTED_TIME_DIFF': None,  # Accepted time difference between your server and the Identity Provider
            'ALLOWED_REDIRECT_HOSTS': ["https://myfrontendclient.com"], # Allowed hosts to redirect to using the ?next parameter
            'TOKEN_REQUIRED': True,  # Whether or not to require the token parameter in the SAML assertion
        }
    ]
    

    SAML2_AUTH_DEFAULTS configuration variable can be used for specifying common settings for all IdP configurations. Same list of settings can be used for SAML2_AUTH_DEFAULTS.

    SAML2_AUTH_DEBUG (default: False) configuration variable toggles debug flag for logging.

  5. In your SAML2 SSO identity provider, set the Single-sign-on URL and Audience URI (SP Entity ID) to http://your-domain/saml2_auth/acs/

How to debug?

To debug what's happening between the SAMLP Identity Provider and your Django application, you can use SAML-tracer for Firefox or Chrome. Using this tool, you can see the SAML requests and responses that are being sent back and forth.

Also, you can enable the debug mode in the settings.py file by setting the DEBUG flag to True and enabling the LOGGING configuration. See above for configuration examples.

Note: Don't forget to disable the debug mode in production and also remove the logging configuration if you don't want to see internal logs of pysaml2 library.

Module Settings

Some of the following settings are related to how this module operates. The rest are passed as options to the pysaml2 library. For more information on the pysaml2 library, see the pysaml2 documentation, which contains examples of available settings. Also, note that all settings are not implemented in this module.

Field name Description Data type(s) Default value(s) Example
IDP_ID Entity ID of IdP which is also used as Issuer information in assertions str None urn:ORG.eu.auth0.com
METADATA_AUTO_CONF_URL Auto SAML2 metadata configuration URL str None https://ORG.eu.auth0.com/samlp/metadata/
METADATA_LOCAL_FILE_PATH SAML2 metadata configuration file path str None /path/to/the/metadata.xml
KEY_FILE SAML2 private key file path str None /path/to/the/key.pem
CERT_FILE SAML2 public certificate file path str None /path/to/the/cert.pem
DEBUG Send debug information to a log file bool False
LOGGING Logging configuration dictionary dict Not set.
DEFAULT_NEXT_URL Custom target redirect URL after the user get logged in. Default to /admin if not set. This setting will be overwritten if you have parameter ?next= specificed in the login URL. str admin:index https://app.example.com/account/login
CREATE_USER Determines if a new Django user should be created for new users bool True
CREATE_GROUPS Determines if a new Django group should be created if the SAML2 Group does not exist bool False
NEW_USER_PROFILE Default settings for newly created users dict {'USER_GROUPS': [], 'ACTIVE_STATUS': True, 'STAFF_STATUS': False, 'SUPERUSER_STATUS': False}
ATTRIBUTES_MAP Mapping of Django user attributes to SAML2 user attributes dict {'email': 'user.email', 'username': 'user.username', 'first_name': 'user.first_name', 'last_name': 'user.last_name', 'token': 'token'} {'your.field': 'SAML.field'}
TOKEN_REQUIRED Set this to False if you don't require the token parameter in the SAML assertion (in the attributes map) bool True
TRIGGER Hooks to trigger additional actions during user login and creation flows. These TRIGGER hooks are strings containing a dotted module name which point to a method to be called. The referenced method should accept a single argument: a dictionary of attributes and values sent by the identity provider, representing the user's identity. Triggers will be executed only if they are set. dict {}
TRIGGER.GET_USER A method to be called upon getting an existing user. This method will be called before the new user is logged in and is used to customize the retrieval of an existing user record. This method should accept ONE parameter of user dict and return a User model instance or none. str None my_app.models.users.get
TRIGGER.CREATE_USER A method to be called upon new user creation. This method will be called before the new user is logged in and after the user's record is created. This method should accept ONE parameter of user dict. str None my_app.models.users.create
TRIGGER.BEFORE_LOGIN A method to be called when an existing user logs in. This method will be called before the user is logged in and after the SAML2 identity provider returns user attributes. This method should accept ONE parameter of user dict. str None my_app.models.users.before_login
TRIGGER.AFTER_LOGIN A method to be called when an existing user logs in. This method will be called after the user is logged in and after the SAML2 identity provider returns user attributes. This method should accept TWO parameters of session and user dict. str None my_app.models.users.after_login
TRIGGER.GET_METADATA_AUTO_CONF_URLS A hook function that returns a list of metadata Autoconf URLs. This can override the METADATA_AUTO_CONF_URL to enumerate all existing metadata autoconf URLs. str None my_app.models.users.get_metadata_autoconf_urls
TRIGGER.CUSTOM_DECODE_JWT A hook function to decode the user JWT. This method will be called instead of the decode_jwt_token default function and should return the user_model.USERNAME_FIELD. This method accepts one parameter: token. str None my_app.models.users.decode_custom_token
TRIGGER.CUSTOM_CREATE_JWT A hook function to create a custom JWT for the user. This method will be called instead of the create_jwt_token default function and should return the token. This method accepts one parameter: user. str None my_app.models.users.create_custom_token
TRIGGER.CUSTOM_TOKEN_QUERY A hook function to create a custom query params with the JWT for the user. This method will be called after CUSTOM_CREATE_JWT to populate a query and attach it to a URL; should return the query params containing the token (e.g., ?token=encoded.jwt.token). This method accepts one parameter: token. str None my_app.models.users.get_custom_token_query
ASSERTION_URL A URL to validate incoming SAML responses against. By default, django-saml2-auth will validate the SAML response's Service Provider address against the actual HTTP request's host and scheme. If this value is set, it will validate against ASSERTION_URL instead - perfect for when Django is running behind a reverse proxy. str https://example.com
ENTITY_ID The optional entity ID string to be passed in the 'Issuer' element of authentication request, if required by the IDP. str None https://exmaple.com/sso/acs
NAME_ID_FORMAT Set to the string 'None', to exclude sending the 'Format' property of the 'NameIDPolicy' element in authentication requests. str <urn:oasis:names:tc:SAML:2.0:nameid-format:transient>
USE_JWT Set this to the boolean True if you are using Django with JWT authentication bool False
JWT_ALGORITHM JWT algorithm (str) to sign the message with: supported algorithms. str HS512 or RS512
JWT_SECRET JWT secret to sign the message if an HMAC is used with the SHA hash algorithm (HS*). str None
JWT_PRIVATE_KEY Private key (str) to sign the message with. The algorithm should be set to RSA256 or a more secure alternative. str or bytes --- YOUR PRIVATE KEY ---
JWT_PRIVATE_KEY_PASSPHRASE If your private key is encrypted, you must provide a passphrase for decryption. str or bytes None
JWT_PUBLIC_KEY Public key to decode the signed JWT token. str or bytes '--- YOUR PUBLIC KEY ---'
JWT_EXP JWT expiry time in seconds int 60
FRONTEND_URL If USE_JWT is True, you should set the URL to where your frontend is located (will default to DEFAULT_NEXT_URL if you fail to do so). Once the client is authenticated through the SAML SSO, your client is redirected to the FRONTEND_URL with the JWT token as token query parameter. Example: https://app.example.com/?&token=<your.jwt.token. With the token, your SPA can now authenticate with your API. str admin:index
AUTHN_REQUESTS_SIGNED Set this to False if your provider doesn't sign each authorization request. bool True
LOGOUT_REQUESTS_SIGNED Set this to False if your provider doesn't sign each logout request. bool True
WANT_ASSERTIONS_SIGNED Set this to False if your provider doesn't sign each assertion. bool True
WANT_RESPONSE_SIGNED Set this to False if you don't want your provider to sign the response. bool True
ACCEPTED_TIME_DIFF Sets the accepted time diff in seconds int or None None
ALLOWED_REDIRECT_HOSTS Allowed hosts to redirect to using the ?next= parameter list [] ['https://app.example.com', 'https://api.exmaple.com']

Triggers

Setting name Description Interface
GET_METADATA_AUTO_CONF_URLS Auto SAML2 metadata configuration URL get_metadata_auto_conf_urls(user_id: Optional[str] = None) -> Optional[List[Dict[str, str]]]
GET_USER_ID_FROM_SAML_RESPONSE Allows retrieving a user ID before GET_METADATA_AUTO_CONF_URLS gets triggered. Warning: SAML response still not verified. Use with caution! get_user_id_from_saml_response(saml_response: str, user_id: Optional[str]) -> Optional[str]

JWT Signing Algorithm and Settings

Both symmetric and asymmetric signing functions are supported. If you want to use symmetric signing using a secret key, use either of the following algorithms plus a secret key:

  • HS256
  • HS384
  • HS512
{
    ...
    'USE_JWT': True,
    'JWT_ALGORITHM': 'HS256',
    'JWT_SECRET': 'YOU.ULTRA.SECURE.SECRET',
    ...
}

Otherwise if you want to use your PKI key-pair to sign JWT tokens, use either of the following algorithms and then set the following fields:

  • RS256
  • RS384
  • RS512
  • ES256
  • ES256K
  • ES384
  • ES521
  • ES512
  • PS256
  • PS384
  • PS512
  • EdDSA
{
    ...
    'USE_JWT': True,
    'JWT_ALGORITHM': 'RS256',
    'JWT_PRIVATE_KEY': '--- YOUR PRIVATE KEY ---',
    'JWT_PRIVATE_KEY_PASSPHRASE': 'your.passphrase',  # Optional, if your private key is encrypted
    'JWT_PUBLIC_KEY': '--- YOUR PUBLIC KEY ---',
    ...
}

Note: If both PKI fields and JWT_SECRET are defined, the JWT_ALGORITHM decides which method to use for signing tokens.

Custom token triggers

This is an example of the functions that could be passed to the TRIGGER.CUSTOM_CREATE_JWT (it uses the DRF Simple JWT library) and to TRIGGER.CUSTOM_TOKEN_QUERY:

from rest_framework_simplejwt.tokens import RefreshToken


def get_custom_jwt(user):
    """Create token for user and return it"""
    return RefreshToken.for_user(user)


def get_custom_token_query(refresh):
    """Create url query with refresh and access token"""
    return "?%s%s%s%s%s" % ("refresh=", str(refresh), "&", "access=", str(refresh.access_token))

Customize Error Messages

The default permission denied, error and user welcome page can be overridden.

To override these pages put a template named 'django_saml2_auth/error.html', 'django_saml2_auth/welcome.html' or 'django_saml2_auth/denied.html' in your project's template folder.

If a 'django_saml2_auth/welcome.html' template exists, that page will be shown to the user upon login instead of the user being redirected to the previous visited page. This welcome page can contain some first-visit notes and welcome words. The Django user object is available within the template as the user template variable.

To enable a logout page, add the following lines to urls.py, before any urlpatterns:

# The following line will replace the default user logout with the signout page (optional)
url(r'^accounts/logout/$', django_saml2_auth_multi.views.signout),

# The following line will replace the default admin user logout with the signout page (optional)
url(r'^admin/logout/$', django_saml2_auth_multi.views.signout),

To override the built in signout page put a template named 'django_saml2_auth/signout.html' in your project's template folder.

If your SAML2 identity provider uses user attribute names other than the defaults listed in the settings.py ATTRIBUTES_MAP, update them in settings.py.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

django_saml2_auth_multi-3.11.1rc7.tar.gz (42.7 kB view details)

Uploaded Source

Built Distribution

django_saml2_auth_multi-3.11.1rc7-py2.py3-none-any.whl (43.6 kB view details)

Uploaded Python 2 Python 3

File details

Details for the file django_saml2_auth_multi-3.11.1rc7.tar.gz.

File metadata

File hashes

Hashes for django_saml2_auth_multi-3.11.1rc7.tar.gz
Algorithm Hash digest
SHA256 f2c9d5591a93109f6a72830664c7e38c57dfda05a4e2fd88228fbf638d24a1f9
MD5 c9799a9f8f1b9c3e0c05b49ec6d7ed26
BLAKE2b-256 a039c65d23dc13953b44fafa7c73be1b4aa695c51eaac6caee82d8b2c7599cc1

See more details on using hashes here.

File details

Details for the file django_saml2_auth_multi-3.11.1rc7-py2.py3-none-any.whl.

File metadata

File hashes

Hashes for django_saml2_auth_multi-3.11.1rc7-py2.py3-none-any.whl
Algorithm Hash digest
SHA256 2fcd7a379848c628b31da96dedfb831ddb7917e15c0460cc14aa27a9abd540ec
MD5 1ac35c587c90f56a40b3eba59e472e7d
BLAKE2b-256 5284df7f002a397c7b62b76ed696296fa29fdbf78e668026539746274c3ca3a3

See more details on using hashes here.

Supported by

AWS AWS Cloud computing and Security Sponsor Datadog Datadog Monitoring Fastly Fastly CDN Google Google Download Analytics Microsoft Microsoft PSF Sponsor Pingdom Pingdom Monitoring Sentry Sentry Error logging StatusPage StatusPage Status page