An Easy way to Support ADFS / AZURE / OKTA / Sibboleth and other SAML2 SSO Scenarios

120 views
Skip to first unread message

Pbop

unread,
Dec 8, 2018, 10:37:13 AM12/8/18
to web2py-users
Greetings Fellow Web2Pyers,

It's the season of giving. I hope what I share inspires others to share some of their tips and tricks in Web2Py that others can use! Many thanks to the community for your great help in the past! 

This post assumes the reader has limited exposure to SAML2. The solution here allows any Web2Py app hosted on servers you control to work with any SAML2 IDP (in theory). 

SAML2 is a mark-up language to support federated single-sign services which include Microsoft ADFS and AZURE, Shibboleth, OKTA to name a few. What's cool with federated SSO is your web2py app can support SSO with any of these services with only registration information needing to be shared. Conversely, when you authenticate into any of these providers, you gain access to any other application with the same credentials also registered into the provider. 

For those not familiar with how SAML2 works, there are two pieces of technology needed: an IDP (identity provider) and the SP (service provider).  Microsoft refers to the IDP as Claims Provider and SP as Relaying Party. The IDP is where the user authenticates and contains identity information about the user and what applications the user is allowed to access. The SP is the application (your Web2Py app)  that wants to use the IDP for sign-on services. When the user lands to the SP and is not authenticated, the user is redirected to the IDP. The IDP will present the user with a login form which means the user is authenticating into the IDP, not directly into your application. On submission of credentials, the IDP completes the sign-on process and redirects the authenticated and authorized user back to the SP (your application). When redirecting the user back to the SP, the post back includes whatever identity information the IDP is authorized to release to the SP such as first and last name, email, organization... This is different than CAS or AD which returns only a login name or unique identifier. The IDP can release any information it has about the user which means your app gains access to both authentication and identity management services.  

While there are a number of python based SAML2 implementations including a 5 year old web2py version, it gets fairly deep into details that can be entirely avoided with what I am about to share. 

Shibboleth is a SAML2 implementation you can use to make your Web2Py app SAML2 ready immediately. Shibboleth is used mostly in higher education and includes both IDP and SP software installations. Both installations are open source downloads that you can install to a web server (Unix and IIS), but we're only interested in the service provider installation. The service provider when installed to your server can protect a folder, including a web2py application/controller/function folder. By protecting the folder your web2py app is running against, you instantly gain SAML2 capability and out of the box support to any SAML2 IDP. This is because all of the identity attributes are now available in value pairs in the header (the web2py request.env object) once the user is authenticated. 

To show how easy this is, let's say the folder we want the Shibboleth service provider to protect is welcome\secure where secure is your controller in the welcome app using a default function. When the user lands to that folder, the Shibboleth SP kicks in and redirects the user to the IDP. Your web2py app will not even respond until Shibboleth has authenticated you. The user logs on at the IDP, the IDP determines the user is authenticated and then redirects back to the protected folder. Since Shibboleth has determined you are now authorized to use the folder, your web2py app fires and all of the identity attributes are now available for your web2py application in the request.env object to use as you need. 

Here is a set of headers from a Shibboleth authentication... This represents what the IDP is releasing back to the SP and in turn represent header variables available to your web2py app.  

http_cn : Joe Shmoe
http_officialemail : jsh...@schmoeland.com
http_uclalabasuuid : 202e96f3-919f-479e-80e1-9a03f2416b9d
http_uid : f0007939

For your web2Py app to use this data it is simple variable assignments...

# Load Shibboleth Attributes
data = request.env
ucla = Storage()   
ucla.university_id = data['http_uid']
ucla.email = data['http_officialemail']
        ...
 
# Onward... 

What identity attributes (header variables) are returned are a function of the Shibboleth SP configuration with whatever IDP you are using and what your app needs. Shibboleth handles producing the metadata the IDP needs, login and logout services, offers comprehensive logging and has an active community. To make your app SAML2 ready, you register the path in the Shibboleth configuration file. You can get started with this approach at www.testshib.org

Pay it forward! 


Massimo Di Pierro

unread,
Dec 30, 2018, 4:51:15 PM12/30/18
to web2py-users
Thank you!

Can you please post a more complete example of integration? This will be very useful to many people here.

黄祥

unread,
Dec 30, 2018, 8:12:54 PM12/30/18
to web2py-users
+1
interested to learn it too

thx n best regards,
stifan

Pbop

unread,
Dec 31, 2018, 5:53:12 PM12/31/18
to web2py-users
The key thing to understand when using the Shibboleth SP is that you are protecting a virtual folder. When you land on that folder, which can also be a web2py route, the SP checks to see if you are authenticated and if not sends you to the IDP. Only once you are authenticated does the end point load. Once authenticated, exposed in the header is identity information about the user. It is sort of like Active Directory where once authenticated with AD, the header contains the users login id. The difference is the identify information in the header is defined by what identity attributes the IDP wants to release to the SP and can contain any user identity information including login id, employee  number, first name, last name, email, department, shoe size... really whatever identity information the IDP provides and which it wants to release to the SP. 

In the example below, I am getting a variety of header fields that I know the SP is getting. An API call is made to another function that looks the user up in an external system and if found redirects the user into that system with signed url. If not found, the identity information is used to add the person on the fly also to that external system which then lands the user into their home page. Either way, our Web2Py app is used very narrowly to handle the results of authentication pulling the needed information to route the user into this external application either as an existing user or as a new user. Truth be told, any technology that can read the browser header can be used, but in Web2Py it is ridiculously easy.  

To SAMLize your web2py app go to https://wiki.shibboleth.net/confluence/display/SHIB2/Installation and download and install the SP to the system of your choice. 

One last thing, testshib.org is no more, and they talk about docker containers to download working environments; however, it looks like this site is able to do the same thing: https://samltest.id/

I am not sure how much the code snippet will help but here you go. Happy New Year All! 

'''
Shibboleth Login Functionality
'''

from gluon.storage import Storage
import datetime, requests, json, traceback

def start():
        
# Load Shibboleth Attributes
data = request.env
ISU = Storage()
        
# ISU.university_id = data['HTTP_ISUUNIVERSITYID']
ISU.university_id = data['HTTP_SHIBISUUNIVERSITYID']
ISU.email = data['HTTP_SHIBISUOFFICIALEMAIL']
ISU.name = data['HTTP_SHIBSN']
ISU.firstname = data['HTTP_SHIBGIVENNAME']
ISU.shib_id = data['HTTP_SHIBISULOGONID']
#ISU.university_id = 'foobar'

if (ISU.university_id == '' or ISU.university_id == None) :
    ISU.university_id = ''
# Check if we should view debug
if lweb.enable_debug == True:
response.view = 'login/start.html'
else:
response.view = 'login/redirect.html'
error = None
# Check if we have the university id
if (ISU.university_id == '' or ISU.university_id == None) and (ISU.shib_id == '' or  ISU.shib_id == None) :
error = 'No Darn University Id found'
return locals()
# Load the client state from ISU
lwebInfo = None
try:
lwebInfo = Storage(getState(ISU.university_id))
except:
error = 'Error loading Remote State from AbilityLMS:<br />%s' % traceback.format_exc().replace("\n", '<br />')
return locals()
# Check for errors from LWEB api
if lwebInfo != None:
if lwebInfo.hasError == True:
error = 'Error from AbilityLMS:<br />%s' % lwebInfo.stateObj['debug']
return locals()
elif lwebInfo.stateObj['Result'] == 'INVALIDDATE':
error = 'Invalid Date sent to AbilityLMS API: %s' % lwebInfo.stateObj['URL'].split('!~Redhead!~')[1]
return locals()
elif lwebInfo.stateObj['Result'] == 'INVALIDLEARNER':
xLearner_LoginID = 'Learner_LoginID=' + ISU.university_id
xLearner_EmailAddress = '&Learner_EmailAddress=' + ISU.email
xLearner_FirstName = '&Learner_FirstName=' + ISU.firstname
xLearner_LastName = '&Learner_LastName=' + ISU.name
xLearner_ShibLoginID = '&Shib_LoginID=' + ISU.shib_id
xLoginURL = lweb.AbilityLMS_URL + '/Programs/Custom/Control/ISU_Register.wml?' + xLearner_LoginID + xLearner_EmailAddress + xLearner_LastName + xLearner_ShibLoginID + xLearner_FirstName
redirect(xLoginURL + '&remoteST=%s' % lwebInfo.stateObj['ClientState'])

elif lwebInfo.stateObj['Result'] == 'SUCCESS':
redirect(lweb.AbilityLMS_URL + lweb.landing_path + '?remoteST=%s' % lwebInfo.stateObj['ClientState'])
else:
error = 'Unkown Error:<br />%s' % BEAUTIFY(lwebInfo)
return locals()
return locals()

黄祥

unread,
Dec 31, 2018, 10:33:36 PM12/31/18
to web2py-users
cant access http://testshib.org

not succeed
docker pull debian
docker run
-it debian /bin/bash

apt update
apt install
-y default-jdk curl unzip
export JAVA_HOME=/usr/lib/jvm/java-8-openjdk-amd64/jre/
curl
-L -O -C - http://shibboleth.net/downloads/identity-provider/latest/shibboleth-identity-provider-3.4.2.zip
unzip shibboleth
-identity-provider-*.zip
shibboleth
-identity-provider-*/bin/install.sh
java
-jar /opt/shibboleth-idp/war/idp.war

no main manifest attribute, in /opt/shibboleth-idp/war/idp.war

not succeed
docker pull centos
docker run
-it centos /bin/bash

cat
<< EOF > /etc/yum.repos.d/shibboleth.repo
[shibboleth]
name
=Shibboleth (CentOS_7)
# Please report any problems to https://issues.shibboleth.net
type
=rpm-md
mirrorlist
=https://shibboleth.net/cgi-bin/mirrorlist.cgi/CentOS_7
gpgcheck
=1
gpgkey
=https://shibboleth.net/downloads/service-provider/RPMS/repomd.xml.key
enabled
=1
EOF
yum install
-y shibboleth
/sbin/service shibd start

Redirecting to /bin/systemctl start shibd.service
Failed to get D-Bus connection: Operation not permitted

best regards,
stifan

Pbop

unread,
Jan 1, 2019, 4:24:29 PM1/1/19
to web2py-users
Should be http://www.testshib.org/

Once your SP is installed test it against the IDP at... https://samltest.id/start-sp-test/

Or set up a test account with OKTA which has a free IDP account you can create. Pretty sure the same is true for Azure, but that was provided to me by the client. Once you get over the hump of the details is setting up the SP and all the options that go into the shibboleth XML file, we have not seen yet an IDP that is not pretty strait forward and we have done integrations against Shibboleth, ADFS, Azurre, OKTA and home grown SAML2 IDPs. 
Reply all
Reply to author
Forward
0 new messages