Package: grafana-django-saml2-auth
Django SAML2 AuthenticationThis 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 InformationOriginal Author: Fang Li (@fangli)Maintainer: Mostafa Moradian (@mostafa)Version support matrix:PythonDjangodjango-saml2-authEnd of Support(django-saml2-auth)End of extended support(Django)3.9.x, 3.10.x3.2.x>=3.4.0April 20243.9.x, 3.10.x, 3.11.x, 3.12.x4.2.x>=3.4.0April 2026Release logs are availablehere.For contribution, readcontributing guide.CycloneDX SBOMFromv3.6.1, CycloneDX SBOMs will be generated forrequirements.txtandrequirements_test.txtand 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.DonatePlease give us a shinyand help spread the word.InstallationYou can install this plugin viapip. Make sure you updatepipto be able to install from git:pipinstallgrafana-django-saml2-author from source:gitclonehttps://github.com/grafana/django-saml2-authcddjango-saml2-auth
pythonsetup.pyinstallxmlsecis also required bypysaml2, so it must be installed://RPM-baseddistributions# yum install xmlsec1//DEB-baseddistributions# apt-get install xmlsec1//macOS# brew install xmlsec1Windows binariesare also available.How to use?Once you have the library installed or in yourrequirements.txt, import the views module in your rooturls.py:importdjango_saml2_auth.viewsOverride the default login page in the rooturls.pyfile, by adding these linesBEFOREanyurlpatterns:# 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.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.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.views.signin),Add'django_saml2_auth'toINSTALLED_APPSin your djangosettings.py:INSTALLED_APPS=['...','django_saml2_auth',]Insettings.py, add the SAML2 related configuration:Please note, the only required setting isMETADATA_AUTO_CONF_URLor the existence of aGET_METADATA_AUTO_CONF_URLStrigger 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/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}In your SAML2 SSO identity provider, set the Single-sign-on URL and Audience URI (SP Entity ID) tohttp://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 forFirefoxorChrome. 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 thesettings.pyfile by setting theDEBUGflag toTrueand enabling theLOGGINGconfiguration. 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 SettingsSome 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 thepysaml2 documentation, which contains examples of available settings. Also, note that all settings are not implemented in this module.Field nameDescriptionData type(s)Default value(s)ExampleMETADATA_AUTO_CONF_URLAuto SAML2 metadata configuration URLstrNonehttps://ORG.okta.com/app/APP-ID/sso/saml/metadataMETADATA_LOCAL_FILE_PATHSAML2 metadata configuration file pathstrNone/path/to/the/metadata.xmlKEY_FILESAML2 private key file pathstrNone/path/to/the/key.pemCERT_FILESAML2 public certificate file pathstrNone/path/to/the/cert.pemENCRYPTION_KEYPAIRSRequired for handling encrypted assertions. Will be automatically set if bothKEY_FILEandCERT_FILEare set.listNot set.[ { 'key_file': '[The key file path]', 'cert_file': '[The certificate file path]' } ]DEBUGSend debug information to a log fileboolFalseLOGGINGLogging configuration dictionarydictNot set.DEFAULT_NEXT_URLCustom 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.stradmin:indexhttps://app.example.com/account/loginCREATE_USERDetermines if a new Django user should be created for new usersboolTrueCREATE_GROUPSDetermines if a new Django group should be created if the SAML2 Group does not existboolFalseNEW_USER_PROFILEDefault settings for newly created usersdict{'USER_GROUPS': [], 'ACTIVE_STATUS': True, 'STAFF_STATUS': False, 'SUPERUSER_STATUS': False}ATTRIBUTES_MAPMapping of Django user attributes to SAML2 user attributesdict{'email': 'user.email', 'username': 'user.username', 'first_name': 'user.first_name', 'last_name': 'user.last_name', 'token': 'token'}{'your.field': 'SAML.field'}TOKEN_REQUIREDSet this toFalseif you don't require the token parameter in the SAML assertion (in the attributes map)boolTrueTRIGGERHooks to trigger additional actions during user login and creation flows. TheseTRIGGERhooks are strings containing adotted module namewhich 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_USERA 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.strNonemy_app.models.users.getTRIGGER.CREATE_USERA 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.strNonemy_app.models.users.createTRIGGER.BEFORE_LOGINA 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.strNonemy_app.models.users.before_loginTRIGGER.AFTER_LOGINA 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.strNonemy_app.models.users.after_loginTRIGGER.GET_METADATA_AUTO_CONF_URLSA hook function that returns a list of metadata Autoconf URLs. This can override theMETADATA_AUTO_CONF_URLto enumerate all existing metadata autoconf URLs.strNonemy_app.models.users.get_metadata_autoconf_urlsTRIGGER.CUSTOM_DECODE_JWTA hook function to decode the user JWT. This method will be called instead of thedecode_jwt_tokendefault function and should return the user_model.USERNAME_FIELD. This method accepts one parameter:token.strNonemy_app.models.users.decode_custom_tokenTRIGGER.CUSTOM_CREATE_JWTA hook function to create a custom JWT for the user. This method will be called instead of thecreate_jwt_tokendefault function and should return the token. This method accepts one parameter:user.strNonemy_app.models.users.create_custom_tokenTRIGGER.CUSTOM_TOKEN_QUERYA hook function to create a custom query params with the JWT for the user. This method will be called afterCUSTOM_CREATE_JWTto 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.strNonemy_app.models.users.get_custom_token_queryASSERTION_URLA URL to validate incoming SAML responses against. By default,django-saml2-authwill 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 againstASSERTION_URLinstead - perfect for when Django is running behind a reverse proxy.strhttps://example.comENTITY_IDThe optional entity ID string to be passed in the 'Issuer' element of authentication request, if required by the IDP.strNonehttps://exmaple.com/sso/acsNAME_ID_FORMATSet 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_JWTSet this to the booleanTrueif you are using Django with JWT authenticationboolFalseJWT_ALGORITHMJWT algorithm (str) to sign the message with:supported algorithms.strHS512orRS512JWT_SECRETJWT secret to sign the message if an HMAC is used with the SHA hash algorithm (HS*).strNoneJWT_PRIVATE_KEYPrivate key (str) to sign the message with. The algorithm should be set toRSA256or a more secure alternative.strorbytes--- YOUR PRIVATE KEY ---JWT_PRIVATE_KEY_PASSPHRASEIf your private key is encrypted, you must provide a passphrase for decryption.strorbytesNoneJWT_PUBLIC_KEYPublic key to decode the signed JWT token.strorbytes'--- YOUR PUBLIC KEY ---'JWT_EXPJWT expiry time in secondsint60FRONTEND_URLIfUSE_JWTisTrue, you should set the URL to where your frontend is located (will default toDEFAULT_NEXT_URLif you fail to do so). Once the client is authenticated through the SAML SSO, your client is redirected to theFRONTEND_URLwith the JWT token astokenquery parameter. Example:https://app.example.com/?&token=<your.jwt.token. With the token, your SPA can now authenticate with your API.stradmin:indexAUTHN_REQUESTS_SIGNEDSet this toFalseif your provider doesn't sign each authorization request.boolTrueLOGOUT_REQUESTS_SIGNEDSet this toFalseif your provider doesn't sign each logout request.boolTrueWANT_ASSERTIONS_SIGNEDSet this toFalseif your provider doesn't sign each assertion.boolTrueWANT_RESPONSE_SIGNEDSet this toFalseif you don't want your provider to sign the response.boolTrueACCEPTED_TIME_DIFFSets theaccepted time diffin secondsintorNoneNoneALLOWED_REDIRECT_HOSTSAllowed hosts to redirect to using the?next=parameterlist[]['https://app.example.com', 'https://api.exmaple.com']TriggersSetting nameDescriptionInterfaceGET_METADATA_AUTO_CONF_URLSAuto SAML2 metadata configuration URLget_metadata_auto_conf_urls(user_id: Optional[str] = None) -> Optional[List[Dict[str, str]]]GET_USER_ID_FROM_SAML_RESPONSEAllows 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 SettingsBoth symmetric and asymmetric signing functions aresupported. If you want to use symmetric signing using a secret key, use either of the following algorithms plus a secret key:HS256HS384HS512{...'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:RS256RS384RS512ES256ES256KES384ES521ES512PS256PS384PS512EdDSA{...'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 andJWT_SECRETare defined, theJWT_ALGORITHMdecides which method to use for signing tokens.Custom token triggersThis is an example of the functions that could be passed to theTRIGGER.CUSTOM_CREATE_JWT(it uses theDRF Simple JWT library) and toTRIGGER.CUSTOM_TOKEN_QUERY:fromrest_framework_simplejwt.tokensimportRefreshTokendefget_custom_jwt(user):"""Create token for user and return it"""returnRefreshToken.for_user(user)defget_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 MessagesThe default permissiondenied,errorand userwelcomepage 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. TheDjango user objectis available within the template as theusertemplate variable.To enable a logout page, add the following lines tourls.py, before anyurlpatterns:# 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 thesettings.pyATTRIBUTES_MAP, update them insettings.py.For Okta UsersI created this plugin originally for Okta. TheMETADATA_AUTO_CONF_URLneeded insettings.pycan be found in the Okta Web UI by navigating to the SAML2 app'sSign Ontab. In theSettingsbox, you should see:Identity Provider metadata is available if this application supports dynamic configuration.TheIdentity Provider metadatalink is theMETADATA_AUTO_CONF_URL.More information can be found in theOkta Developer Documentation.Release ProcessI adopted a reasonably simple release process, which is almost automated, except for two actions that needed to be taken to start a release:Updatesetup.pyand increase the version number in thesetupfunction. Unless something backward-incompatible is introduced, only the minor version is upgraded: 3.8.0 becomes 3.9.0.Tag themainbranch with the thevSEMVER, e.g.v3.9.0, and git-push the tag.The release and publish to PyPI is handled in the CI/CD using GitHub Actions.Create a new release with auto-generated (and polished) release notes on the tag.DownloadSBOM artifactsgenerated by GitHub Actions for the corresponding run, and add them to therelease files.