Enabling SAML 2.0 in a PHP application
This guide shows you how to install the prerequisites of lightbulb, how to install lightbulb, how to configure meta data in lightbulb, how to load meta data in Sun Access Manager, do some fixes in lightbulb, enable logout and attribute retrieval, enable support for transient nameidentifiers and much more. If this interests you, read on...
Lightbulb Prerequisites
Install php5 with session management and OpenSSL.
apt-get install php5.1
apt-get install libapache-mod-php5.1
apt-get install php5.1-session
apt-get install php5.1-openssl
I used this php5 packages to work with Apache 1.3 and Debian sarge:
deb http://people.debian.org/~dexter all sarge
deb-src http://people.debian.org/~dexter all sarge
Lightbulb Installation
Copy over all lightbulb files into a webaccessible folder. The lightbulb installation is tightly integrated with a test service using a mysql database and persistent nameidentifiers. This guide shows you how to setup a much simpler service not requiring any database and not handling nameidentifier mapping.
If you are not going to use the demo example shipped with lightbulb, you can remove anything but the core lightbulb, which includes the following files:
- mapName.php
- nameMapping.php
- samlIdPMetadata.php
- saml-lib.php
- samlSpMetadata.php
- sp.php
- spSSOInit.php
- xmlseclibs.php
- localUserManagement.php
Meta Data
Add Service Provider Meta Data for your lightbulb installation in the file samlSpMetadata.php:
      array( "assertionConsumerServiceURL"  =>  "http://liksom1.uninett.no/lightbulb/sp.php",
             "issuer"                       =>  "liksom1.uninett.no",
             "spNameQualifier"              =>  "http://liksom1.uninett.no" ) );
Add Identity Provider Meta Data for your lightbulb installation in the file samlIdPMetadata.php:
    array( "SingleSignOnUrl"    =>  "http://mars.feide.no:80/amserver/SSORedirect/metaAlias/idp",
        "SingleLogOutUrl"   =>  "http://mars.feide.no:80/amserver/IDPSloRedirect/metaAlias/idp",
        "certFingerprint"   =>  "B0:F9:F2:BF:A0:4C:43:B0:E7:00:9A:7A:10:4E:E0:59:72:B0:BE:20" ) );
Then Meta data is needed for the IdP (Feide), so we need to produce SAML 2.0 SP Meta Data for this service.
SP SAML 2.0 Meta Data:
   ÂÂ
xmlns="urn:oasis:names:tc:SAML:2.0:metadata"   ÂÂ
entityID="liksom1.uninett.no">    <SPSSODescriptor
        AuthnRequestsSigned="false"
        WantAssertionsSigned="false"
        protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
        <SingleLogoutService
            Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"
            Location="http://liksom1.uninett.no/lightbulb/sp_logout.php"
            />
        <NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:persistent</NameIDFormat>
        <NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:transient</NameIDFormat>
        <AssertionConsumerService isDefault="true" index="0"
            Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
            Location="http://liksom1.uninett.no/lightbulb/sp.php"/>
    </SPSSODescriptor>
</EntityDescriptor>
SP Extended Meta Data:
    xmlns:
fm="urn:sun:fm:SAML:2.0:entityconfig"   ÂÂ
hosted="0"   ÂÂ
entityID="liksom1.uninett.no">    <SPSSOConfig metaAlias="/sp">
    <Attribute name="signingCertAlias">
        <Value></Value>
    </Attribute>
    <Attribute name="encryptionCertAlias">
        <Value></Value>
    </Attribute>
    <Attribute name="basicAuthOn">
        <Value>false</Value>
    </Attribute>
    <Attribute name="basicAuthUser">
        <Value></Value>
    </Attribute>
    <Attribute name="basicAuthPassword">
        <Value></Value>
    </Attribute>
    <Attribute name="autofedEnabled">
        <Value>false</Value>
    </Attribute>
    <Attribute name="autofedAttribute">
        <Value></Value>
    </Attribute>
    <Attribute name="transientUser">
        <Value></Value>
    </Attribute>
    <Attribute name="spAccountMapper">
        <Value>com.sun.identity.saml2.plugins.DefaultSPAccountMapper</Value>
    </Attribute>
    <Attribute name="spAttributeMapper">
        <Value>com.sun.identity.saml2.plugins.DefaultSPAttributeMapper</Value>
    </Attribute>
    <Attribute name="spAuthncontextMapper">
        <Value>com.sun.identity.saml2.plugins.DefaultSPAuthnContextMapper</Value>
    </Attribute>
    <Attribute name="spAuthncontextClassrefMapping">
        <Value>PasswordProtectedTransport|0|default</Value>
    </Attribute>
    <Attribute name="spAuthncontextComparisonType">
    <Value>exact</Value>
    </Attribute>
    <Attribute name="attributeMap">
        <Value></Value>
    </Attribute>
    <Attribute name="saml2AuthModuleName">
           <Value></Value>
       </Attribute>
       <Attribute name="localAuthURL">
           <Value></Value>
       </Attribute>
       <Attribute name="intermediateUrl">
           <Value></Value>
       </Attribute>
       <Attribute name="defaultRelayState">
           <Value></Value>
       </Attribute>
       <Attribute name="assertionTimeSkew">
           <Value>300</Value>
       </Attribute>
       <Attribute name="wantAttributeEncrypted">
           <Value></Value>
       </Attribute>
       <Attribute name="wantAssertionEncrypted">
           <Value></Value>
       </Attribute>
       <Attribute name="wantNameIDEncrypted">
           <Value></Value>
       </Attribute>
       <Attribute name="wantArtifactResponseSigned">
           <Value></Value>
       </Attribute>
       <Attribute name="wantLogoutRequestSigned">
           <Value></Value>
       </Attribute>
       <Attribute name="wantLogoutResponseSigned ">
           <Value></Value>
       </Attribute>
       <Attribute name="wantMNIRequestSigned">
           <Value></Value>
       </Attribute>
       <Attribute name="wantMNIResponseSigned">
           <Value></Value>
       </Attribute>
       <Attribute name="cotlist">
           <Value>feidecot</Value>
       </Attribute>
    </SPSSOConfig>
</EntityConfig>
Load the meta data in the IdP running Sun Access Manager:
/opt/SUNWam/saml2/bin/saml2meta delete -u uid=amAdmin,ou=People,dc=feide,dc=no -w PASSWORD -e liksom1.uninett.no
/opt/SUNWam/saml2/bin/saml2meta import -u uid=amAdmin,ou=People,dc=feide,dc=no -w PASSWORD -m /opt/SUNWam/saml2/meta/liksom1-spMeta.xml -x /opt/SUNWam/saml2/meta/liksom1-spExtended.xml
In IdP Meta in lightbulb you must configure the certificate fingerprint. Apply this patch (in saml-lib.php) to make it easier to figure out the fingerprint of the IdP:
    echo "<p>Expecting fingerprint [" . $issuerFingerprint . "] but message was signed with this certificate [" . $fingerprint . "]";
}
return ($fingerprint == $issuerFingerprint);
(in the end of validateCertFingerprint() )
Go to home.php and login via IdP, when you return you get a signature error message:
Expecting fingerprint [b0f9f2bfa04c43b0e7009a7a104ee05972b0be20] but message was signed with this certificate [d8ee63c8c30a9df24b7fc27b434c858c62c78c69]Error: Fingerprint Validation Failed
Update IdP meta data with the correct certificate fingerprint.
Adding support for transient nameidentifiers to Lightbulb
In a simple example we will not use the namemapping functionality, so we want to use transient nameidentifiers. Lightbulb does not support transient nameidentifiers out of the box, but we modify the following line in spSSOInit.php and spSingleLogoutInit.php from:
Format=\"urn:oasis:names:tc:SAML:2.0:nameid-format:persistent
to
Format=\"urn:oasis:names:tc:SAML:2.0:nameid-format:transient
This will make lightbulb ask for a transient nameidentifier when sending the authentication request, and we can leave the nameId Mapping service in Lightbulb out from the meta data loaded in the IdP.
Then we need to clear out some of the nameidenfier mapping functionality. In nameMapping.php we include:
    error_log( "<p><b>Mapping</b>: IdP:" . $idp . "  SP:" . $sp . " NameID:" . $nameID . ".</p>");
    return $nameID;
}
function mapNameIdToLocalId($idp, $sp, $nameID, $localId) {
    error_log( "<p><b>Add Mapping</b>: IdP:" . $idp . "  SP:" . $sp . " NameID:" . $nameID . " LocalID:" . $localId . ".</p>");
}
Creating a test service
Now we are ready to create a test service. We create a page test.php.
// We need session management (separate module in PHP5 - needs to be installed)
session_start();
// URL to return user to after authentication. Will be this page :D
$return_url = "http://liksom1.uninett.no/lightbulb/test.php";
// URL initiating SSO with lighbulb, contains some configuration parameters.
$ssoinit_url = "http://liksom1.uninett.no/lightbulb/spSSOInit.php?" .
    "metaAlias=/sp&" .
    "idpEntityID=mars.feide.no&" .
    "binding=urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST&" .
    "RelayState=" . urlencode($return_url);
// Logout URL. Also a lightbulb service with some parameters and a return url.
$logout_url = "spSingleLogoutInit.php?" .
    "metaAlias=/sp&" .
    "binding=urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST&" .
    "RelayState=" . urlencode($return_url);
?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
        "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
    <meta http-equiv="content-type" content="text/html; charset=utf-8" />
    <title>Test Service</title>
</head>
<body>
    <h1>Test service</h1>
    <p>Welcome to this test service</p>
    <?php if (isset($_SESSION['UserID'])) { ?>
        <p>Your transient nameidentifier is <?php echo $_SESSION['UserID']; ?></p>
        <p><a href="<?php echo (htmlspecialchars($logout_url)); ?>">Logout</a></p>     ÂÂ
       ÂÂ
    <?php } else { ?>
        <p><a href="<?php echo (htmlspecialchars($ssoinit_url)); ?>">Login via FEIDE</a></p>
    <?php } ?>
</body>
</html>
Logout
There is a bug in Lightbulb related to logout where it puts the NameQualifier in the Issuer field. This is easy to fix, just replace this in spSingleLogoutInit.php:
          $nameId["SPNameQualifier"] .
        "</saml:Issuer>" .
should be replaced with:
          $spMetadata[$metaAlias]["issuer"] .
        "</saml:Issuer>" .
And then earlier in the file, you must define MetaAlias:
    $RelayStateURL = $_GET["RelayState"];
    $metaAlias = $_GET["metaAlias"];
Clean away some unneccessary code
As mentioned earlier lightbulb is way to tightly integrated with the demo example.
Consequently the localUserManagement file contains more code than neccessary, please remove the following functions:
- authenticateLocalUser()
- getUserName()
Testing the service
Go to the test service web page, in this example: http://liksom1.uninett.no/lightbulb/test.php, and hopefully you will see a page similar to this:

Click on the login link, and you will be redirected to Feide, then enter your credentials, and you should be redirected back. Now the page should show something similar to:

Now you can click logout and verify that you are logged out both at this service and other Feide services.
Getting attributes
First, saml-lib.php have a function getAttributes() which is a bit buggy. Replace the function with the code below:
//  attributes in the first statement
function getAttributes($token) {    $attributes = array();
   ÂÂ
    if ($token instanceof DOMDocument) {
   ÂÂ
        //echo "<PRE>token:";
        //echo htmlentities($token->saveXML());
        //echo ":</PRE>";
   ÂÂ
        $xPath = new DOMXpath($token);
        $xPath->registerNamespace('mysaml', SAML_ASSERT_NS);
        $xPath->registerNamespace('mysamlp', SAML_PROTOCOL_NS);
        $query = '/mysamlp:Response/mysaml:Assertion/mysaml:Conditions';
        $nodelist = $xPath->query($query);
        if ($node = $nodelist->item(0)) {
           ÂÂ
            $start = $node->getAttribute('NotBefore');
            $end = $node->getAttribute('NotOnOrAfter');
            if (! checkDateConditions($start, $end)) {
                echo " Date check failed ... (from $start to $end)";
               ÂÂ
                return $attributes;
            }
        }
        $query = '/mysamlp:Response/mysaml:Assertion/mysaml:AttributeStatement/mysaml:Attribute';
        $nodelist = $xPath->query($query);
        foreach ($nodelist AS $node) {
            if ($name = $node->getAttribute('Name')) {
                $value = '';
                foreach ($node->childNodes AS $child) {
                    if ($child->localName == 'AttributeValue') {
                        $value = $child->textContent;
                        break;
                    }
                }
                $attributes[$name] = $value;
            }
        }
    }
    return $attributes;
}
Then you may use the attribute function to retrieve attributes from Feide. In test.php add at the top:
// Needs a function to get the token from the php session
require 'localUserManagement.php';
Then some code to dump the attributes to the html page:
        <p>Your transient nameidentifier is <?php echo $_SESSION['UserID']; ?></p>
        <p><a href="<?php echo (htmlspecialchars($logout_url)); ?>">Logout</a></p>     ÂÂ
       ÂÂ
    <p>Attributes:</p><pre><?php
   ÂÂ
        $token = getResponse();
        $attributes = getAttributes($token);
        print_r($attributes);
    ?></pre>
    <?php } else { ?>
And then when you login you will see all the attributes:

[...] Hot on the heels of
[...] Hot on the heels of our launch of OpenSSO Extensions comes the latest extension, contributed by Todd Saxton from New Zealand: a SAML 2.0 relying party implementation in Ruby (already noticed by the sharp-eyed Tatsuo Kudo, here). Todd used the existing SAML 2.0 PHP relying party (formerly known as Lightbulb) as a starting point and ported it to Ruby, using Roland Schmitt's WSS4R to handle the XML Security chores. Note that both the Ruby and PHP SAML 2.0 relying party implementations are very much 'proofs of concept'. They successfully complete SAML 2.0 single sign-on and single logout, but are not to be considered production quality. In particular, Andreas Solberg has identified some bugs and shortcomings in the PHP implementation and kindly offered to contribute his fixes (nudge!). [...]
both the Ruby and PHP SAML
both the Ruby and PHP SAML 2.0 relying party implementations are very much 'proofs of concept'. They successfully complete SAML 2.0 single sign-on and single logout, but are not to be considered production quality. In particular, Andreas Solberg has identified some bugs and shortcomings in the PHP implementation and kindly offered to contribute his fixes (nudge!). I just downloaded the Ruby SAML 2.0 code and... it works! I made one minor fix to account for differences in my environment, but everything else was just configuration. Here is a checklist of what
both the Ruby and PHP SAML
both the Ruby and PHP SAML 2.0 relying party implementations are very much 'proofs of concept'. They successfully complete SAML 2.0 single sign-on and single logout, but are not to be considered production quality. In particular, Andreas Solberg has identified some bugs and shortcomings in the PHP implementation and kindly offered to contribute his fixes (nudge!). I just downloaded the Ruby SAML 2.0 code and... it works! I made one minor fix to account for differences in my environment, but everything else was just configuration. Here is a checklist of what
both the Ruby and PHP SAML
both the Ruby and PHP SAML 2.0 relying party implementations are very much 'proofs of concept'. They successfully complete SAML 2.0 single sign-on and single logout, but are not to be considered production quality. In particular, Andreas Solberg has identified some bugs and shortcomings in the PHP implementation and kindly offered to contribute his fixes (nudge!). I just downloaded the Ruby SAML 2.0 code and... it works! I made one minor fix to account for differences in my environment, but everything else was just configuration. Here is a checklist of what