Using SAML-based authentication

LiveCycle supports various web service authentication modes when invoking services. One authentication mode is specifying both a user name and password value using a basic authorization header in the web service call. LiveCycle also supports SAML assertion-based authentication. When a client application invokes a LiveCycle service using a web service, the client application can provide authentication information in one of the following ways:

  • Passing credentials as part of Basic Authorization

  • Passing username token as part of WS-Security header

  • Passing a SAML assertion as part of WS-Security header

  • Passing Kerberos token as part of WS-Security header

LiveCycle does not support standard certificate-based authentication but it does support certificate-based authentication in a different form.

Note: The web service quick starts in Programming with LiveCycle specify user name and password values to perform authorization.

The identity of AEM forms users can be represented through a SAML assertion signed using a secret key. The following XML code shows an example of a SAML assertion.

<Assertion xmlns="urn:oasis:names:tc:SAML:1.0:assertion" 
    xmlns:saml="urn:oasis:names:tc:SAML:1.0:assertion" 
    xmlns:samlp="urn:oasis:names:tc:SAML:1.0:protocol" 
    AssertionID="fd4bd0c87302780e0d9bbfa8726d5bc0" IssueInstant="2008-04-17T13:47:00.720Z" Issuer="LiveCycle" 
    MajorVersion="1" MinorVersion="1"> 
    <Conditions NotBefore="2008-04-17T13:47:00.720Z" NotOnOrAfter="2008-04-17T15:47:00.720Z"> 
    </Conditions> 
    <AuthenticationStatement 
        AuthenticationInstant="2008-04-17T13:47:00.720Z" 
        AuthenticationMethod="urn:oasis:names:tc:SAML:1.0:am:unspecified"> 
        <Subject> 
            <NameIdentifier NameQualifier="DefaultDom">administrator</NameIdentifier> 
            <SubjectConfirmation> 
                <ConfirmationMethod>urn:oasis:names:tc:SAML:1.0:cm:sender-vouches</ConfirmationMethod> 
            </SubjectConfirmation> 
        </Subject> 
    </AuthenticationStatement> 
    <ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#"> 
        <ds:SignedInfo> 
            <ds:CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"></ds:CanonicalizationMethod> 
            <ds:SignatureMethod    Algorithm="http://www.w3.org/2000/09/xmldsig#hmac-sha1"></ds:SignatureMethod> 
            <ds:Reference URI="#fd4bd0c87302780e0d9bbfa8726d5bc0"> 
                <ds:Transforms> 
                    <ds:Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"></ds:Transform> 
                    <ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"> 
                        <ec:InclusiveNamespaces    xmlns:ec="http://www.w3.org/2001/10/xml-exc-c14n#" 
                            PrefixList="code ds kind rw saml samlp typens #default"> 
                        </ec:InclusiveNamespaces> 
                    </ds:Transform> 
                </ds:Transforms> 
                <ds:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"></ds:DigestMethod> 
                <ds:DigestValue>hVrtqjWr+VzaVUIpQx0YI9lIjaY=</ds:DigestValue> 
            </ds:Reference> 
        </ds:SignedInfo> 
        <ds:SignatureValue>UMbBb+cUcPtcWDCIhXes4n4FxfU=</ds:SignatureValue> 
    </ds:Signature> 
</Assertion>

This example assertion is issued for an administrator user. This assertion contains the following noticeable items:

  • It is valid for certain duration.

  • It is issued for a particular user.

  • It is digitally signed. So any modification done to it would break the signature.

  • It can be presented to LiveCycle as a token of user's identity similar to user name and password.

A client application can retrieve the assertion from any LiveCycle AuthenticationManager API which returns an AuthResult object. You can obtain an AuthResult instance by performing one of the following two methods:

  • Authenticating the user using any of the authenticate methods exposed by AuthenticationManager API. Typically, one would use the user name and password; however, you can also use the certificate authentication.

  • Using the AuthenticationManager.getAuthResultOnBehalfOfUser method. This method lets a client application get an AuthResult object for any AEM forms user.

a AEM forms user can be authenticated using a SAML token that is obtained. This SAML assertion (xml fragment) can be send as part of the WS-Security header with the web service call for user authentication. Typically, a client application has authenticated a user but has not stored the user credentials. (Or the user has logged on to that client through a mechanism other than using a user name and password.) In this situation, the client application has to invoke LiveCycle and impersonate a specific user which is allowed to invoke LiveCycle.

To impersonate a specific user, invoke the AuthenticationManager.getAuthResultOnBehalfOfUser method using a web service. This method returns an AuthResult instance which contains the SAML assertion for that user.

Next, use that SAML assertion to invoke any service that requires authentication. This action involves sending the assertion as part of the SOAP header. When a web service call is made with this assertion, LiveCycle identifies the user as the one represented by that assertion. That is, the user specified in the assertion is the user who is invoking the service.

Using Apache Axis classes and SAML-based authentication

You can invoke a LiveCycle service by Java proxy classes that were created using the Axis library. (See Creating Java proxy classes using Apache Axis .)

When using AXIS that uses SAML-based authentication, register the request and response handler with Axis. Apache Axis invokes the handler before sending an invocation request to LiveCycle. To register a handler, create a Java class that extends org.apache.axis.handlers.BasicHandler .

Create an AssertionHandler with Axis

The following Java class, named AssertionHandler.java , shows an example of a Java class that extends org.apache.axis.handlers.BasicHandler .

public class AssertionHandler extends BasicHandler { 
       public void invoke(MessageContext ctx) throws AxisFault { 
           String assertion = (String) ctx.getProperty(LC_ASSERTION); 
 
           //no assertion hence nothing to insert 
           if(assertion == null) return;  
     
           try { 
               MessageElement samlElement = new MessageElement(convertToXML(assertion)); 
               SOAPHeader header = (SOAPHeader) ctx.getRequestMessage().getSOAPHeader(); 
               //Create the wsse:Security element which would contain the SAML element 
               SOAPElement wsseHeader = header.addChildElement("Security", "wsse", WSSE_NS); 
               wsseHeader.appendChild(samlElement); 
               //remove the actor attribute as in LC we do not specify any actor. This would not remove the actor attribute though 
               //it would only remove it from the soapenv namespace 
               wsseHeader.getAttributes().removeNamedItem("actor"); 
           } catch (SOAPException e) { 
               throw new AxisFault("Error occured while adding the assertion to the SOAP Header",e); 
           } 
       } 
}

Register the handler

To register a handler with Axis, create a client-config.wsdd file. By default, Axis looks for a file with this name. The following XML code is an example of a client-config.wsdd file. See Axis documentation for more information.

<deployment xmlns="http://xml.apache.org/axis/wsdd/" xmlns:java="http://xml.apache.org/axis/wsdd/providers/java"> 
    <transport name="http" pivot="java:org.apache.axis.transport.http.HTTPSender"/> 
     <globalConfiguration > 
      <requestFlow > 
       <handler type="java:com.adobe.idp.um.example.AssertionHandler" /> 
      </requestFlow > 
     </globalConfiguration > 
</deployment> 

Invoke a LiveCycle service

The following code example invokes a LiveCycle service using SAML-based authentication.

public class ImpersonationExample { 
       . . . 
       public void  authenticateOnBehalf(String superUsername,String password,  
               String canonicalName,String domainName) throws UMException, RemoteException{ 
           ((org.apache.axis.client.Stub) authenticationManager).setUsername(superUsername); 
           ((org.apache.axis.client.Stub) authenticationManager).setPassword(password); 
     
           //Step 1 - Invoke the Auth manager api to get an assertion for the user to be impersonated 
           AuthResult ar = authenticationManager.getAuthResultOnBehalfOfUser(canonicalName, domainName, null); 
           String assertion = ar.getAssertion(); 
           //Step 2 - Setting the assertion here to be picked later by the AssertionHandler. Note that stubs are not threadSafe 
           //hence should not be reused. For this simple example we have made them instance variable but care should be taken 
           //regarding the thread safety 
           ((javax.xml.rpc.Stub) authorizationManager)._setProperty(AssertionHandler.LC_ASSERTION, assertion); 
       } 
     
       public Role findRole(String roleId) throws UMException, RemoteException{ 
           //This api would be invoked under bob's user rights 
           return authorizationManager.findRole(roleId); 
       } 
     
       public static void main(String[] args) throws Exception { 
           ImpersonationExample ie = new ImpersonationExample("http://localhost:5555"); 
           //Get the SAML assertion for the user to impersonate and store it in stub 
           ie.authenticateOnBehalf( 
                   "administrator", //The Super user which has the required impersonation permission 
                   "password", // Password of the super user as referred above 
                   "bob", //Cannonical name of the user to impersonate 
                   "testdomain" //Domain of the user to impersonate 
                   ); 
     
           Role r = ie.findRole("BASIC_ROLE_ADMINISTRATOR"); 
           System.out.println("Role "+r.getName()); 
       } 
}

Using a .NET client assembly and SAML-based authentication

You can invoke a LiveCycle service by using a .NET client assembly and SAML-based authentication. To do so, you must use the Web Service Enhancements 3.0 (WSE). For information about creating a .NET client assembly that uses WSE, see Creating a .NET project that uses DIME .

Note: The DIME section uses WSE 2.0. To use SAML-based authentication, follow the same instructions that are specified in the DIME topic. However, replace WSE 2.0 with WSE 3.0. Install Web Services Enhancements 3.0 on your development computer and integrate it with Microsoft Visual Studio .NET. You can download Web Services Enhancements 3.0 from the Microsoft Download Center .

The WSE architecture uses Policies, Assertions, and SecurityToken data types. Briefly, for a web service call, specify a policy. A policy can have multiple assertions. Each assertion can contain filters. A filter is invoked at certain stages in a web service call and, at that time, they can modify the SOAP request. For full details, see the Web Service Enhancements 3.0 documentation.

Create the Assertion and Filter

The following C# code example creates filter and assertion classes. This code example creates a SamlAssertionOutputFilter. This filter is invoked by the WSE framework before the SOAP request is sent to LiveCycle.

class LCSamlPolicyAssertion : Microsoft.Web.ServicES4.Design.PolicyAssertion 
{ 
       public override Microsoft.Web.ServicES4.SoapFilter CreateClientOutputFilter(FilterCreationContext context) 
       { 
          return new SamlAssertionOutputFilter(); 
       } 
       . . . 
} 
 
     
class SamlAssertionOutputFilter : SendSecurityFilter 
{ 
       public override void SecureMessage(SoapEnvelope envelope, Security security) 
       { 
          // Get the SamlToken from the SessionState 
          SamlToken samlToken = envelope.Context.Credentials.UltimateReceiver.GetClientToken<SamlToken>(); 
          security.Tokens.Add(samlToken); 
       } 
}

Create the SAML Token

Create a class to represent the SAML assertion. The main task that this class performs is convert data values from string to xml and preserve white space. This assertion xml is later imported into the SOAP request.

class SamlToken : SecurityToken 
{ 
       public const string SAMLAssertion = "http://docs.oasis-open.org/wss/oasis-wss-saml-token-profile-1.1#SAMLV1.1"; 
       private XmlElement _assertionElement; 
 
       public SamlToken(string assertion) 
            : base(SAMLAssertion) 
       { 
          XmlDocument xmlDoc = new XmlDocument(); 
          //The white space has to be preserved else the digital signature would get broken 
          xmlDoc.PreserveWhitespace = true; 
          xmlDoc.LoadXml(assertion); 
          _assertionElement = xmlDoc.DocumentElement; 
        } 
     
        public override XmlElement GetXml(XmlDocument document) 
        { 
           return (XmlElement)document.ImportNode(_assertionElement, true); 
        } 
       . . .  
}

Invoke a LiveCycle service

The following C# code example invokes a LiveCycle service by using SAML-based authentication.

public class ImpersonationExample 
{ 
       . . . 
       public void AuthenticateOnBehalf(string superUsername, string password, string canonicalName, string domainName) 
       { 
           //Create a policy for UsernamePassword Token 
           Policy usernamePasswordPolicy = new Policy(); 
           usernamePasswordPolicy.Assertions.Add(new UsernameOverTransportAssertion()); 
     
           UsernameToken token = new UsernameToken(superUsername, password, PasswordOption.SendPlainText); 
           authenticationManager.SetClientCredential(token); 
           authenticationManager.SetPolicy(usernamePasswordPolicy); 
 
           //Get the SAML assertion for impersonated user 
           AuthClient.AuthenticationManagerService.AuthResult ar  
               = authenticationManager.getAuthResultOnBehalfOfUser(canonicalName, domainName, null); 
           System.Console.WriteLine("Received assertion " + ar.assertion); 
 
           //Create a policy for inserting SAML assertion 
           Policy samlPolicy = new Policy(); 
           samlPolicy.Assertions.Add(new LCSamlPolicyAssertion()); 
           authorizationManager.SetPolicy(samlPolicy); 
           //Set the SAML assertion obtained previously as the token 
           authorizationManager.SetClientCredential(new SamlToken(ar.assertion)); 
       } 
 
       public Role findRole(string roleId) 
       { 
           return authorizationManager.findRole(roleId); 
       } 
 
       static void Main(string[] args) 
       { 
           ImpersonationExample ie = new ImpersonationExample("http://localhost:5555"); 
           ie.AuthenticateOnBehalf( 
                "administrator", //The Super user which has the required impersonation permission 
                "password", // Password of the super user as referred above 
                "bob", //Cannonical name of the user to impersonate 
                "testdomain" //Domain of the user to impersonate 
                ); 
         
        Role r = ie.findRole("BASIC_ROLE_ADMINISTRATOR"); 
           System.Console.WriteLine("Role "+r.name); 
    } 
}

// Ethnio survey code removed