NAME
Plack::Auth::SSO::Shibboleth - implementation of Plack::Auth::SSO for Shibboleth
SYNOPSIS
DESCRIPTION
This is an implementation of Plack::Auth::SSO to authenticate behind a Shibboleth Service Provider (SP)
It inherits all configuration options from its parent.
CONFIG
- error_path
-
This option is inherited by its parent class Plack::Auth::SSO, but cannot be used unfortunately
because an SP will never allow an invalid request to be passed to the backend. This should be configured in
/etc/shibboleth/shibboleth2.xml ( cf. https://wiki.shibboleth.net/confluence/display/SHIB2/NativeSPErrors ).
- request_type
-
* "env": Shibboleth SP sends attributes using environment variables (CGI and FCGI)
* "header": Shibboleth SP sends attributes using headers (proxy)
Default is "env"
- shib_session_id_field
-
Field where Shibboleth SP stores the session id.
Default is "Shib-Session-ID"
- shib_application_id_field
-
Field where Shibboleth SP stores the application id.
Default is "Shib-Application-ID"
- uid_field
-
Field to be used as uid
Default is "eppn"
- info_fields
-
Fields to be extracted from the environment/headers
auth_sso output
{
package => "Plack::Auth::SSO::Shibboleth",
package_id => "Plack::Auth::SSO::Shibboleth",
#configured by "uid_field"
uid => "<unique-identifier>",
#configured by "info_fields". Empty otherwise
info => {
attr1 => "attr1",
attr2 => "attr2"
},
#Shibboleth headers/environment variables
extra => {
"Shib-Session-Id" => "..",
"Shib-Application-Id" => "..",
"Shib-Identity-Provider" => "https://path.to/shibboleth./idp",
"Shib-Authentication-Instant" => "",
"Shib-Authentication-Method" => "POST",
"Shib-AuthnContext-Class" => "..",
"Shib-AuthnContext-Decl" => "..",
"Shib-Handler" => "..",
"Shib-Session-Index" => ".."
"Shib-Cookie-Name" => ".."
},
#We cannot access the original SAML response, so we rely on the headers/environment
response => {
content_type => "application/json",
content => "<headers/environment serialized as json>"
}
}
GLOBAL SETUP
This module does not do what it claims to do: authenticating the user by communicating with an external service.
The real authenticating module lives inside the Apache web server, and is called "mod_shib".
That module intercepts all requests to a specific path (e.g. "/auth/shibboleth"), authenticates the user, and, when done, sends the requests to the backend application. As long as a Shibboleth session exists in mod_shib, the request passes through.
That backend application merely receives the end result of the authentication: a list of attributes. The original SAML response from the Shibboleth Identity Provider is not sent.
There are two ways to transfer the attributes from mod_shib to the application:
* the application lives inside Apache (CGI, FCGI). The attributes are sent as environment variables. This is the default situation, and the most secure.
* the application is a separate server, and Apache merely a proxy server. The attributes are sent as headers. This is less secure.
cf. <https://wiki.shibboleth.net/confluence/display/SHIB2/NativeSPAttributeAccess>
This module merely convert these attributes.
SETUP BEHIND PROXY
- plack application
-
use strict; use Data::Util qw(:check); use Plack::Auth::SSO::Shibboleth; use Plack::Builder; use Plack::Session; use JSON; my $uri_base = "https://example.org"; builder { enable "Session", #mod_shib should intercept all requests to this path mount "/auth/shibboleth" => Plack::Auth::SSO::Shibboleth->new( uri_base => $uri_base, authorization_path => "/authorize", uid_field => "uid", request_type => "header", info_fields => [qw(mail organizational-unit-name givenname sn unscoped-affiliation entitlement persistent-id)] )->to_app(); mount "/authorize" => sub { my $env = shift; my $session = Plack::Session->new($env); #already logged in. What are you doing here? if ( is_hash_ref( $session->get("user") ) ) { return [ 302, [ Location => "${uri_base}/authorized" ], [] ]; } my $auth_sso = $session->get("auth_sso"); #not authenticated yet unless($auth_sso){ return [ 302, [ "Location" => "${uri_base}/" ], [] ]; } $session->set("user",{ uid => $auth_sso->{uid}, auth_sso => $auth_sso }); [ 302, [ Location => "${uri_base}/authorized" ], [] ]; }; mount "/authorized" => sub { state $json = JSON->new->utf8(1); my $env = shift; my $session = Plack::Session->new($env); my $user = $session->get("user"); #not logged in unless ( is_hash_ref( $user ) ) { return [ 401, [ "Content-Type" => "text/plain" ], [ "Forbidden" ] ]; } #logged in: show user his/her data [ 200, [ "Content-Type" => "application/json" ], [ $json->encode( $user ) ] ]; }; };
- httpd.conf
-
NameVirtualHost *:443 <VirtualHost *:443 > ServerName example.org #shibd is a background service, so it needs to know the domain and port UseCanonicalName on UseCanonicalPhysicalPort on #configure SSL SSLEngine on SSLProtocol all -SSLv2 -SSLv3 SSLHonorCipherOrder on SSLCipherSuite "ALL:!ADH:!EXP:!LOW:!RC2:!SEED:!RC4:+HIGH:+MEDIUM HIGH:!SSLv2:!ADH:!aNULL:!eNULL:!NULL !PSK !SRP !DSS" SSLCertificateFile /etc/httpd/ssl/server.pem SSLCertificateKeyFile /etc/httpd/ssl/server.key SSLCACertificateFile /etc/httpd/ssl/server.pem #do not proxy Shibboleth paths ProxyPass /shibboleth-sp ! ProxyPass /Shibboleth.sso ! #proxy all requests to background Plack application ProxyPass / http://127.0.0.1:5000/ ProxyPassReverse / http://127.0.0.1:5000/ #all request to /auth/shibboleth should be intercepted by mod_shib before #sending to background plack application <Location /auth/shibboleth> AuthName "shibboleth" AuthType shibboleth Require valid-user ShibRequestSetting requireSession true ShibRequestSetting redirectToSSL 443 #necessary to send the attributes in the headers ShibUseHeaders On </Location> #Path to metadata.xml Alias /shibboleth-sp /var/www/html/shibboleth-sp #handler for Shibboleth Service Provider <Location /Shibboleth.sso> SetHandler shib-handler ErrorDocument 403 /public/403.html </Location> ProxyRequests Off <Proxy *> Order Deny,Allow Allow from all </Proxy> </VirtualHost>
LOGGING
All subclasses of Plack::Auth::SSO use Log::Any to log messages to the category that equals the current package name.
AUTHOR
Nicolas Franck, <nicolas.franck at ugent.be>