Giter VIP home page Giter VIP logo

django-saml2-auth's Introduction

Django SAML2 Authentication

PyPI GitHub Workflow Status Coveralls Downloads

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 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.

Project Information

  • Original Author: Fang Li (@fangli)

  • Maintainer: Mostafa Moradian (@mostafa)

  • Version support matrix:

    Python Django django-saml2-auth End of extended support
    (Django)
    3.10.x 3.2.x >=3.4.0 April 2024
    3.10.x, 3.11.x, 3.12.x 4.2.x >=3.4.0 April 2026
    3.10.x, 3.11.x, 3.12.x 5.0.x >3.12.0 April 2026
  • Release logs are available here.

  • For contribution, read contributing guide.

CycloneDX SBOM

From v3.6.1, CycloneDX SBOMs will be generated for requirements.txt and requirements_test.txt and it can be accessed from the latest build of GitHub Actions for a tagged release, for example, this one. The artifacts are only kept for 90 days.

Donate

Please give us a shiny star and help spread the word.

Installation

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

pip install grafana-django-saml2-auth

or from source:

git clone https://github.com/grafana/django-saml2-auth
cd django-saml2-auth
python setup.py install

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.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. (required)
    re_path(r'^sso/', include('django_saml2_auth.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.
    re_path(r'^accounts/login/$', django_saml2_auth.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.
    re_path(r'^admin/login/$', django_saml2_auth.views.signin),
  3. Add 'django_saml2_auth' to INSTALLED_APPS in your django settings.py:

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

    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 = {
        # 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]',
        
        # If both `KEY_FILE` and `CERT_FILE` are provided, `ENCRYPTION_KEYPAIRS` will be added automatically. There is no need to provide it unless you wish to override the default value.
        'ENCRYPTION_KEYPAIRS': [
            {
                "key_file": '[The key file path]',
                "cert_file": '[The certificate file path]',
            }
        ],
    
        'DEBUG': False,  # Send debug information to a log file
        # Optional logging configuration.
        # By default, it won't log anything.
        # The following configuration is an example of how to configure the logger,
        # which can be used together with the DEBUG option above. Please note that
        # the logger configuration follows the Python's logging configuration schema:
        # https://docs.python.org/3/library/logging.config.html#logging-config-dictschema
        'LOGGING': {
            'version': 1,
            'formatters': {
                'simple': {
                    'format': '[%(asctime)s] [%(levelname)s] [%(name)s.%(funcName)s] %(message)s',
                },
            },
            'handlers': {
                'stdout': {
                    'class': 'logging.StreamHandler',
                    'stream': 'ext://sys.stdout',
                    'level': 'DEBUG',
                    'formatter': 'simple',
                },
            },
            'loggers': {
                'saml2': {
                    'level': 'DEBUG'
                },
            },
            'root': {
                'level': 'DEBUG',
                'handlers': [
                    'stdout',
                ],
            },
        },
    
        # 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/sso/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
    }
  5. In your SAML2 SSO identity provider, set the Single-sign-on URL and Audience URI (SP Entity ID) to http://your-domain/sso/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
METADATA_AUTO_CONF_URL Auto SAML2 metadata configuration URL str None https://ORG.okta.com/app/APP-ID/sso/saml/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
ENCRYPTION_KEYPAIRS Required for handling encrypted assertions. Will be automatically set if both KEY_FILE and CERT_FILE are set. list Not set. [ { 'key_file': '[The key file path]', 'cert_file': '[The certificate file path]' } ]
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.views.signout),

# The following line will replace the default admin user logout with the signout page (optional)
url(r'^admin/logout/$', django_saml2_auth.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.

For Okta Users

I created this plugin originally for Okta. The METADATA_AUTO_CONF_URL needed in settings.py can be found in the Okta Web UI by navigating to the SAML2 app's Sign On tab. In the Settings box, you should see:

Identity Provider metadata is available if this application supports dynamic configuration.

The Identity Provider metadata link is the METADATA_AUTO_CONF_URL.

More information can be found in the Okta Developer Documentation.

Release Process

I adopted a reasonably simple release process, which is almost automated, except for two actions that needed to be taken to start a release:

  1. Update setup.py and increase the version number in the setup function. Unless something backward-incompatible is introduced, only the minor version is upgraded: 3.8.0 becomes 3.9.0.
  2. Tag the main branch with the the vSEMVER, e.g. v3.9.0, and git-push the tag.
  3. The release and publish to PyPI is handled in the CI/CD using GitHub Actions.
  4. Create a new release with auto-generated (and polished) release notes on the tag.
  5. Download SBOM artifacts generated by GitHub Actions for the corresponding run, and add them to the release files.

django-saml2-auth's People

Contributors

alxbridge avatar anthonyeden avatar ayr-ton avatar dependabot[bot] avatar dspeichert avatar fangli avatar gene1wood avatar gnuman avatar gregorywong avatar henxing avatar jacobh avatar jberkz avatar jean-sh avatar jheld avatar kevpo avatar kronion avatar mahaffey avatar mostafa avatar mvbattista avatar oussjarrousse avatar paoloromolini avatar qwrrty avatar rrauenza avatar sahir avatar santigandolfo avatar sgabb avatar snyk-bot avatar syre avatar tonylechner-mitel avatar tonymke avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

django-saml2-auth's Issues

Make package available on pypi

I was wondering if there was a reason this package hasn't been made available on pypi. This is definitely the most convenient way to be able to download and install python packages. I would also imagine this would increase the discoverability of the package itself too.

Sorry, you are not allowed to access this app To report a problem with your access please contact your system administrator

Hi, I'm facing this error when login http://localhost/accounts/login/

This is my uls.py
from django.contrib import admin
from django.urls import path, include
import django_saml2_auth.views

urlpatterns = [
path('admin/', admin.site.urls),
path(r'saml2_auth/', include('django_saml2_auth.urls')),
path(r'accounts/login/', django_saml2_auth.views.signin),
path(r'admin/login/', django_saml2_auth.views.signin),
]

I have added keycloak url in my settings.py

'METADATA_AUTO_CONF_URL': 'https://localhost:8443/auth/realms/NOC/protocol/saml/descriptor',
Please help to fix this

There was no response from SAML client.

Sorry, you are not allowed to access this app
To report a problem with your access please contact your system administrator
Error code: 1108
Reason: There was an error processing your request. There was no response from SAML client.

My SAML_AUTH configuration as below:
SAML2_AUTH = {
# Metadata is required, choose either remote url or local file path
'METADATA_AUTO_CONF_URL': 'https://dev-60303895.okta.com/app/exkdgtxgklmzYzqKq5d7/sso/saml/metadata',

# 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
},
'TRIGGER': {
    'CREATE_USER': 'path.to.your.new.user.hook.method',
    'BEFORE_LOGIN': 'path.to.your.login.hook.method',
},
'ASSERTION_URL': 'http://localhost', # Custom URL to validate incoming SAML requests against
'ENTITY_ID': 'http://localhost/saml2_auth/acs/', # Populates the Issuer element in authn request
'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
'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

}

Extract metadata file/URL with django-saml2-auth

I have a Python-Django project and I'm trying to integrate with a C#/.NET existing website SAML authentication, using their idp.
I absolutely need to generate a metadata file to give to whoever manages the IdP (C#/.NET website) to implement SSO.
How can I implement it?

Create group if not in DB

Currently if a group from the user_identity and a Group.DoesNotExist is thrown, then the group is skipped.

        groups = []

        for group_name in user["user_identity"][group_attribute]:
            # Group names can optionally be mapped to different names in Django
            if group_map and group_name in group_map:
                group_name_django = group_map[group_name]
            else:
                group_name_django = group_name

            try:
                groups.append(Group.objects.get(name=group_name_django))
            except Group.DoesNotExist:
                pass

It would be nice to have a setting 'CREATE_GROUPS': True, (similar to CREATE_USER) that instead would let us create the group and add it to the list:

        groups = []

        for group_name in user["user_identity"][group_attribute]:
            # Group names can optionally be mapped to different names in Django
            if group_map and group_name in group_map:
                group_name_django = group_map[group_name]
            else:
                group_name_django = group_name

            try:
                groups.append(Group.objects.get(name=group_name_django))
            except Group.DoesNotExist:
                should_create_new_groups = dictor(saml2_auth_settings, "CREATE_GROUPS", False)
                if should_create_new_groups:
                    groups.append(Group.objects.create(name=group_name_django))

Problem with Okta saml and Docker

I started using this library in local without any problem, everything work perfectly fine, but in my production env I use docker and ECS (AWS). And all my problem started here.. So I decided to used Docker in my local env and i get the same problem, did everyone get this bug ?

The only log i get from this: Internal Server Error: /saml2_auth/acs/

Here my SAML2 Config, which worked perfectly without docker

SAML2_AUTH = {
    # Metadata is required, choose either remote url or local file path
    "METADATA_AUTO_CONF_URL": "https://dev-123123123.okta.com/app/<app-id>/sso/saml/metadata",
    "DEBUG": True,
    # "METADATA_LOCAL_FILE_PATH": os.path.join(BASE_DIR + '/settings/', 'metadata.xml'),
    # Optional settings below
    "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.
    "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": "Email",
        "username": "UserName",
        "first_name": "FirstName",
        "last_name": "LastName",
    },
    "TOKEN_REQUIRED": False,
    # "TRIGGER": {
    #     # "CREATE_USER": "path.to.your.new.user.hook.method",
    #     "BEFORE_LOGIN": "path.to.your.login.hook.method",
    # },
    "ASSERTION_URL": "http://localhost:8000/saml2_auth/acs/",  # Custom URL to validate incoming SAML requests against
    "ENTITY_ID": "http://localhost:8000",  # Populates the Issuer element in authn request
    "NAME_ID_FORMAT": "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress",  # # Sets the Format property of authn
    # NameIDPolicy element
    "USE_JWT": False,  # Set this to True if you are running a Single Page Application (SPA) with Django Rest
}

Hope someone already get this problem, thanks in advance !

No username or email provided.

Hi, Im tryng to create a POC to try to use SAML to authenticate users in a Django project, but I'm blocked after receiving a SAML Response.

This is my current SAML2_AUTH:

SAML2_AUTH = {
    'DEBUG': True,
    'METADATA_AUTO_CONF_URL': 'https://redacted.com/Gsuite/GoogleIDPMetadata.xml',
    'ASSERTION_URL': 'https://127.0.0.1:8000',
    'ENTITY_ID': 'https://127.0.0.1:8000/saml2_auth/acs/',
    'DEFAULT_NEXT_URL': '/',
    'USE_JWT': True,
    'FRONTEND_URL': 'http://127.0.0.1:5173/',
    'AUTHN_REQUESTS_SIGNED': False,  # Require each authentication request to be signed
    'LOGOUT_REQUESTS_SIGNED': False,  # Require each logout request to be signed
    'WANT_ASSERTIONS_SIGNED': False,  # Require each assertion to be signed
    'WANT_RESPONSE_SIGNED': False,  # Require response to be signed
    'TOKEN_REQUIRED': False,
    'NAME_ID_FORMAT': '<urn:oasis:names:tc:SAML:2.0:nameid-format:emailAddress>'
}

And this is the response I'm getting:

<?xml 
version="1.0" 
encoding="UTF-8" 
standalone="no"?>
<saml2p:Response 
    xmlns:saml2p="urn:oasis:names:tc:SAML:2.0:protocol" 
    Destination="https://127.0.0.1:8000/saml2_auth/acs/" 
    ID="_2392de0148b141c290591d8e9e279eeb" 
    InResponseTo="id-6E7o8vsnlmwqeuJtd" 
    IssueInstant="2023-07-03T18:40:35.226Z" 
    Version="2.0">
    <saml2:Issuer 
        xmlns:saml2="urn:oasis:names:tc:SAML:2.0:assertion">https://accounts.google.com/o/saml2?
        idpid=C00xoo4uv
    </saml2:Issuer>
    <saml2p:Status>
        <saml2p:StatusCode 
            Value="urn:oasis:names:tc:SAML:2.0:status:Success"/>
    </saml2p:Status>
    <saml2:Assertion 
        xmlns:saml2="urn:oasis:names:tc:SAML:2.0:assertion" 
        ID="_509f9b945285e09236ad9555ab4d8fd1" 
        IssueInstant="2023-07-03T18:40:35.226Z" 
        Version="2.0">
        <saml2:Issuer>https://accounts.google.com/o/saml2?
            idpid=C00xoo4uv
        </saml2:Issuer>
        <ds:Signature 
            xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
            <ds:SignedInfo>
                <ds:CanonicalizationMethod 
                    Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
                <ds:SignatureMethod 
                    Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
                <ds:Reference 
                    URI="#_509f9b945285e09236ad9555ab4d8fd1">
                    <ds:Transforms>
                        <ds:Transform 
                            Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/>
                        <ds:Transform 
                            Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
                    </ds:Transforms>
                    <ds:DigestMethod 
                        Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
                    <ds:DigestValue>ncYSkItwHYfojUZrTOhKovFq9XWdyeELoLexeQkE6/
                        Y=
                    </ds:DigestValue>
                </ds:Reference>
            </ds:SignedInfo>
            <ds:SignatureValue>dDPiJ+fMd/cZVQ47MBcKwvmxfpvpCtXg4r/bkk796HJ129aQKZ+
jMJlDlC/h4isX3jK1KSaqFusSiTG16z4aX9aiuRjsnP2Uw1sseoxljLwuA59NpXI7LZStnSdvWt1
2Xk9+YFQRi/
                mk0aA0CuwAbqBWHv8kMbg3jEzlQ==
            </ds:SignatureValue>
            <ds:KeyInfo>
                <ds:X509Data>
                    <ds:X509SubjectName>
                        ST=California,
                        C=US,
                        OU=Google For Work,
                        CN=Google,
                        L=Mountain View,
                        O=Google Inc.
                    </ds:X509SubjectName>
                    <ds:X509Certificate>MIIDdDCCAlygAwIBAgIGAWqx7+csMA0GCSqGSIb3DQEBCwUAMHsxFDASBgNVBAoTC0dvb2dsZSBJ
bmMuMRYwFAYDVQQHEBIoEaOuX5YZVj0T8l8sSwlmDfY3NMKwK5PtpimHlklVAoy249d
tJE9gy6snxCvrRcJ7IQS0VhBM7X3aC6ZCAxibs7I9XRVUcZsbgqGpzR/NTSwTVCxLrBWjHvTH5
7ZBte+rQdbnJWBiZLaV+fVphyjOo7GPuPwmsMGzvNoYs</ds:X509Certificate>
                </ds:X509Data>
            </ds:KeyInfo>
        </ds:Signature>
        <saml2:Subject>
            <saml2:NameID 
                Format="urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified">[email protected]
            </saml2:NameID>
            <saml2:SubjectConfirmation 
                Method="urn:oasis:names:tc:SAML:2.0:cm:bearer">
                <saml2:SubjectConfirmationData 
                    InResponseTo="id-6E7o8vsnlmwqeuJtd" 
                    NotOnOrAfter="2023-07-03T18:45:35.226Z" 
                    Recipient="https://127.0.0.1:8000/saml2_auth/acs/"/>
            </saml2:SubjectConfirmation>
        </saml2:Subject>
        <saml2:Conditions 
            NotBefore="2023-07-03T18:35:35.226Z" 
            NotOnOrAfter="2023-07-03T18:45:35.226Z">
            <saml2:AudienceRestriction>
                <saml2:Audience>https://127.0.0.1:8000/saml2_auth/acs/</saml2:Audience>
            </saml2:AudienceRestriction>
        </saml2:Conditions>
        <saml2:AttributeStatement>
            <saml2:Attribute 
                Name="Groups">
                <saml2:AttributeValue 
                    xmlns:xs="http://www.w3.org/2001/XMLSchema" 
                    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
                    xsi:type="xs:anyType">app_stats_access_full_test
                </saml2:AttributeValue>
            </saml2:Attribute>
        </saml2:AttributeStatement>
        <saml2:AuthnStatement 
            AuthnInstant="2023-07-03T14:02:48.000Z" 
            SessionIndex="_509f9b945285e09236ad9555ab4d8fd1">
            <saml2:AuthnContext>
                <saml2:AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:unspecified</saml2:AuthnContextClassRef>
            </saml2:AuthnContext>
        </saml2:AuthnStatement>
    </saml2:Assertion>
</saml2p:Response>

My problem is that when I get redirected back to my app, the screen says:

Sorry, you are not allowed to access this app
To report a problem with your access please contact your system administrator

Error code: 1114

Reason: Username or email must be configured on the SAML app before logging in.

And the Django logs say:

 No username or email provided.

What am I doing wrong? I'm following this article (https://medium.com/cogito-engineering/enabling-sso-for-your-react-and-django-app-with-saml-2-0-754ef752acc1). The only difference in my case is that I'm using Google instead of Okta.

Why is the token attribute mandatory?

I'm noticing the token attribute in ATTRIBUTES_MAP is mandatory and an error is raised if it's not specified, why is that? What is it used for? I can't find it in django-saml2-auth, pysaml2 or Django...

Creating user with custom function

Hi, thanks a lot for this library! I was wondering if there is any way of creating a user with my own function. I've seen that the function create_new_user creates the User with the code:

user = user_model.objects.create_user(email, first_name=firstname, last_name=lastname)

But my User model has attributes name and surname instead of first_name and last_name.

It'd be interesting to be able to provide our own function to create it. Is there a way rn? Thanks a lot!

login page redirect url passed as RelayState instead of "login_next_url"

Thank you so much for maintaining this project.
I'm not sure about if its the similar issue (#145)

I am using MS-AAD, for admin urls such as https://dns/admin/django_celery_beat/periodictask/, I enforce login first.
After login, next url is always assigned to default url as code over here.

next_url = request.session.get("login_next_url") or get_default_next_url()

After login, its POST request to acs where new session gets created so request.session.get("login_next_url") is always None.
However I observed that in such cases next_url is passed as relay_state.

So to keep existing behaviour and fix the problem, assigning next_url to relay_state if it's None

Please find PR, #164

Inefficient metadata URL validation

When validating a metadata url here it's done by performing a GET request against the URL. This adds quite a bit of delay in the authentication process since if I have several metadata urls in my GET_METADATA_AUTO_CONF_URLS each of them will first be fetched by django-saml2-auth and then actually fetched by saml2.

Multiple IDP's?

Can this library work with multiple identity providers? If so, can it be more dynamic than hard coding the settings.py file which requires a restart of the django site to take effect? I'm trying to find something that would allow multiple organizations optionally use their IDP with my site by putting their required configs in the web ui.

By the way, thanks for the work on this fork. Very glad to see that there's an updated version supporting modern versions of django.

Unhelpful Error Output

I'm trying to replace the fangli library with this one, but cannot see anything but a generic "Sorry, you are not allowed to access this app" error - no error code is specified. I'm using Duo as my SAML IdP.

I cannot get a log file to generate, despite enabling the DEBUG setting.

A stack trace shows this line as the last path before handling the exception.

How can I get more info to continue troubleshooting this problem?

How to modify acs view to allow configuration per company (multi-tenant)?

I would need to add path("acs/<int:customer_id>/", views.acs, name="acs") and then in acs view get configuration based on customer (company) ID.

@csrf_exempt
@exception_handler
def acs(request: HttpRequest, customer_id: int):
    company = Company.objects.get(pk=customer_id)
    saml2_auth_settings = {
        'METADATA_AUTO_CONF_URL': company.sso_metadata_url,
        'ASSERTION_URL': company.sso_assertion_url,
        'ENTITY_ID': company.sso_entity_id
        ...
    }

but in other methods, for example, in saml.py file, configuration is get from settings.SAML2_AUTH and I do not know how to adjust it. Any recommendations?

[Question] Manage JWT with djangorestframework_simplejwt

Hello,
I was wondering if the JWT used in the library is only the one created specifying the JWT properties in settings.SAML2_AUTH or there would be the possibility to use another manager for the JWT?

For instance, I am using DRF Simple JWT library linked to Django Rest Framework.

Whether this would be a new feature, how would you suggest to plug it in?

Thanks in advance.

Multiple IDP's config

There is no proper documentation that says multiple IDP's are supported. For ex: if we want to support google as IDP and amazon as another IDP, how do we configure both the metadata xml in django with this library?

Certificate and Key file

Hello!
Is it there a way to specify certificate and key file or information?
Something like it is done within pysaml2:
https://pysaml2.readthedocs.io/en/latest/howto/config.html#cert-file

At the moment when the code execute saml_client = Saml2Client(config=sp_config):

saml_client = Saml2Client(config=sp_config)

then runs here:
https://github.com/IdentityPython/pysaml2/blob/155910dabffa0f60a2075be862f2cf8c31fad685/src/saml2/sigver.py#L1044

doesn't find the file and return sec_backend = None

Then, here gives error because of the above:
https://github.com/IdentityPython/pysaml2/blob/master/src/saml2/pack.py#L194

Any advice?
Thank you very much.

Question: Encrypted Assertion not unencrypting

Having spent many hours on this, I am asking for your help.
IDP is a wordpress site using a mini-Orange plugin for SSO SAML. User logs into this wordpress site, clicks a button, and is transferred via SSO to my django site where user is auto-logged in using django-saml2-auth.

This works fine with a signed Assertion/Request (with SAML2_AUTH.CERT_FILE pointed to the mini-Orange public key file and Encrypted Assertion not enabled.

However, when I enable Encrypted Assertion and provide mini-Orange my openSSL public key and point SAML2_AUTH.KEY_FILE to my private_key.pem, it seems that the decryption (and login) fails with Error Code 1110.

I have separately confirmed the private and public keys match by checking the modulus of each:
openssl rsa -noout -modulus -in private_key.pem | openssl sha256

final debug output is:
[2023-11-22 19:50:36,103] [DEBUG] [saml2.response.parse_assertion] Encrypted assertion/-s
[2023-11-22 19:50:36,104] [DEBUG] [saml2.response.parse_assertion] --- AVA: {}
[2023-11-22 19:50:36,104] [DEBUG] [django_saml2_auth.utils.handle_exception] No name_id in SAML response.

At this point, I am at a loss about what to do next to get Encrypted Assertion working. Any suggestions / help will be greatly appreciated.

'KEY_FILE': os.path.join(CERT_DIR, 'private_key.pem'),
'CERT_FILE': os.path.join(CERT_DIR, 'idp-signing.crt'),

# Require each authentication request to be signed
'AUTHN_REQUESTS_SIGNED': False,
# Require each assertion to be signed
'WANT_ASSERTIONS_SIGNED': True,
# Require response to be signed
'WANT_RESPONSE_SIGNED': False,
# Require each logout request to be signed
'LOGOUT_REQUESTS_SIGNED': False,
'TOKEN_REQUIRED': False,

Multi-tenant configuration while using JIT provisioning

I am currently running into limitations with the existing triggers in a scenario where a new user attempts to authentication via an IDP-initiated flow with multiple tenants/IDPs on the backend.

Details of my scenario:

  • Account administrator sets up an SSO configuration for their app. We save the metadata for this configuration.
  • A user on that team attempts to sign in via the IDP app. The user has not yet signed in and therefore has no account.
  • The GET_USER_ID_FROM_SAML_RESPONSE cannot find a user since they do not exist.
  • We're unable to pass a user_id to GET_METADATA_AUTO_CONF_URLS to pull the organization's specific metadata file.

One option would be to return a different ID (the team ID instead of the user ID) from the GET_USER_ID_FROM_SAML_RESPONSE call, then use that downstream, but it seems like this could have unintended side effects.

Is there a recommended approach for how to handle this situation? Should we consider adding another trigger type or even passing the SAML response into GET_METADATA_AUTO_CONF_URLS so it can pull the proper configuration file?

Ability to select URL to authenticate against

Let's say we have 3 different environments, dev, staging and production.

Each has a different URL:

dev.company.com
staging.company.com
company.com

Currently we have 3 different apps for each env. The goal is to only have one, and have the user select which one to use.

With AWS, its one app, and we select which account/role to auth to. For example: LINK

Is it possible to achieve the same result? The only difference would be the URL. Maybe the BEFORE_LOGIN hook is what would help here? It's like we need some sort of splash page beforehand.

Pulling user ID from request

In my workflow I need to pull some user info from the request and then use that in the GET_METADATA_AUTO_CONF_URLS trigger.

So when I call the acs or signin views I either need to pass in the user_id that I pulled previously, or change the trigger to allow me to pass in a request. Right now these flows pass in no user_id nor the ability to pull the user_id from the trigger.

I'm new to SAML though so perhaps I'm just missing something? My project needs to support multiple organizations using the same provider, which means storing different metadata urls for different users. I'm not seeing any way of doing that with the existing signin and acs views. I don't think my use case is particularly unique though, so I'm wondering how other people are provisioning the metadata currently.

Mapping groups to staff/superusers

More of a general question than an issue.

We have the implementation working, but this gives everyone superuser status by default:

'NEW_USER_PROFILE': {
    'ACTIVE_STATUS': True,
    'STAFF_STATUS': True,
    'SUPERUSER_STATUS': True,
}

If we wanted a second group of folks who would be staff, but not superusers, what's the best way to achieve this? I was thinking just creating two groups, but am not sure if cherry-picking permissions to assign to that group gives the same abilities as setting a user to staff/superuser.

Sorry, you are not allowed to access this app

Hi @mostafa,

I am having the same issue. I am having problem in configuring SAML2_AUTH. Can you please help me. Below are my SAML2_AUTH configuration. I am trying with FusionAuth IDP.

`SAML2_AUTH = {
# Metadata is required, choose either remote url or local file path
'METADATA_AUTO_CONF_URL': 'http://localhost:9011/samlv2/metadata/d7d09513-a3f5-401c-9685-34ab6c552453',
# 'METADATA_LOCAL_FILE_PATH': '[The metadata configuration file path]',
'KEY_FILE': '[The key file path]',
'CERT_FILE': '[The certificate file path]',

# If both `KEY_FILE` and `CERT_FILE` are provided, `ENCRYPTION_KEYPAIRS` will be added automatically. There is no need to provide it unless you wish to override the default value.
'ENCRYPTION_KEYPAIRS': [
    {
        "key_file": '[The key file path]',
        "cert_file": '[The certificate file path]',
    }
],

'DEBUG': False,  # Send debug information to a log file
# Optional logging configuration.
# By default, it won't log anything.
# The following configuration is an example of how to configure the logger,
# which can be used together with the DEBUG option above. Please note that
# the logger configuration follows the Python's logging configuration schema:
# https://docs.python.org/3/library/logging.config.html#logging-config-dictschema
'LOGGING': {
    'version': 1,
    'formatters': {
        'simple': {
            'format': '[%(asctime)s] [%(levelname)s] [%(name)s.%(funcName)s] %(message)s',
        },
    },
    'handlers': {
        'stdout': {
            'class': 'logging.StreamHandler',
            'stream': 'ext://sys.stdout',
            'level': 'DEBUG',
            'formatter': 'simple',
        },
    },
    'loggers': {
        'saml2': {
            'level': 'DEBUG'
        },
    },
    'root': {
        'level': 'DEBUG',
        'handlers': [
            'stdout',
        ],
    },
},

# 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': '[email protected]',
    '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': 'http://localhost:9011/',  # Custom URL to validate incoming SAML requests against
'ENTITY_ID': 'http://localhost:9011/saml2_auth/acs/',  # Populates the Issuer element in authn request
# 'NAME_ID_FORMAT': user.email,  # 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': 'http://localhost:8000/',
# 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': ["http://localhost:8000/"],
# Allowed hosts to redirect to using the ?next parameter
'TOKEN_REQUIRED': False,  # Whether or not to require the token parameter in the SAML assertion

}`

Capture

Originally posted by @PrabhuBose in #205 (comment)

How set USE_JWT=True when SPA and USE_JWT=False when web access?

Hi @mostafa,
thanks for support this library!

When i use SPA i want to generate token, but when i use web i want to navigate to the web home page.

In summary i want to switch ON if i login with Single Page Application (need Token redirect), switch OFF when i login from web (need login into django).

How can i do that?
Thanks!

use_jwt = dictor(saml2_auth_settings, "USE_JWT", False)
if use_jwt and target_user.is_active:
# Create a new JWT token for IdP-initiated login (acs)
jwt_token = create_custom_or_default_jwt(target_user)
custom_token_query_trigger = dictor(saml2_auth_settings, "TRIGGER.CUSTOM_TOKEN_QUERY")
if custom_token_query_trigger:
query = run_hook(custom_token_query_trigger, jwt_token)
else:
query = f"?token={jwt_token}"
# Use JWT auth to send token to frontend
frontend_url = dictor(saml2_auth_settings, "FRONTEND_URL", next_url)
return HttpResponseRedirect(frontend_url + query)
if target_user.is_active:
# Try to load from the `AUTHENTICATION_BACKENDS` setting in settings.py
if hasattr(settings, "AUTHENTICATION_BACKENDS") and settings.AUTHENTICATION_BACKENDS:
model_backend = settings.AUTHENTICATION_BACKENDS[0]
else:
model_backend = "django.contrib.auth.backends.ModelBackend"
login(request, target_user, model_backend)
after_login_trigger = dictor(saml2_auth_settings, "TRIGGER.AFTER_LOGIN")
if after_login_trigger:
run_hook(after_login_trigger, request.session, user) # type: ignore
else:
raise SAMLAuthError("The target user is inactive.", extra={
"exc_type": Exception,
"error_code": INACTIVE_USER,
"reason": "User is inactive.",
"status_code": 500
})

Package appears to be broken

Opening a new issue around the reports at the bottom of #25. The issues reported by all of those users is different than the issue the OP reported.

To summarize, with everything configured on the Okta/IdP side (signing turned on, etc.) and the METADATA_AUTO_CONF_URL set, the login process breaks when Pysaml tries to sign the request using xmlsec without a private key. The com_list in my case looks like

['/usr/local/bin/xmlsec1', '--sign', '--privkey-pem', None, '--id-attr:ID', 'urn:oasis:names:tc:SAML:2.0:protocol:AuthnRequest', '--node-id', 'id-DfHiSpJb952UZTS0G']

The last call is below -_runxmlsec in the Pysaml module. The exception generated by this mis-configuration is not an XmlsecError so it's uncaught.

https://github.com/IdentityPython/pysaml2/blob/4fa20a92a9d7fccc2ca34f1f6ad777cc0fd36ef7/src/saml2/sigver.py#L783

The actual error is
TypeError: sequence item 3: expected str instance, NoneType found error

And all I see in the log is

Internal Server Error: /accounts/login/
Internal Server Error: /accounts/login/

I have spent some tracking the code path and context at each step and don't see where the private key could possibly be populated from. That, along with the reports from other users on the same error leads me to believe that the package is broken at the moment. I don't have experience with the package however and would be happy to be proven wrong.

No username or email provided

Hi, I have used your package to login sso with Okta IdP.
When call login from browser, it will direct to login page of Okta. After login, it will call to acs/ path. And raise an error [django_saml2_auth.utils.handle_exception] No username or email provided.
I have no idea for this problem. Please help me with that. Thank you so much!
This is my setting:
SAML2_AUTH = {
'DEFAULT_NEXT_URL': '/admin/', # after login, it will redirect to this URL.
'ENTITY_ID': "https://090e-14-176-231-159.ngrok-free.app/sso/acs/",
'NAME_ID_FORMAT': 'urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress',
'USE_JWT': False, # use JWT for info storage, can be True or False
'KEY_FILE': './private_key.pem',
'CERT_FILE': './public_certificate.pem',
'ENCODING': 'UTF-8',
'LOGIN_URL': [http:url/to/okta/login/],
'METADATA_AUTO_CONF_URL': [http:url/to/okta/metadata],
'ASSERTION_URL': 'https://090e-14-176-231-159.ngrok-free.app',
'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',
},
'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
},
'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
'WANT_ASSERTIONS_SIGNED': True, # Require each assertion to be signed
'WANT_RESPONSE_SIGNED': True, # Require response to be signed
'LOGGING': {
'version': 1,
'formatters': {
'simple': {
'format': '[%(asctime)s] [%(levelname)s] [%(name)s.%(funcName)s] %(message)s',
},
},
'handlers': {
'stdout': {
'class': 'logging.StreamHandler',
'stream': 'ext://sys.stdout',
'level': 'DEBUG',
'formatter': 'simple',
},
},
'loggers': {
'saml2': {
'level': 'DEBUG'
},
},
'root': {
'level': 'DEBUG',
'handlers': [
'stdout',
],
},
},
}

Getting "expected str instance, NoneType found" error

Hoping you can help - wondering if this is a config problem.

Request is formed:

<ns0:AuthnRequest xmlns:ns0="urn:oasis:names:tc:SAML:2.0:protocol" xmlns:ns1="urn:oasis:names:tc:SAML:2.0:assertion" xmlns:ns2="http://www.w3.org/2000/09/xmldsig#" ID="id-3ZwAhtkpJ7Z6z7SBp" Version="2.0" IssueInstant="2023-01-19T13:45:36Z" Destination="https://wgbh.okta.com/app/wgbh_gbhannualreportsaml2_1/exk1qzsalqiXXNp7c0h8/sso/saml" ProtocolBinding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" AssertionConsumerServiceURL="https://cms.local.wgbhdigital.org/sso/acs/"><ns1:Issuer Format="urn:oasis:names:tc:SAML:2.0:nameid-format:entity">https://cms.local.wgbhdigital.org/saml2_auth/acs/</ns1:Issuer><ns2:Signature Id="Signature1"><ns2:SignedInfo><ns2:CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#" /><ns2:SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1" /><ns2:Reference URI="#id-3ZwAhtkpJ7Z6z7SBp"><ns2:Transforms><ns2:Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature" /><ns2:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#" /></ns2:Transforms><ns2:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1" /><ns2:DigestValue /></ns2:Reference></ns2:SignedInfo><ns2:SignatureValue /></ns2:Signature></ns0:AuthnRequest>

But then I get this error:
2023-01-19 08:45:36,205 django_saml2_auth.utils:158 DEBUG sequence item 3: expected str instance, NoneType found

I've tried a number of SAML configs, but thing seems to change this. Most recent is:

    SAML2_AUTH = {
        # Metadata is required, choose either remote url or local file path
        'METADATA_AUTO_CONF_URL': os.environ['SAML_METADATA_URL'],
        'METADATA_LOCAL_FILE_PATH': False,   

        'DEBUG': True,

        # 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': False,   # 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': True,  # 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': MAP_SAML_EMAIL,
            'username': MAP_SAML_USERNAME,
            'first_name': MAP_SAML_FIRSTNAME,
            'last_name': MAP_SAML_LASTNAME,
        },
        'TRIGGER': {
            'CREATE_USER': None,
            'BEFORE_LOGIN': None,
            'GET_METADATA_AUTO_CONF_URLS': None
        },
        'ASSERTION_URL': None, # Custom URL to validate incoming SAML requests against
        'ENTITY_ID': f'https://{HOST_DOMAIN}/saml2_auth/acs/', # Populates the Issuer element in authn request
        'NAME_ID_FORMAT': None, # Sets the Format property of authn NameIDPolicy element
        'USE_JWT': False, # 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
        'FRONTEND_URL': None, # 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': None, # Allowed hosts to redirect to using the ?next parameter
        'TOKEN_REQUIRED': False,  # Whether or not to require the token parameter in the SAML assertion
    }

Thanks for looking.

Understanding sp-initiated process

I've an app working just fine with IdP-initiated login. However, the app is behind a proxy and doesn't replay the POST request from the IdP. It simply issues a GET request for the same URL (domain.com/sso/acs) after authentication. This causes us to have to refresh twice, in order to authenticate with the proxy, and then go through the IdP process again.

To work around this, I figure if I can get the app to redirect to our IdP with a GET request, I'd be good. If I navigate to domain.com/sso/sp/, I seem to get denied. I've played around with the USE_JWT setting, but we don't use JWT for user authentication so I don't think this is the right path. The code for sp_initiated_login seems to require a token, so maybe JWT has to be used? Is there a way to check if you're logged in, and if not, redirect you to your IdP? I'm passing in a RelayState from our IdP in hopes of it redirecting me, but don't think that is right either.

Username SAML attribute not passing to User object

We have forked this library with a few tweaks, wanted to run this by you on adding these to the main branch:

user.py

-def create_new_user(email: str, firstname: str, lastname: str) -> Type[Model]:
+def create_new_user(username: str, email: str, firstname: str, lastname: str) -> Type[Model]:

-        user = user_model.objects.create_user(email, first_name=firstname, last_name=lastname)
+        user = user_model.objects.create_user(username=username, email=email, first_name=firstname, last_name=lastname)

-            target_user = create_new_user(get_user_id(user), user["first_name"], user["last_name"])
+            target_user = create_new_user(user["username"], user["email"], user["first_name"], user["last_name"])

We were originally passing all 4 attributes, and the user was being created, but with a blank email field. Now we are only passing three (email, first, last) and inferring the username below:

saml.py

+    # Infer username from email
+    user["username"] = user["email"].split("@")[0]

To add the username attribute is a non-trivial task with our idP provider. Only one line in Python. Could make a INFER_USERNAME_FROM_EMAIL flag for use in the SAML2_AUTH map.

Help locating sp metadata URL to provide to Idp

This is my first time working on a SAML integration and I'm a bit confused. We need to provide an sp metadata URL to our Idp and we can't figure it out. I'm running my django application locally.

In my urls.py, I have
import django_saml2_auth.views

and

urlpatterns = [
 re_path(r'^sso/', include('django_saml2_auth.urls')),
...
]

In settings, I have

INSTALLED_APPS = [
    '...',
    'django_saml2_auth',
]

And

SAML2_AUTH = {
    # Metadata is required, choose either remote url or local file path
    'METADATA_AUTO_CONF_URL': <Our URL>,
    'ENTITY_ID': 'http://django:8000/sso/sp',
}

When I go to any of the URLs (ex. /sso/sp), I get the permission denied page. We haven't set up the SAML integration with our provider yet because they need the sp metadata URL before they can set up things on their end. Can someone let me know where to find this URL? All the views in the app seem to require the SAML integration to already be set up.

Auto created users are missing email when using 'email' as USERNAME_FIELD and are not able to login afterwards

Hello there,

I ran into a problem using the package with the following configuration:

  • Custom user model
  • USERNAME_FIELD set as 'email'

When a user does not exist, he is correctly created but after logout and logging back in, he is not found anymore. The packages tries to create it but as he already exists with the same username, it crashes.

The mail is never set in the create_new_user method even if USERNAME_FIELD is set as email. Check here

I have been able to patch this behaviour using the following TRIGGER.CREATE_USER :

def post_create_user(user_dict, *args, **kwargs):
    user_model = get_user_model()
    user_id = user_dict.get(user_model.USERNAME_FIELD)
    user = user_model.objects.get(username=user_id)
    user.email = user_id
    user.save()

CSRF Token Issue

image @johnyaku and I are getting this error message when we followed the steps to set up SSO using this library. We have tried commenting out the middleware setting for csrf but have still received the same error message. The variables we added in settings included the metadata URL and entity id. We were just wondering if there was anything else we should consider for the setup or if the certificate variables in settings need to be provided?

Can't download v3.8.0

Version 3.8.0 doesn't seem to be downloadable on my end. Error message below:

ERROR: Could not find a version that satisfies the requirement grafana-django-saml2-auth==3.8.0 (from -r /var/folders/mm/n8kv1v8d36n8hmhk82rtvvj40000gn/T/pipenv-7108zoit-requirements/pipenv-ezvkw52z-requirement.txt (line 1)) (from versions: 3.6.0, 3.7.0) ERROR: No matching distribution found for grafana-django-saml2-auth==3.8.0 (from -r /var/folders/mm/n8kv1v8d36n8hmhk82rtvvj40000gn/T/pipenv-7108zoit-requirements/pipenv-ezvkw52z-requirement.txt (line 1))

Pip still has 3.7.0 as latest: https://pypi.org/project/grafana-django-saml2-auth/

Specifically, I'd like to use the new TRIGGER.GET_USER_ID_FROM_SAML_RESPONSE feature!

Any ideas?

Configurable authentication backend

Currently, the authentication backend passed to the django.contrib.auth.login method is hard-coded to "django.contrib.auth.backends.ModelBackend". This makes it impossible to use this library if you use any other authentication backend, e.g. a custom one. Since the django settings dict is already imported in views.py, one implementation method could be to replace the hard-coded line with

model_backend = dictor(
    settings,
    "AUTHENTICATION_BACKENDS.0",
    default="django.contrib.auth.backends.ModelBackend"
)

This will look at the current list of authentication backends defined in settings.py and return the first one from the list.

?next=/path/ does not overwrite default next url

All my configuration works fine, except ?next=/path/ is not redirecting me to anywhere else rather than what I have configured as default next url /admin.

Any suggestions on this?

Thank you.

I'm still having a problem with signing SSO communication (the same problem that was fixed in Issue #25)

Hi. I installed 3.11 and I'm still having the same problem as discussed in issue #25 (Certificate and Key File) that was fixed in 3.11. Here is the relevant part of the log file:

Traceback (most recent call last):
  File "/usr/local/lib/python3.9/dist-packages/django_saml2_auth/utils.py", line 185, in wrapper
    result = function(request)
  File "/usr/local/lib/python3.9/dist-packages/django_saml2_auth/views.py", line 276, in signin
    _, info = saml_client.prepare_for_authenticate(relay_state=next_url)  # type: ignore
  File "/usr/local/lib/python3.9/dist-packages/saml2/client.py", line 72, in prepare_for_authenticate
    reqid, negotiated_binding, info = self.prepare_for_negotiated_authenticate(
  File "/usr/local/lib/python3.9/dist-packages/saml2/client.py", line 150, in prepare_for_negotiated_authenticate
    reqid, request = self.create_authn_request(
  File "/usr/local/lib/python3.9/dist-packages/saml2/client_base.py", line 446, in create_authn_request
    msg = self._message(
  File "/usr/local/lib/python3.9/dist-packages/saml2/entity.py", line 588, in _message
    signed_req = self.sign(
  File "/usr/local/lib/python3.9/dist-packages/saml2/entity.py", line 524, in sign
    return signed_instance_factory(msg, self.sec, to_sign)
  File "/usr/local/lib/python3.9/dist-packages/saml2/sigver.py", line 331, in signed_instance_factory
    signed_xml = seccont.sign_statement(signed_xml, node_name=node_name, node_id=nodeid)
  File "/usr/local/lib/python3.9/dist-packages/saml2/sigver.py", line 1673, in sign_statement
    return self.crypto.sign_statement(
  File "/usr/local/lib/python3.9/dist-packages/saml2/sigver.py", line 779, in sign_statement
    (stdout, stderr, output) = self._run_xmlsec(com_list, [tmp.name])
  File "/usr/local/lib/python3.9/dist-packages/saml2/sigver.py", line 841, in _run_xmlsec
    logger.debug("xmlsec command: %s", " ".join(com_list))
TypeError: sequence item 3: expected str instance, NoneType found

Internal Server Error: /mailman3/accounts/login/

Generated jwt token is not validated in API (djangorestframework-simplejwt)

Hello,

I am using the django-saml2-auth with JWT to integrating to django-restframework. In my project I am using too djangorestframework-simplejwt. However, the jwt token generated after saml authentication in Azure AD is not validated in my API.

When I try to access some endpoint of my API:

{
  "status": "error",
  "code": 401,
  "data": null,
  "message": "Given token not valid for any token type"
}

Some points of my settings:

SIMPLE_JWT = {
    "ACCESS_TOKEN_LIFETIME": timedelta(days=1),
    "REFRESH_TOKEN_LIFETIME": timedelta(days=2),
    'AUTH_HEADER_TYPES': ('Token',),
    'UPDATE_LAST_LOGIN': True,
    "TOKEN_OBTAIN_SERIALIZER": "myapi.token.serializer.CustomTokenObtainPairSerializer",
    "ALGORITHM": "HS256",
}
SAML2_AUTH = {
    'METADATA_LOCAL_FILE_PATH': '/app/static/PSAT-Core-Saml2.xml',

    'KEY_FILE': '/app/static/chave_privada.key',
    'CERT_FILE': '/app/static/certificado.crt',

    'DEBUG': True,
    'DEFAULT_NEXT_URL': '/api',

    'CREATE_USER': 'TRUE',
    'NEW_USER_PROFILE': {
        'USER_GROUPS': [],
        'ACTIVE_STATUS': True,
        'STAFF_STATUS': False,
        'SUPERUSER_STATUS': False,
    },
    'ATTRIBUTES_MAP': {
        'email': 'name',
        'username': 'name',
        'first_name': 'givenname',
        'last_name': 'surname',
    },

    'ASSERTION_URL': 'http://localhost:8000', # Custom URL to validate incoming SAML requests against
    'ENTITY_ID': 'http://localhost:8000/saml2_auth/acs/', # Populates the Issuer element in authn request
    'NAME_ID_FORMAT': 'urn:oasis:names:tc:SAML:2.0:nameid-format:persistent', # Sets the Format property of authn NameIDPolicy element
    'USE_JWT': True,
    'JWT_ALGORITHM': 'HS256',  # JWT algorithm to sign the message with
    'JWT_SECRET': 'your.jwt.secret',  # JWT secret to sign the message with
    'FRONTEND_URL': 'http://localhost:3000/login',
    'WANT_ASSERTIONS_SIGNED': True,
    'AUTHN_REQUESTS_SIGNED': True,
    'WANT_RESPONSE_SIGNED': True,
    'TOKEN_REQUIRED': False
}

I'm glad for any help.

Okta login page redirect url passed as RelayState instead of "login_next_url"

When a user logs into my application from the Okta homepage, they are automatically taken to the default next url instead of their intended destination because the POST request to acs contains their redirect url as a RelayState instead of the "login_next_url" parameter. In the source code, I see that RelayState is sometimes used to pass a JWT, but if RelayState is a redirect url, acs ignores it. Can django-saml2-auth please support redirect urls by way of RelayStates?

(Solving this problem via customizing Okta's signin widget is unfortunately not an option for me.)
Thank you for continuing to maintain django-saml2-auth!

Permission Denied, accounts/login/

Hi, i'm trying to integrate SAML authentication in my Django 3.2 Project but i'm having a permission denied on /accounts/login/ Sorry, you are not allowed to access this app Error code: 1107 Reason: There was an error processing your request.

I've tried the package djangosaml2 from IdentityPython and it't worked great, I can login and logout with SAML authentication but with your plugin I can't even acces the login page.

Thanks in advanced.

urlpatterns = [
    # django saml auth grafana
    path(r'saml/', include('django_saml2_auth.urls')),
    path(r'accounts/login/', django_saml2_auth.views.signin),
]
SAML2_AUTH = {
    # Metadata is required, choose either remote url or local file path
    'METADATA_LOCAL_FILE_PATH': [os.path.join(BASE_DIR, 'mylocalfile.xml')],

    'DEBUG': True,  # Send debug information to a log file

    # 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.
    'ATTRIBUTES_MAP': {  # Change Email/UserName/FirstName/LastName to corresponding SAML2 userprofile attributes.
        'Email': 'email',
        'UserName': 'username',
        'FirstName': 'first_name',
        'LastName': 'last_name',
    },
    'ASSERTION_URL': 'https://company-name.com/saml/acs',  # Custom URL to validate incoming SAML requests against
    'ENTITY_ID': 'https://company-name.com/',  # Populates the Issuer element in authn request
    'AUTHN_REQUESTS_SIGNED': False, # Require each authentication request to be signed
    'LOGOUT_REQUESTS_SIGNED': False,  # Require each logout request to be signed
    'WANT_ASSERTIONS_SIGNED': False,  # Require each assertion to be signed
    'WANT_RESPONSE_SIGNED': False,  # Require response to be signed
}

Getting 1106 Error on Dev Server

I recently started working on SAML, through the previous answers I was able to successfully implement SAML auth, login via Active Directory IDP. But when I ran my code on a development server, I am getting error 1106.

Upon some debugging i have figured out that the reverse generation of acs url is the issue.
acs_url = domain + get_reverse([acs, "acs", "django_saml2_auth:acs"]) # type: ignore

But I am unable to understand why the behaviour changes between a local system and on a server.

My django project urls:
SAML URLs
path('sso/signin/', saml_views.signin),
path('sso/acs/', saml_views.acs),
path('api/v1/redirect/auth', saml_triggers.saml_auth_redirect, name='saml_auth_redirect')

Settings
These are my saml_settings and respective urls:

default_next_url = 'http://127.0.0.1:8003/api/v1/redirect/auth'
assertion_url = 'http://127.0.0.1:8003'
entity_id = 'http://127.0.0.1:8003/'
frontend_url = 'http://localhost:3000/auth/saml'

SAML2_AUTH = {
# Metadata is required, choose either remote url or local file path
'KEY_FILE': os.path.join(BASE_DIR, 'appraisal/views/'),
'CERT_FILE': os.path.join(BASE_DIR, 'appraisal/views/'),

'DEBUG': False,  # Send debug information to a log file

# Optional settings below
'DEFAULT_NEXT_URL': default_next_url,
'ATTRIBUTES_MAP': {  # Change Email/UserName/FirstName/LastName to corresponding SAML2 userprofile attributes.
    'email': 'emailAddress',   # currently fetching user from email
},
'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
    # Trigger paths removed they are working
    'GET_USER': 'trigger_path',
    'CUSTOM_CREATE_JWT': 'trigger_path',
    'CUSTOM_TOKEN_QUERY': 'trigger_path',
    'GET_METADATA_AUTO_CONF_URLS': 'trigger_path',
},
'ASSERTION_URL': assertion_url,  # Custom URL to validate incoming SAML requests against
'ENTITY_ID': entity_id,  # Populates the Issuer element in authn request
'NAME_ID_FORMAT': 'urn:oasis:names:tc:SAML:2.0:nameid-format:email',  # 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
'FRONTEND_URL': frontend_url,  # 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_sensitive 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': ['127.0.0.1:8003'],
'TOKEN_REQUIRED': False,

}

WANT_ASSERTIONS_SIGNED not working

Summary

I apologize if this is a configuration issues, but after attempting to fix this for over a week and reading all the documentation I feel like there is an issue with 'WANT_RESPONSE_SIGNED': True, not actually taking affect. I have also tried removing 'WANT_RESPONSE_SIGNED': True, from the config as this library should default to wanting the SAML Response to be signed.

Background

To give a little bit of background, I have a Django app acting as the SP. Everything works with the SAML login, I browse to my Django app, get redirected to the IdP, enter in the SAML creds, get redirected to the Django app, and now have they attributes from the SAML account in Django. The IdP is sending an XML response properly signed via the metadata file, however I can "strip" that signiture from the SAML Request and the Django app happily grants me with a session ID.

Issue

This can allow an actor to "spoof" their SAML attributes causing the app to grant privileged escalation depending on the Django app.

Screenshots of SAML Raider

This request gets a valid sessionid with the Response being Signed from the IdP. This is expected behavior.
image

This request has the signature remove by pressing "Remove Signatures". As we can see we still get a valid sessionid. This is bad.
testgit

image

Framework

Python version : 3.9.16
Django version : 3.2.18
django-saml2-auth version : 3.9.0

SAML Config

SAML2_AUTH = {
    'METADATA_LOCAL_FILE_PATH': 'metadatadev.xml',
    'DEFAULT_NEXT_URL': '/',
    'CREATE_USER': True,
    "NEW_USER_PROFILE": {
        "USER_GROUPS": [],
        "ACTIVE_STATUS": True,
        "STAFF_STATUS": False,
        "SUPERUSER_STATUS": False
    },
    'ATTRIBUTES_MAP': {
        'email': 'email',
	'username': 'UserName',
	'first_name': 'FirstName',
        'last_name': 'LastName',
        #'token': 'Token',
    },

    'ENTITY_ID': 'example.net',

    'USE_JWT': False,
    'FRONTEND_URL': 'https://example.net',
    'LOGIN_CASE_SENSITIVE': False,
    'AUTHN_REQUESTS_SIGNED': True,
    'LOGOUT_REQUESTS_SIGNED': True,
    'WANT_ASSERTIONS_SIGNED': True,
    'WANT_RESPONSE_SIGNED': True,
    "ALLOWED_REDIRECT_HOSTS": ["https://app.example.com",
                               "https://api.example.com",
                               "https://example.com"],
    "TOKEN_REQUIRED": True
}

If anyone can help me with some guidance on how to fix this that would be awesome. I am thinking of trying to use the "raw" pysaml library but don't want to run into the same issue if this is something wrong with my app. Thanks for the help!

Saml2 Attributes_Map, email and username attributes don't work

'ATTRIBUTES_MAP': { 
    'email': 'mail',
    'username': 'eduPersonPrincipalName',
    'first_name': 'givenName',
    'last_name': 'sn',
    'token': False,  # Mandatory, can be unrequired if TOKEN_REQUIRED is False
    'groups': 'Groups',  # Optional

In the settings.py file, it does not get the assignment of the email and username attributes correctly. We have tried to change and put our "mail" attribute in the first_name section that works and it correctly assigns the mail but in the "email" saml attribute you put the "mail" and it does not assign anything. The same goes for the "username" attribute.

remove the exception handler / improve logging out of the box (esp. when settings.DEBUG=True)

Hi !

Out of the box, the package is very silent whenever issues happen.

It's not trivial to set logging correctly (it's not with native Django, and it's even less with possible not clearly defined interference between the native django settings.LOGGING and settings.DEBUG and their counterparts in settings.SAML2_AUTH).

In my case, after trying for over one hour to get output, I got rid of the exception handler and then only got a nice and clear trace indicating the exact issue (which was Cannot find ['xmlsec1']). From the issue tracker, it seem many people struggle in a similar way with uninformative error messages (Sorry, you are not allowed to access this app).

What about completely removing the exception handler when Django's settings.DEBUG=True, so that we get regular debug information ? Arguably even so when DEBUG=False, as I don't see a good reason for this package to provide some custom exception handling ?

Thanks !

For reference, here's a dirty monkey-patch to disable the exception handler (in your settings.py):

# monkey-patch out django_saml2_auth's exception handler to get proper traces
# https://github.com/grafana/django-saml2-auth/issues/275
if DEBUG:
    import django_saml2_auth.utils
    django_saml2_auth.utils.exception_handler = lambda func: func

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.