The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.

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

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>

AUTHOR

Nicolas Franck, <nicolas.franck at ugent.be>

SEE ALSO

Plack::Auth::SSO