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"
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)
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'])
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')
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