Skip to main content

Django SAML2 Authentication Made Easy.

Project description

Django SAML2 Authentication

PyPI GitHub Workflow Status Coveralls Downloads (new) Downloads (legacy)

[!NOTE] To learn more about SAML SSO in Django, read the "SAML SSO in Django" series: Part 1: Introduction to SAML SSO and Part 2: Integrating SAML SSO into a Django app with Okta.

[!IMPORTANT] This project is a fork of grafana/django-saml2-auth, itself forked from fangli/django-saml2-auth. As I stated in this comment, I no longer work for Grafana and no longer have access to that repository, so ongoing work continues here: mostafa/django-saml2-auth.

PyPI: django-saml2-auth-community is the community-maintained line, not a Grafana product. Keep import django_saml2_auth; only the pip/lockfile install name changes from grafana-django-saml2-auth. The old PyPI project may ship a final release that depends on this package (essentially renaming the project to django-saml2-auth-community).

This plugin integrates SAML2 authentication into Django apps. SAML2 is a standard; most SAML2 identity providers work with it.

IdP-initiated vs SP-initiated SSO

  • IdP-initiated: The user starts at the identity provider (e.g. Okta), opens your application there, and the IdP sends a SAML assertion to your app (the service provider).
  • SP-initiated: The user starts at your site (for example /accounts/login/), is redirected to the IdP to sign in, then returns with an assertion.

With CREATE_USER enabled, new users can be created in Django when they complete SSO, according to your attribute mapping and hooks and not only for IdP-initiated flows.

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.11.x, 3.12.x 4.2.x >=3.4.0 April 2026
    3.10.x, 3.11.x, 3.12.x, 3.13.x, 3.14.x 5.2.x (≥5.2.8) >3.12.0 April 2028
    3.12.x, 3.13.x, 3.14.x 6.0.x >3.12.0 April 2027

    Python 3.14 is supported with Django 5.2.8+ or Django 6.0 (5.2, 6.0). Django 4.2 does not support Python 3.14. Django 6.0 supports Python 3.12, 3.13, and 3.14 only; use Django 5.2 for Python 3.10 or 3.11.

  • Release notes are on GitHub Releases.

  • To contribute, read the contributing guide. To report security issues privately, see SECURITY.md.

CycloneDX SBOM

Each release includes a CycloneDX SBOM (JSON) as a workflow artifact, built with cyclonedx-python in .github/workflows/release.yml.

Installation

Install from PyPI:

pip install django-saml2-auth-community

Or install from a Git clone (use a virtual environment):

git clone https://github.com/mostafa/django-saml2-auth
cd django-saml2-auth
pip install .

pysaml2 also needs xmlsec on the system:

# RPM-based (e.g. yum/dnf)
# yum install xmlsec1

# Debian/Ubuntu
# apt-get install xmlsec1

# macOS (Homebrew)
# brew install xmlsec1

Windows binaries are also available.

Usage

  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)
    # To set the after-login redirect, use "?next=/the/path/you/want" on this view.
    re_path(r'^accounts/login/$', django_saml2_auth.views.signin),
    
    # The following line will replace the admin login with SAML2 (optional)
    # To set the after-login redirect, use "?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.

    Click to see the entire settings block
    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',  # Redirect after login. If omitted, defaults to the Django admin index. Overridden by ?next= on 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',  # Required in the map unless TOKEN_REQUIRED is False
            'groups': 'Groups',  # Optional
        },
        'GROUPS_MAP': {  # Optionally allow mapping SAML2 Groups to Django Groups
            'SAML Group Name': 'Django Group Name',
        },
        'TRIGGER': {
            'EXTRACT_USER_IDENTITY': 'path.to.your.extract.user.identity.hook.method',
            # 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',
            # This will override ASSERTION_URL to allow more dynamic assertion URLs
            'GET_CUSTOM_ASSERTION_URL': 'path.to.your.get.custom.assertion.url.hook.method',
            # This will override FRONTEND_URL for more dynamic URLs
            'GET_CUSTOM_FRONTEND_URL': 'path.to.your.get.custom.frontend.url.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': 'urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress',  # Match your IdP / NameID policy
        '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 for asymmetric JWT; use an RS* or ES* algorithm
        '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,  # Match USERNAME_FIELD with exact case when True
        '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
        'FORCE_AUTHN': False, # Forces the user to re-authenticate with each authentication request
        'ACCEPTED_TIME_DIFF': None,  # Accepted time difference between your server and the Identity Provider
        # Hostnames only (no `https://` prefix), per Django's url_has_allowed_host_and_scheme.
        # Used for `?next` (signin), post-login ACS redirects (including RelayState), and USE_JWT targets.
        'ALLOWED_REDIRECT_HOSTS': ["myfrontendclient.com"],
        'TOKEN_REQUIRED': True,  # Whether or not to require the token parameter in the SAML assertion
        'DISABLE_EXCEPTION_HANDLER': True,  # Whether the custom exception handler should be used
    }
    
  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/

Debugging

To inspect SAML traffic between the identity provider and Django, use SAML-tracer for Firefox or Chrome.

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 settings control this module; others are forwarded to pysaml2. See the pysaml2 configuration docs for the full set—not every pysaml2 option is wired through here.

Click to see the module settings
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 path. Required when AUTHN_REQUESTS_SIGNED is True. 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 Post-login redirect if ?next= is not used. If unset, resolves to the Django admin index (admin:index). str None (→ 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 Optional hooks (dotted paths to callables) for login and user provisioning. Arguments vary by hook; see each row below. dict {}
TRIGGER.EXTRACT_USER_IDENTITY Called when extracting the user identity from the SAML2 response. Should accept (user_dict, authn_response) and may return an enriched user_dict. str None my_app.models.users.extract_user_identity
TRIGGER.GET_USER Custom lookup for an existing user before login. Should accept the user dict and return a User instance or None. str None my_app.models.users.get
TRIGGER.CREATE_USER Called after a new user row is created, before login. Accepts the 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 as dictionary, where the key is "url" and the value is the corresponding metadata Autoconf URL. (e.g., [{"url": METADATA_URL1}, {"url": METADATA_URL2}]). 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.GET_CUSTOM_METADATA Custom SAML metadata loader. Should return metadata as Mapping[str, Any] and overrides other metadata settings. Same arguments as django_saml2_auth.saml.get_metadata (user_id, domain, saml_response). See tests.test_saml.get_custom_metadata_example. str None my_app.utils.get_custom_saml_metadata
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
TRIGGER.GET_CUSTOM_ASSERTION_URL A hook function to get the assertion URL dynamically. Useful when you have dynamic routing, multi-tenant setup and etc. Overrides ASSERTION_URL. str None my_app.utils.get_custom_assertion_url
TRIGGER.GET_CUSTOM_FRONTEND_URL Dynamic FRONTEND_URL when using JWT (overrides FRONTEND_URL). Accepts relay_state. str None my_app.utils.get_custom_frontend_url
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. This will only allow to customize the domain part of the URL, for more customization use GET_CUSTOM_ASSERTION_URL. str None https://example.com
ENTITY_ID Optional entity ID for the Issuer element in the authentication request, if required by the IdP. str None https://example.com/sso/acs/
NAME_ID_FORMAT The optional value of the 'Format' property of the 'NameIDPolicy' element in authentication requests. str None urn:oasis:names:tc:SAML:2.0:nameid-format:persistent
USE_JWT Set this to the boolean True if you are using Django with JWT authentication bool False
JWT_ALGORITHM Algorithm for signing JWTs (PyJWT algorithms). Must match JWT_SECRET (HMAC) or key pair (RSA/EC). str None HS256, RS256
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 for asymmetric JWT signing (RS*, ES*, etc.). str or bytes None
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 When USE_JWT is True, redirect target for the browser after SSO; JWT is appended as ?token=.... Defaults to DEFAULT_NEXT_URL / admin index if unset. Example: https://app.example.com/?token=<jwt>. str None (→ same as default next URL) https://app.example.com/
AUTHN_REQUESTS_SIGNED Set this to False if your provider doesn't sign each authorization request. KEY_FILE is required if this is set True. 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
FORCE_AUTHN SAML2 request attribute that forces the user to re-authenticate with the Identity Provider (IdP), even if they already have an active session. bool False
ACCEPTED_TIME_DIFF Sets the accepted time diff in seconds int or None None
ALLOWED_REDIRECT_HOSTS Hostnames allowed for ?next= and post-login redirects (no scheme; see Django’s url_has_allowed_host_and_scheme). list [] ['app.example.com', 'api.example.com']
DISABLE_EXCEPTION_HANDLER Set this to True if you want to disable the exception handler. Make sure to handle the SAMLAuthErrors and other exceptions. bool False

Triggers

Setting name Description Interface
GET_METADATA_AUTO_CONF_URLS Return candidate metadata URLs (see trigger implementation). 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))

Exception Handling

By default, errors render a built-in template. Customize templates in the next section, or set DISABLE_EXCEPTION_HANDLER to True to let SAMLAuthError (and other exceptions) propagate—useful for API-only integrations where you handle errors yourself.

Customize Error Messages and Templates

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

To override these pages, add templates named django_saml2_auth/error.html, django_saml2_auth/welcome.html, or django_saml2_auth/denied.html under your project’s template directories.

[!NOTE] If you set DISABLE_EXCEPTION_HANDLER to True, the custom error pages will not be displayed.

If a django_saml2_auth/welcome.html template exists, it is shown after login instead of redirecting to the previous page. The user object is available as user in the template context.

To use the bundled sign-out view, add these before your urlpatterns (use re_path on Django 4+):

from django.urls import re_path

import django_saml2_auth.views

# Optional: replace default logout with SAML2 sign-out
re_path(r"^accounts/logout/$", django_saml2_auth.views.signout),
re_path(r"^admin/logout/$", django_saml2_auth.views.signout),

Override sign-out UI with django_saml2_auth/signout.html if needed.

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

The METADATA_AUTO_CONF_URL for settings.py appears in the Okta admin UI under the SAML2 app Sign On tab. In Settings, look for:

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

Releases are mostly automated; you still configure GitHub once, then tag and push:

  1. In the GitHub repository, create an Actions environment named pypi (Settings → Environments) and store the PYPI_API_TOKEN secret there for PyPI uploads. The release workflow attaches the tag build job to this environment.
  2. Tag the main branch locally with vSEMVER, e.g. v3.9.0, and push the tag.
  3. Pushing the tag triggers the workflow, which will:
    1. run the linters and tests.
    2. build the binary and source package.
    3. publish the package to PyPI.
    4. create a new release with auto-generated release notes on the tag.
    5. upload the SBOM artifacts and build artifacts to the release.

Project details


Download files

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

Source Distribution

django_saml2_auth_community-3.21.0.tar.gz (157.3 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

django_saml2_auth_community-3.21.0-py3-none-any.whl (56.4 kB view details)

Uploaded Python 3

File details

Details for the file django_saml2_auth_community-3.21.0.tar.gz.

File metadata

File hashes

Hashes for django_saml2_auth_community-3.21.0.tar.gz
Algorithm Hash digest
SHA256 3afb5560a7931cc4a891327920b89cd5e337b4bb9e596041995dfdebcde07d03
MD5 4e3e06ddee2bdc49a05e66c62f6b9613
BLAKE2b-256 e8c6d41bbb9bb631f729ecb25d33c78281ff4e5e8278326dd1f3b0c557bae30d

See more details on using hashes here.

File details

Details for the file django_saml2_auth_community-3.21.0-py3-none-any.whl.

File metadata

File hashes

Hashes for django_saml2_auth_community-3.21.0-py3-none-any.whl
Algorithm Hash digest
SHA256 4765d3cd8e4a791438fd1277038819c9d1337b6a1bcaba20f5e353c77043faf3
MD5 d3fee645ff1844539c1d9c96d2034201
BLAKE2b-256 cab4c9ee7459ed1ebfc6f04681c793c341518f45e8edce21a2dd28d0983048f6

See more details on using hashes here.

Supported by

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