Exploring OAM’s SAML Identity Assertion

Introduction

OAM (Oracle Access Manager) has an interesting feature that often goes unnoticed to a considerable number of people wishing to tackle the problem of identity propagation. It’s OAM’s ability to generate a secure token embedding user information as a result of successful authentication or authorization. My colleagues Rob Otto and Simon Kissane have talked about it in “Retrieving the OAM SessionID for Fun and Profit” and “Authenticating to OIM SCIM server using an OAM-generated SAML identity assertion”.

Motivated by a recent customer inquiry, on this post I want to talk about such ability from the perspective of browser-based clients invoking REST services, a very common pattern these days. Imagine, for instance, that the end user identity must be securely propagated from an AngularJS-based application to REST services deployed on Weblogic server or JBoss. While there are a few solution options to consider, here I want to focus on this pre-built OAM feature that requires near-to-zero implementation effort. Additionally, I want to say a few words on how to customize that secure token by adding extra information to be eventually consumed by downstream resources.

The use case in question is as simple as this:

Use Case

Use Case

In the context of Oracle Fusion Middleware, the Identity Assertion feature usage is covered in Using Identity Context chapter of OAM’s Administrator’s Guide.

The Identity Assertion

A protected resource in OAM is associated with an Authentication policy and, optionally, with an Authorization policy.
In OAM admin console, if you look at the Response tab of either Authentication or Authorization policy, there’s a check box named “Identity Assertion”.

Identity Assertion

Identity Assertion

Marking the checkbox makes OAM server issuing a SAML assertion as a result of successful authentication and/or authorization. OAM then adds the assertion as an HTTP response header named “OAM_IDENTITY_ASSERTION” back to the requesting Webgate. This is a very important aspect to understand: the response header is NOT sent back to the browser, which is actually something very welcome, because it makes it very hard for any token hijack attempt. With the request authorized, the Webgate turns the response header into a request header to be forwarded to the downstream resource being invoked by the HTTP Server.

Pretty much the same process that natively happens with OAM_REMOTE_USER HTTP header, who already has the end user identity. So, why bother with OAM_IDENTITY_ASSERTION? For basically three reasons (1 and 2 actually being consequences of 3):

1) it’s safer, because it’s digitally signed by OAM server;
2) it can convey much more information than a simple user id;
3) it’s a standard SAML assertion, therefore, interoperable.

Here’s how it looks like:

<saml:Assertion xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xs="http://www.w3.org/2001/XMLSchema" Version="2.0" ID="fd53fa85-4646-41e3-9d4b-e95bc3c56b33" IssueInstant="2016-03-31T12:49:06Z">
	<saml:Issuer>OAM User Assertion Issuer</saml:Issuer>
	<dsig:Signature xmlns:dsig="http://www.w3.org/2000/09/xmldsig#">
		<dsig:SignedInfo>
			<dsig:CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
			<dsig:SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"/>
			<dsig:Reference URI="#fd53fa85-4646-41e3-9d4b-e95bc3c56b33">
				<dsig:Transforms>
					<dsig:Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/>
					<dsig:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
				</dsig:Transforms>
				<dsig:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>
				<dsig:DigestValue>KWCLd8vBg0KW5fBHD+D7ALxLsh4=</dsig:DigestValue>
			</dsig:Reference>
		</dsig:SignedInfo>
		<dsig:SignatureValue>R9fNdqSqiTQaG5gDDjv5Gue3ziZPNUfLgcT880ViUDiN3HcCpKLJ1L2PIKfQgMIjajZXO/PN/j+IC8SlmBeRZ/bI9BmHF9skqI2A+Q0+uJfgqnyw+Fy/nIPGGraTK3AVsivv5j5tkdeDVJ+dBUfBT+Gf6A/onVp7YSwpAQ48psg=</dsig:SignatureValue>
		<dsig:KeyInfo>
			<dsig:X509Data>
				<dsig:X509Certificate>MIIBxzCCATACAWYwDQYJKoZIhvcNAQEEBQAwLDEqMCgGA1UEAxMhT0FNIFVzZXIgQXNzZXJ0aW9uIElzc3VlciBDQSBSb290MB4XDTE1MTAxMzE1MTIwMVoXDTI0MDMyMjE1MTIwMVowLDEqMCgGA1UEAxMhT0FNIFVzZXIgQXNzZXJ0aW9uIElzc3VlciBDQSBSb290MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCX1C6Qrk42DsLD0QC4mx9U0kyl2MD6K1qu13N9qqv/xYHi2nmM6h/M8frFP0Czngjlm7gHzgHDRVLkMBxEiOOOpChOnygF0OhdrmeziwUNd2VxjKf8pDU17YYR06lwj4ad702Z4dFmz+rsBX/MPap8XzfwOa6Dj1DPa/5xC7buswIDAQABMA0GCSqGSIb3DQEBBAUAA4GBADCM5s2fUm4lHenm3BlRwq8JVjj6D31DWKuN4qjMKY1vHluqmfexjofzs2PtAk/4bwZN4DIKJg6qVTs5YqStlGcvDsaBsSJoxEmPOJ8PF7jdDP1bxZfxfz6AajthA4fMfwPfVDu++VGEBZ9AYBc7f9tskIDN/TVyntQlWD1he9Ru</dsig:X509Certificate>
				<dsig:X509IssuerSerial>
					<dsig:X509IssuerName>CN=OAM User Assertion Issuer CA Root</dsig:X509IssuerName>
					<dsig:X509SerialNumber>102</dsig:X509SerialNumber>
				</dsig:X509IssuerSerial>
				<dsig:X509SubjectName>CN=OAM User Assertion Issuer CA Root</dsig:X509SubjectName>
			</dsig:X509Data>
		</dsig:KeyInfo>
	</dsig:Signature>
	<saml:Subject>
		<saml:NameID NameQualifier="oud_slc09iug" Format="urn:oasis:names:tc:SAML:1.1:nameid-format:X509SubjectName" SPProvidedID="SADMIN">uid=SADMIN,cn=Users,dc=oracle,dc=com</saml:NameID>
		<saml:SubjectConfirmation Method="urn:oasis:names:tc:SAML:2.0:cm:bearer">
			<saml:SubjectConfirmationData Address="10.88.248.71"/>
		</saml:SubjectConfirmation>
	</saml:Subject>
	<saml:Conditions NotBefore="2016-03-31T12:49:06Z" NotOnOrAfter="2016-03-31T20:49:06Z"/>
	<saml:AuthnStatement AuthnInstant="2016-03-31T12:49:00Z">
		<saml:AuthnContext>
			<saml:AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:Unspecified</saml:AuthnContextClassRef>
		</saml:AuthnContext>
	</saml:AuthnStatement>
	<saml:AttributeStatement>
		<saml:Attribute NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri" Name="urn:oasis:names:tc:SAML:2.0:profiles:session:sessionId">
			<saml:AttributeValue xsi:type="xs:string">a316e3d4-0a54-4c28-a398-694dad853b1a|4QCSd0VILGDCLvqf5WH+l566Mbk=</saml:AttributeValue>
		</saml:Attribute>
		<saml:Attribute NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri" Name="urn:oasis:names:tc:SAML:2.0:profiles:session:authenticationStrength">
			<saml:AttributeValue xsi:type="xs:integer">2</saml:AttributeValue>
		</saml:Attribute>
		<saml:Attribute NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri" Name="urn:oasis:names:tc:SAML:2.0:profiles:session:timeLastActive">
			<saml:AttributeValue xsi:type="xs:dateTime">2016-03-31T12:49:06Z</saml:AttributeValue>
		</saml:Attribute>
		<saml:Attribute NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri" Name="urn:oasis:names:tc:SAML:2.0:profiles:session:tokenFormatVersion">
			<saml:AttributeValue xsi:type="xs:string">1.0</saml:AttributeValue>
		</saml:Attribute>
		<saml:Attribute NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic" Name="oracle:idm:claims:ids:attributes">
			<saml:AttributeValue xsi:type="xs:string">email=sadmin@oracle.com</saml:AttributeValue>
		</saml:Attribute>
		<saml:Attribute NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic" Name="oracle:idm:claims:tenant:name">
			<saml:AttributeValue xsi:type="xs:string">siebel</saml:AttributeValue>
		</saml:Attribute>
	</saml:AttributeStatement>
</saml:Assertion>

A few important aspects to notice when looking at the Identity Assertion are listed below. All these are valuable information for the component/agent that is going to validate the assertion or make authorization decisions based on user information right before letting the request hit the invoked resource.

1) The SAML issuer: the value always is “OAM User Assertion Issuer”.

2) The Digital Signature, signed with OAM server private key. A required task in verifying the assertion is veryfing the signature. As such, OAM server public key must be exported and made available to the validating agent. Execute the following steps for exporting the public key (courtesy of my colleague Simon Kissane):

a) Find out what your .oamkeystore password is:
Start $OAM_ORACLE_HOME/common/bin/wlst.sh

> cd $OAM_ORACLE_HOME/common/bin
> ./wlst.sh
> connect();
<enter username and password>
> print(mbs.invoke(ObjectName('com.oracle.jps:type=JpsCredentialStore'),"getPortableCredential",["OAM_STORE","jks"],["java.lang.String","java.lang.String"]).get("password"));
<copy password output>
> exit();

b) To export the assertion-cert certificate from $OAM_DOMAIN_HOME/config/fmwconfig/.oamkeystore, run the following command:

> export OAM_DOMAIN_HOME=<path_to_your_oam_domain_folder>
> cd $JAVA_HOME/bin
> ./keytool -exportcert -keystore $OAM_DOMAIN_HOME/config/fmwconfig/.oamkeystore -storetype JCEKS -storepass <password_obtained_in_step_a> -alias assertion-cert -file /tmp/assertion-cert.cer

3) The SAML Subject NameID: contains the user’s DN (Distinguished Name) in the underlying LDAP server. If the validating component/agent needs to establish a user context, this is the identity to parse and assert.

4) The SAML AttributeStatement: containing both OAM session information and custom user attributes. Notice the element

<saml:Attribute NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri" Name="urn:oasis:names:tc:SAML:2.0:profiles:session:sessionId">
     <saml:AttributeValue xsi:type="xs:string">a316e3d4-0a54-4c28-a398-694dad853b1a|4QCSd0VILGDCLvqf5WH+l566Mbk=</saml:AttributeValue>
</saml:Attribute>

As pointed by Rob Otto on his post, the string a316e3d4-0a54-4c28-a398-694dad853b1a|4QCSd0VILGDCLvqf5WH+l566Mbk= corresponds to the user session id in the OAM server. It could be used by the validating component/agent, for instance, to cross check the identity assertion with a valid OAM session, although I wouldn’t recommended it to be used without considering the security requirements of the service in question being accessed. Reason is performance, since that would involve a remote call back to OAM server on every service access. Verifying the digital signature seems good enough for the whole majority of services, since a compromised OAM’s private key means a deep serious disaster already. But if for some reason we really need to cross check the session id, one idea is doing it selectively, based on some custom attribute in the identity assertion itself.

As mentioned, extra information can be added to the identity assertion. That’s also done in OAM console’s Policy Response tab, by using the “Asserted Attribute” response type and adding the user attribute name in the underlying LDAP server to $user.attr. The screen shot below shows the adding of the user “mail” attribute.

Customizing the Identity Assertion

Customizing the Identity Assertion

That makes up for the following Attribute element in the assertion:

<saml:Attribute NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic" Name="oracle:idm:claims:ids:attributes">
    <saml:AttributeValue xsi:type="xs:string">email=sadmin@oracle.com</saml:AttributeValue>
</saml:Attribute>

For an overall discussion on Policy Responses, look at Introduction to Policy Responses for SSO.

 

Securing the Application

Now that we have good grasp on the identity assertion feature, integrating it into our use case scenario becomes simpler.

A JavaScript-based client web application is typically hosted by an HTML page. Executed by the web browser, it makes AJAX-like calls to REST services. We want to protect the services and the HTML page itself with OAM. The user typically authenticates to OAM when requesting the HTML page. The AJAX-like calls also go through the Webgate, when an authorization policy is triggered and the identity assertion is issued back to the Webgate, that adds it to the outgoing request to the service endpoint. From OAM’s perspective, the point to consider here is that a REST service is nothing different than a traditional web-based resource typically secured by OAM. The diagram below illustrates the REST service invocation.

 

Use Case Implementation with OAM

Use Case Implementation with OAM

From an OAM policy standpoint, there are three resources to protect. The HTML page along with the JavaScript resource and the services. Since we’re only interested in generating the assertion when invoking the service, I’d have two separate authorization policies: one for the HTML and the JavaScript, with no assertion generation; and another one for the services, this time marking the “Identity Assertion” checkbox.

Let me reinforce the importance of protecting the hosting HTML page in the first place. That’s an absolute must, or an unauthenticated request to the REST service will disrupt the user experience, because the redirects during login time will bypass what you intended to provide with your nice and slick JavaScript code. Another aspect worth mentioning is regarding to OAM session timeouts when a REST call is made. The JavaScript must be able to handle such event, possibly sending a request to the hosting HTML page, so that the whole process starts over and the user experience is preserved.

 

Sample Code and Configuration

I’ve done a small implementation myself. I’ve deployed my REST service on JBoss EAP and used OHS as my HTTP server. If you’ve followed along, understanding the artifacts in this section is straightforward. Please, notice that JBoss has no pre-built agent for validating the identity assertion. As you can see by looking at the REST Service Implementation below, it simply prints the OAM_IDENTITY_ASSERTION request header, just to confirm it indeed comes in. Writing an agent and integrating it with JBoss Login Module is the custom work necessary for this specific scenario. Important to mention this is not the case of Weblogic server in Oracle Fusion Middleware, where you have OWSM to the rescue.

HTML

<html ng-app="partsInvApp">
  <head>
    <meta charset="utf-8">
    <title>Parts Inventory Application</title>

    <link href="bootstrap/css/bootstrap.min.css" rel="stylesheet">

    <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.4/angular.min.js"></script>
    <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.4/angular-cookies.js"></script>
    <script src="./partsInvApp.js"></script>
  </head>
  <body>
<!--
    <p ng-controller="userProfileController">  Welcome <b>{{firstName}} {{lastName}}</b>, check out our inventory list</p>
-->
    <div style="width:600px" width="600" class="table-responsive" ng-controller="partsInvController">
      <table class="table table-striped" style="width:600px" width="600">
        <thead>
          <tr>
            <th width="15%">Id</th>
            <th width="15%">Name</th>
            <th width="40%">Description</th>
            <th width="15%">Price</th>
            <th width="15%"> </th>
          </tr>
        </thead>  
        <tbody>
          <form name="orderForm">
          <tr ng-repeat="part in parts">
          	<td width="15%">{{part.uniqueid}}</td>
            <td width="15%">{{part.name}}</td>
            <td width="40%">{{part.desc}}</td>
            <td width="15%">{{part.price}}</td>
            <td width="15%" valign="top">
             <!-- <form name="orderForm"> -->
                    <input type="hidden" name="partid" value="{{part.uniqueid}}" ng-model="part.uniqueid">
                    <button type="submit" class="btn btn-sm btn-primary"
                      ng-click="orderPart(part)"
                      ng-disabled="orderForm.$invalid">Order</button>
             <!-- </form> -->
            </td>  
          </tr>
        </form>
        </tbody>  
      </table>
      <h4 align="center" ng-if="submitResult">
        <span class="label label-success">
          {{submitResult}}
        </span>  
      </h4>  
    </div>  
  </body>
</html>

AngularJS

var partsInvApp = angular.module('partsInvApp', []).config(['$httpProvider', function($httpProvider) {
    $httpProvider.defaults.withCredentials = true;
  }]);

partsInvApp.controller('partsInvController', function ($scope, $http){
      $http.get('http://den00hdo.us.oracle.com:7777/services/parts').success(function(data) {
    	//$http.get('services/partsinventory/parts').success(function(data) {
    	$scope.parts = data.result;
    });

    $scope.orderPart = function(part) {

		var config = {
        	params: {
          		part: part
        	}
      	};
      	console.log(config.params.part.uniqueid);
      	$scope.submitResult = "Order successfully placed for part id " + config.params.part.uniqueid;
    };   	
});

http://den00hdo.us.oracle.com:777/services/parts points to the Oracle HTTP Server, who in turn forwards the request to the backend service. See below.

 

Oracle HTTP Server mod_proxy Rule

This is within OHS httpd.conf.

NameVirtualHost *.7777
<VirtualHost *:7777>

#    Routing to REST service
     ProxyPass /services/parts http://den00hdo.us.oracle.com:8080/services/rest/partsinventory/parts
     ProxyPassReverse /services/parts http://den00hdo.us.oracle.com:8080/services/rest/partsinventory/parts

</VirtualHost>

/services/parts is the resource for which an Authorization Policy with Identity Assertion checkbox marked must be created in OAM console.

 

REST Service Implementation

package oracle.ateam.sample.partsinvapi;

import javax.ws.rs.CookieParam;
import javax.ws.rs.GET;
import javax.ws.rs.HeaderParam;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;

@Path("/partsinventory")
public class PartsInventory {
    public PartsInventory() {
        super();
    }

    @GET
    @Produces("application/json")
    @Path("parts")
    public String getParts(@HeaderParam("OAM_IDENTITY_ASSERTION") String identityAssertion) {
        
        System.out.println("** DEBUG: PartsInventory web service: Request header OAM_IDENTITY_ASSERTION: " + identityAssertion);
        
        try {
            String result = "{\"result\":"; 
            result += "[";
            result += "{\"uniqueid\" : \"123\", \"name\" : \"ABC\", \"desc\" : \"This is part ABC\", \"price\" : \"100.00\"},";
            result += "{\"uniqueid\" : \"456\", \"name\" : \"DEF\", \"desc\" : \"This is part DEF\", \"price\" : \"200.00\"},";
            result += "{\"uniqueid\" : \"789\", \"name\" : \"GHI\", \"desc\" : \"This is part GHI\", \"price\" : \"300.00\"}";
            
            result += "]";
            result += "}";
            return result;
        }
        catch (Exception e) {
            e.printStackTrace();
            return "{\"error\":\"" + e.getMessage() + "\"}";
        }
    }
}

Conclusion

On this post, I’ve demonstrated how to use a sometimes unnoticed yet powerful feature of OAM for implementing identity propagation between a JavaScript-based client web application and REST services, a very common architectural pattern nowadays. So, if you’re already using OAM to secure your traditional server-side web applications, like JavaEE, Perl, PHP, etc, and want to embrace this new application model, you have a tool right at your fingertips to use, and with minimal implementation effort.

Comments

  1. Hi Andre,

    First of all, Thanks for the wonderful post.

    After making necessary configuration, we still could not get the OAM_IDENTITY_ASSERTION(or HTTP_OAM_IDENTITY_ASSERTION) http token. I am trying to hit the target servers OHS – “http://hostName:port/cgi-bin/printenv” to print all the http headers and the assertion is not one among them.

    Could you please suggest a way to check it on OAM end, if the token is actually generated successfully?

    Thanks,

    • Thanks Vyas.

      1) The assertion is added to the request by the WebGate, so you won’t likely see it in OHS.
      2) Try to print the headers in Weblogic (assuming you’re using WebGate to protect a resource in Weblogic)
      3) Make sure you set it as a header as a result of successful **authorization** in OAM.

      Hope this helps.

      • Thanks for the reply Andre.

        That was the very first thing we tried. Trying to read the token in a webcenter application (on a wls managed server, configured with SSO with OAM). While iterating through the http headers in custom adf code, we could not file the token.

        And yes, we tried setting the flag with authorization and as well with authentication policies separately with no luck.

        Is there anything to be configured at OAM end for this token to be propagated upstream to the webcenter servers?

Add Your Comment