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 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
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>

SEE ALSO

Plack::Auth::SSO