Photo by William Iven
on Unsplash
Want to enable SAML 2.0 SSO authentication in your Elixir/Phoenix application?
It is fairly easy to do using the Samly
Elixir library.
Samly
is known to work with
SimpleSAMLphp
,
Shibboleth
. It has been tried with a few
cloud based commercial SAML providers as well.
Let us get a bit of terminology clarified up front. Your Phoenix/Plug-based application will be known as the “SAML Service Provider” (SAML SP) or “Relying Party” (RP) that relies on a SAML Identity Provider (SAML IdP) for authentication. This IdP in turn uses an authoritative source such as LDAP for user authentication.
The IdP publishes the HTTP endpoints to be used for communications. The endpoint information along with the certificate are typically made available in an XML file. This XML should be registered with or provided as configuration in SP. This is how SP knows how to communicate with IdP. Similarly, SP publishes the endpoints and certificate that IdP can use for communication. The “SP/RP registration” process used by IdPs differs from vendor to vendor.
When the end user successfully authenticates and consents to provide the user attributes, IdP sends the user attributes as part of SAML Assertion in its response to SP (sent as a redirect or POST).
With that out of the way, let us move on to the mechanics.
You need an IdP to talk to - be it one of the cloud SAML providers or your corporate SAML provider. Fear not if you don’t have one. We will cover how to setup a Docker based Shibboleth SAML Identity Provider. This is the first part of this post.
If you already have an IdP to talk to, consider yourself really fortunate and skip to the second part. The primary purpose of the second part is to make sure that your get the configuration settings right to talk to your IdP without writing your own code.
Once you have the right configuration settings, move onto incorporating
Samly
into your own Phoenix/Plug based application.
Skip this part if you already have a SAML IdP to work with.
We are going to use a Shibboleth Docker image for our IdP. This IdP will use an OpenLDAP Docker container for the user repository. Let us get started by cloning the following Github repo.
git clone https://github.com/handnot2/samly_shibboleth
cd samly_shibboleth
Let us setup an LDAP instance for use with the IdP. We need to seed this
LDAP instance with an organization and a set of users. The ldif
directory contains a file with the seed data. Before proceeding further edit
the ldif/add_users.ldif
file if you want to change/add user records
(simply copy the existing user records and make the needed changes).
These entries in the ldif file will be automatically seeded
when the OpenLDAP Docker container is started using the following script:
sudo bin/start_shibb_ldap.sh
The OpenLDAP Docker container persists its data in samly_ldap_dv
directory.
The Docker container this script starts is named samly_ldap
. Use this name
when working with the Docker CLI.
The Shibboleth setup we are going to perform is built using the Docker Hub Image unicon/shibboleth-idp. Use the following script to setup a mostly pre-wired Shibboleth configuration.
sudo bin/1_setup_shibb_idp.sh
This command shows a sequence of prompts expecting input. Simply accept the defaults recommended within square brackets with Enter/Return key. When prompted, enter passwords for “backchannel” and “cookie encryption”. Make sure to note down the backchannel PKCS12 password.
Here is what the console output from this command looks like:
Please complete the following for your IdP environment:
Hostname: [shibb.idp]
Attribute Scope: [idp]
SAML EntityID: [https://shibb.idp/idp/shibboleth]
Generating Signing Key, CN = shibb.idp URI = https://shibb.idp/idp/shibboleth ...
...done
Creating Encryption Key, CN = shibb.idp URI = https://shibb.idp/idp/shibboleth ...
...done
Backchannel PKCS12 Password:
Re-enter password:
Creating Backchannel keystore, CN = shibb.idp URI = https://shibb.idp/idp/shibboleth ...
...done
Cookie Encryption Key Password:
Re-enter password:
Creating cookie encryption key files...
...done
Rebuilding /opt/shibboleth-idp/war/idp.war ...
...done
BUILD SUCCESSFUL
Total time: 23 seconds
A basic Shibboleth IdP config and UI has been copied to ./customized-shibboleth-idp/ (assuming the default volume mapping was used).
Most files, if not being customized can be removed from what was exported/the local Docker image and baseline files will be used.
Generating Front Channel Certificate ...
Generating a 4096 bit RSA private key
....................................................................................................................................................................................................................................................................................................................++
.........................++
writing new private key to 'customized-shibboleth-idp/credentials/jetty.key'
-----
Exporting Front Channel Key ...
Done
Set Backchannel PKCS12 password in:
./customized-shibboleth-idp/ext-conf/idp-secrets.properties
Should match what was provided earlier.
Edit the
idp-secrets.properties
file mentioned above in the console output to set the backchannel PKCS12 password. (Usesudo
)
The Shibboleth Identity Provider is mostly ready. We should now be able to build the IdP Docker image and start the Identity Provider.
sudo bin/2_build_image.sh
sudo bin/3_start_shibb_idp.sh
The “start” script above creates a docker container by the name
samly_shibb_idp
.Confirm that IdP is successfully started by checking the logs. It takes a little while to start the Shibboleth Identity Provider.
sudo docker logs samly_shibb_idp
# IdP is up and running when you see the following message in the logs
....
....
... INFO:oejs.Server:main: Started @15880ms
We need to get a copy of the IdP Metadata XML file from this setup
that we can use to configure SAML SP (Samly
enabled Phoenix application).
We need to be able to reach the Shibboleth IdP by the hostname “
shibb.idp
”.Make sure to add
127.0.0.1 shibb.idp
to your/etc/hosts
file. (Check if you are able to “ping shibb.idp
”.)
wget --no-check-certificate -O idp_metadata.xml https://shibb.idp/idp/shibboleth
The Single Logout endpoint information in this
idp_metadata.xml
is commented out by default. Open this file in an editor, search forSingleLogoutService
, uncomment that block and save it back.
The IdP setup is complete except for the SAML SP registration. We need to get the metadata from SAML SP to complete this step. Instructions for this are provided in the next part.
The configuration that is! As mentioned before, the demo Phoenix application
samly_howto
serves two purposes. It is a showcase on how to use the Samly
Elixir library. It is also useful to make sure that you get the SAML SP
configuration right when talking to the IdP.
Follow these instructions to clone the demo application repo, generate
the keys and cert needed for Samly
and fetch the npm
packages needed
for the web UI.
# clone this at the same level as samly_shibboleth and NOT as s sub-directory
cd ..
git clone https://github.com/handnot2/samly_howto
cd samly_howto
./gencert.sh
mix deps.get
mix compile
cd assets && npm install && cd ..
We are going to use “
samly.howto
” to access this demo application. Make sure to add127.0.0.1 samly.howto
to your/etc/hosts
file.
We need to provide the SAML Identity Provider metadata information to samly_howto
.
Do this by copying the idp_metadata.xml
file that we obtained earlier
into the samly_howto
directory. This metadata file has the SAML endpoint URLs
as well as the certificate needed to verify the SAML responses from Idp.
cp ../samly_shibboleth/idp_metadata.xml .
The last step is to register samly_howto
SAML SP with the SAML IdP.
Start the demo application and use wget
command to get the SAML SP
metadata XML file with the following commands:
./runit.sh
Now open another terminal and follow the rest of the instructions in this part using that terminal.
cd path/to/samly_howto
wget --no-check-certificate -O sp_metadata.xml \
https://samly.howto:4443/sso/sp/metadata/idp1
The instructions for registering the SAML SP are very specific to IdPs. If you are working with an already available SAML IdP installation, follow the instructions from that vendor. All information you need for the registration are available in
sp_metadata.xml
file.
If you used instructions in Part One to setup your own Shibboleth based SAML IdP,
copy the sp_metadata.xml
over to the IdP, regenerate the IdP Docker image
and restart the IdP.
sudo cp sp_metadata.xml ../samly_shibboleth/customized-shibboleth-idp/metadata/
cd ../samly_shibboleth
sudo bin/4_stop_shibb_idp.sh
sudo bin/2_build_image.sh
sudo bin/3_start_shibb_idp.sh
Watch the docker container logs to make sure that the IdP startup is complete. With that the Shibboleth IdP setup is now complete!
Well, the IdP knows about SP and SP knows about IdP by now. Let us try out the SAML authentication! Open a browser window and enter the following URL:
You will have to temporarily trust the self-signed certificates that were generated during the installation of both the IdP and SP.
You should see a page with a Sign in button. Clicking on this button
takes you through Shibboleth Identity Provider Login screen. Upon successful
login, you are prompted for your consent to release the authenticated user
attributes to the samly_howto
Phoenix application. Pick a consent choice
and click on the Accept button.
Login with the uid and password of one of the users in the ldif file. For example, you can login as
wflintstone
withchangeme
as the password. If you had changed the defaults, use your modified values.
Upon successful authentication and attribute release consent, you will be redirected back to the demo application that shows the user attributes available in the authenticated SAML Assertion.
Try out the Sign out button as well to complete the logout process.
There is a handy SAML Tracer browser plugin that is very helpful to view the SAML requests and responses between SP and IdP. Google it.
Make sure to check out the Samly
documentation on various configuration choices
it offers. Sometimes, you run into issues talking to SAML providers because those
providers do not sign their requests by default. Samly
by default signs its
requests and expects signed responses. You can either make sure that signing
is enabled at IdP or turn it off at Samly
.
Congratulations! You have a working SAML authentication setup now. The
Samly
related config settings in theconfig/dev.exs
file is what you need in your own Phoenix application. This takes us to the third part.
Having gotten this far, you should not have any major surprises. Most of what is
here is covered in Samly
documentation. Start with dependency related changes
to your mix.exs
and update the dependencies.
# mix.exs
defp deps() do
[
# ...
{:samly, "~> 0.8"}, # Check hex.pm for the latest version number
]
end
The next step is to add the Samly Provider to your application supervision tree.
This is done in your application.ex
file:
# application.ex
children = [
# ...
worker(Samly.Provider, []),
]
Routing comes after this. Make the following changes to setup the SAML related
routes in your application. Do this is by literally copying the following
to your router.ex
file:
# router.ex
# Add the following scope ahead of other routes
# Keep this as a top-level scope and **do not** add
# any plugs or pipelines explicitly to this scope.
scope "/sso" do
forward "/", Samly.Router
end
Samly
needs a key and certificate that it can use to sign the requests it sends
to IdP. You can use openssl
for this or take advantage of the gencert.sh
from
Part 2.
Now bring in the Samly
related configuration from Part 2 to your application,
making accommodations for any name changes.
Follow the instructions in Part Two on how to fetch the SP Metadata XML file and register with the IdP. Also copy over the
idp_metadata.xml
and make it available to your application.
Check out the Samly
documentation. It explains the routing URLs. You can also
checkout the samly_howto
demo application as your reference.
Samly
provides an API to get the SAML Assertion
Samly.get_active_assertion
. This function returns
%Samly.Assertion{}
if the user is authenticated and nil
otherwise. Use this
call in an Elixir Plug to check if the user is authenticated or not and redirect
to the SP signing URL appropriately. This Plug should be part of the pipeline
that protects your routes.
Be sure to checkout the customization section of the
Samly
documentation. It covers how you can use a Plug pipeline to transform the attributes in the SAML assertion. You can also use this pipeline mechanism to handle Just-In-Time user creation in your own application.
Comments and Discussion | https://elixirforum.com/t/samly-add-saml-sso-to-your-phoenix-application-now-with-multiple-identity-provider-support/8511 |
Documentation | https://hexdocs.pm/samly/readme.html |
Samly Repo | https://github.com/handnot2/samly |
Samly Demo Application | https://github.com/handnot2/samly_howto |
Shibboleth 3 | https://wiki.shibboleth.net/confluence/display/IDP30/Home |
Self-hosted Shibboleth Idp | https://github.com/handnot2/samly_shibboleth |
SimpleSAMLphp | https://simplesamlphp.org/ |
Self-hosted SimpleSAMLphp Idp | https://github.com/handnot2/samly_simplesaml |
Dockerized OpenLDAP | https://hub.docker.com/r/osixia/openldap |