Quick first introduction to PySAML

Overview

PySAML2 is a powerful Python library designed to streamline the integration of the Security Assertion Markup Language (SAML) protocol into Python applications. As a widely used standard for exchanging authentication and authorization data between parties, SAML ensures secure and seamless user authentication across disparate systems.

In this quick tutorial we will look at the how to set up a simple SAML Service provider using PySAML to talk to mocksaml.dev as IdP.

Introduction

First lets quickly look into some of the basics of SAML and PySAML.

Quick recap on SAML

SAML serves as a foundation for enabling Single Sign-On (SSO) capabilities, allowing users to access multiple applications through a single set of credentials. It facilitates secure exchanges of authentication and authorization data between an identity provider (IdP) and a service provider (SP).

The primary components of a SAML transaction include:

  • Identity Provider (IdP): Manages user identities and authentication processes.
  • Service Provider (SP): Hosts the applications/services users want to access.

The SP requests a user to be authenticated by the IdP and get a Authentication Response back as a proof authentication.

How PySAML2 Simplifies Authentication

PySAML2 abstracts the complexities of SAML protocols, providing a Python-based framework to seamlessly integrate SAML-based authentication into applications. It empowers developers to:

  • Effortlessly Implement SAML: With PySAML2, developers can focus on application logic while the library handles the intricate SAML protocol details.
  • Enable Secure Authentication: Ensure secure and reliable authentication mechanisms, meeting the stringent security requirements of modern applications.
  • Facilitate Interoperability: PySAML2 adheres to the SAML standard, ensuring compatibility and interoperability across various identity providers and service providers.

Key Benefits of PySAML2

  • Robust Security Measures: PySAML2 incorporates robust security features, including encryption and digital signatures, to fortify authentication processes.
  • Flexible Integration: Seamlessly integrate SAML-based authentication into Flask, Django, and other Python frameworks.
  • Comprehensive Protocol Support: Support for SAML 2.0 and its various profiles, enabling comprehensive SSO capabilities.

Digging in

The PySAML sample below is a minimal sample of a PySAML usage, with all the shortcuts, just to get PySAML up andd running as simple as possible so showcase it. For example

  • No signatures are done or required
  • Unsolicited responses are allowed
  • No encryption or anything

All of this can pose security problems and should be addressed in anything used for more than a demo like this. Those topics is or will be covered in the rest of my series on PySAML.

To setup this small sample we will look att three parts of the typical PySAML application

  • Service configuration: the configuration defining the service PySAML is running. In this case the SP.
  • Metadata: The metadata of the part we are talking to. In this case the mocksaml.dev IdP
  • The python application code using PySAML

Now PySAML it self does not itself handle any redirection of use or receiving of responses. It just does the base work of SAML. To make this a bit more of a complete sample I use flask to be the web server and handle this.

Prerequisites

Before digging in to PySAML2 into your Flask application, ensure you have the following prerequisites installed:

Flask Framework: PySAML2 integrates seamlessly with Flask. Install Flask by running pip install flask. xmlsec1 Library: For cryptographic operations, install xmlsec1 based on your system requirements. On Ubuntu/Debian, use sudo apt-get install xmlsec1. PySAML itself: Install by running pip install pysaml2

Service configuration

The first part we will look at here is the service configuration

 1from saml2.config import Config
 2from saml2 import config as saml_config
 3from saml2.saml import NAMEID_FORMAT_UNSPECIFIED
 4
 5# SP Configuration
 6SP_CONFIG = {
 7    "entityid": "urn:example:sp",
 8    "service": {
 9        "sp": {
10            "want_response_signed": False,
11            "want_assertions_signed": False,
12            "name_id_format": NAMEID_FORMAT_UNSPECIFIED,
13            "endpoints": {
14                "assertion_consumer_service": [
15                    ("http://localhost:8000/acs", saml_config.BINDING_HTTP_POST)
16                ]
17            },
18            "allow_unsolicited": True,
19            "require_signed_assertion": False,
20            "require_signed_response": False 
21        }
22        
23    },
24    "metadata": {
25        "local": ["metadata.xml"]    
26    }
27}

The service configuration above shows a simple example of the configuration of the service run by PySAML. For simplicity and demoing we say that we don't want or require signing.

1"service": {
2        "sp": {
3            "want_response_signed": False,
4            "want_assertions_signed": False,
5...
6            "require_signed_assertion": False,
7            "require_signed_response": False, 

We also defines that it is ok with unsolicited responses.

1"allow_unsolicited": True

The endpoint definition tells the IdP where to send the resulting authentication response

1"endpoints": {
2    "assertion_consumer_service": [
3        ("http://localhost:8000/acs", saml_config.BINDING_HTTP_POST)
4    ]
5}

and we refer to our IdP Metadata in the metadata.xml explained in the next section.

1"metadata": {
2    "local": ["metadata.xml"]
3}

IdP Metadata

Next lets look at the IdP metadata describing the mocksaml IdP

1<?xml version="1.0"?>
2<md:EntityDescriptor xmlns:md="urn:oasis:names:tc:SAML:2.0:metadata" cacheDuration="PT1701351683S" entityID="samlmock.dev">
3  <md:IDPSSODescriptor WantAuthnRequestsSigned="false" protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
4    <md:NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified</md:NameIDFormat>
5    <md:SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" Location="https://samlmock.dev/idp"/>
6  </md:IDPSSODescriptor>
7</md:EntityDescriptor>

The XML above shows a typical example of a simple metadata for a IdP. The IdP, in this case accepts authentication requests that are not signed, do for simplicity's sake we use a metadata without keys and with the attribute

1WantAuthnRequestsSigned="false"
, that states that the IdP does not want it singed.

Other than that it mainly says that the binding HTTP Redirect should be used to send the authentication request.

Application code

Now finally lets look at the application code doing the lifting

1from sp_config import SP_CONFIG
2
3config = saml_config.Config()
4config.load(SP_CONFIG)
5app = Flask(__name__)
6sp = Saml2Client(config=config)
Some basic initiation. We loud our service configuration in SP_CONFIG and initiate a SAML2 client from it. Also initiate the flask app

1@app.route('/login')
2def login():
3    request_id, authn_request = sp.prepare_for_authenticate()
4    headers = dict(authn_request['headers'])
5    return redirect(headers['Location'])
A simple endpoint in flask that creates the authentication response and then redirects the user to the IdP along with it.

1@app.route('/acs', methods=['POST'])
2def acs():
3    authn_response = sp.parse_authn_request_response(request.form['SAMLResponse'], saml_config.BINDING_HTTP_POST)
4    print(authn_response)
5    return redirect('/success' if authn_response else '/error')
A flask endpoint acting as the Assertion Consumer Service, receiving the Authentication Response from IdP, when the user is sent back. The SAML Response is send as a form parameter in a POST request. The SAML response is printed to the console The user is then redirected to the success page

1@app.route('/success', methods=['GET']) 
2def success():
3    return "<h1>Authentication successful!</h1>"

The final success page indicating that the user was successfully authenticated.

Get it on Github and try it out!

The full sample code is available on Github at https://github.com/rasmusson/pysaml-samples under intro/

Just clone it, run it, go nuts!

1apt-get install xmlsec1
2pip install flask
3pip install pysaml2
4
5git clone https://github.com/rasmusson/pysaml-samples.git
6cd intro
7
8python sp_server.py