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>

SEE ALSO

Plack::Auth::SSO