Quantcast
Channel: ATeam Chronicles
Viewing all 95 articles
Browse latest View live

Exploring OAM’s SAML Identity Assertion

$
0
0

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.


OAM Protected SPAs and Same-Origin Policy

$
0
0

Introduction

On a previous post, I described the usage of OAM’s SAML Identity Assertion in the context of SPA (Single Page Applications) and how easy it is to take advantage of it for securely propagating the end user identity from the client to the backend services. However, that post is written with the assumption that both the JavaScript code and the REST services are protected by the same WebGate. Speaking in HTTP protocol terms, we say they have same origin.

Modern web browsers natively take a security measure called Same-Origin policy, enforcing that a script can only invoke a service if both the script and service are served from the same host. This is done for avoiding a 3rd-party web site sending a malicious script to the user web browser, taking advantage of any browser session data (including http cookies), thus making itself able for executing remote calls to legitimate services.

In OAM real world deployment scenarios, it’s totally ok that customers want to use separate WebGates for JavaScript and REST services. This breaks the Same-Origin policy right away. The XHRs (XML HTTP Requests) made by the browser on behalf of the JavaScript would be automatically denied. This post describes how to deal with this by using the CORS (Cross Object Request Sharing) mechanism, as well as how to handle pre-flight requests and HTTP redirects in the context of REST services protected by OAM.

Setting the basis for this discussion, the deployment topology is depicted in the following diagram. It’s true that we could have a Reverse Proxy in front of the two WebGates as well. That would obviously moot this discussion, but that’s not what we want, since having WebGates directly exposed to clients is totally valid.

Deployment Topology

Deployment Topology

1 – The user first loads a JavaScript into the browser by accessing a resource protected by a WebGate running on host myapp.ateam.com.
2 – The Javascript (an AngularJS application) makes XHRs (GET and POST) to REST services protected by a Webgate running on host myservice.ateam.com.

It looks simple. But there are a few devils on the way.

CORS – Cross Object Request Sharing

Refer to this document for the CORS specification.

In a nutshell, CORS is a mechanism defined around the notion of allowing user-defined resources to relax the Same-Origin policy. These resources can basically tell the browser which are the origins they accept requests from. In our scenario, the REST services running on host myservice.ateam.com tell the browser to accept requests from myapp.ateam.com. There are many other aspects in CORS, like methods and headers allowed, credentials propagation and pre-flight requests. We’ll certainly touch upon them here, but it is highly recommended that you take a read on the CORS specification for fully understanding their semantics.

Handling Same-Origin Policy

Let’s assume the user has been authenticated and the AngularJS code, served by myapp.ateam.com, is loaded by the browser. It’s now going to invoke a REST service on myservice.ateam.com.

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

For relaxing the Same-Origin policy, we need the following ‘Header’ directives in the routing rule for the backend services in mod_wl_ohs.conf of myservice.ateam.com’s OHS:

<Location /services>
    ## Handling the internal forward for the WebLogic server actually hosting the services
    SetHandler weblogic-handler
    WebLogicHost int.us.oracle.com
    WeblogicPort 8003
    
    ## The following 'Header always set' directives are mandatory for cross domain XHR
    SetEnvIf Origin "(http://myapp.ateam.com:7777|null)" ACAO=$0
    Header always set Access-Control-Allow-Origin %{ACAO}e env=ACAO
    Header always set Access-Control-Allow-Credentials "true"
    Header always set Access-Control-Allow-Methods "GET, POST, OPTIONS"
    Header always set Access-Control-Allow-Headers "Origin, Content-Type, Accept"    
</Location>

Let’s first focus on the SetEnvIf directive and the 1st Header directive. The SetEnvIf is whitelisting “http://myapp.ateam.com:7777” and “null” values in the Origin request header. Only these two values are echoed back in Access-Control-Allow-Origin response header. Handling “null” is necessary due to HTTP redirects between the WebGate and OAM server. The browser sets the Origin header to “null” when a redirect is made to a different server than the one originally requested. That’s what happens, for instance, when the browser is redirected from http://myservice.ateam.com:7777/services/partsinventory/parts to http://<oam_server>:<port>/oam/server/obrareq.cgi.

Accepting “null” origins may ease the way for CSRF (Cross Site Request Forgery) attacks. In general, for guarding against CSRF attacks, do not rely on the Origin header, do not allow the “safe” operations like GET, HEAD and OPTIONS to change server-side data and use CSRF tokens in those considered “unsafe” operations, like POST, PUT, PATCH and DELETE.

When a request hits the WebGate on myservice.ateam.com, it will be detected there’s no OAM authentication cookie for that WebGate. Hence, an HTTP redirect is made to http://<oam_server>:<port>/oam/server/obrareq.cgi with OAM_ID cookie. OAM verifies the cookie and does another HTTP redirect, this time to myservice.ateam.com WebGate on /obrar.cgi, where the OAM authentication cookie is generated. Finally, the browser is redirected to the originally requested resource. This is just the way OAM works. It’s not at all particular to the use case here described.

From the standpoint of the JavaScript code, it doesn’t need to follow all those redirects. Even in the context of an XHR, the redirects are natively handled by the browser. Let’s notice this: in the context of the XHR. As such, the redirects are like just another XHR. As a consequence, CORS headers also need to be defined for them. So, re-reading the second last paragraph, here is the sequence of redirects that takes place:

1 – http://myservice.ateam.com:7777/services/partsinventory/parts -> http://<oam_server>:<oam_port>/oam/server/obrareq.cgi

2 – http://<oam_server>:<oam_port>/oam/server/obrareq.cgi -> http://myservice.ateam.com:7777/obrar.cgi

3 – http://myservice.ateam.com:7777/obrar.cgi -> http://myservice.ateam.com:7777/services/partsinventory/parts

Those redirects tell us we need CORS headers for the OAM server itself. How do we deal with this? We have to front-end OAM server with a reverse proxy that allows us to set those headers. Actually, this is a recommended approach for not exposing OAM server in the DMZ for internet facing web applications.

In my setup, I’ve used OHS (mylogin.ateam.com:7778) for the purpose and set CORS headers within mod_wl_ohs.conf:

# Handling OAM redirects in the context of XML HTTP Requests
<Location /oam>
SetHandler weblogic-handler
WebLogicHost oamserver.us.oracle.com
WeblogicPort 14100

SetEnvIf Origin "(http://myapp.ateam.com:7777|null)" ACAO=$0

Header always set Access-Control-Allow-Origin %{ACAO}e env=ACAO
Header always set Access-Control-Allow-Credentials "true"
Header always set Access-Control-Allow-Methods "GET, POST, OPTIONS"
Header always set Access-Control-Allow-Headers "Origin, Content-Type, Accept"
</Location>

When front-ending OAM, it’s imperative to update OAM server host and port in OAM Console, per image below:

OAM Front End Host

OAM Front End Host

To fully satisfy the browser in that sequence of redirects, we also need CORS headers for http://myservice.ateam.com:7777/obrar.cgi. I’ve defined them as follows in myservice.ateam.com OHS httpd.conf:

# Handling OAM redirects in the context of XML HTTP Requests
<Location /obrar.cgi>
SetEnvIf Origin "(http://myapp.ateam.com:7777|null)" ACAO=$0
Header always set Access-Control-Allow-Credentials "true"
Header always set Access-Control-Allow-Methods "GET, POST, OPTIONS"
Header always set Access-Control-Allow-Headers "Origin, Content-Type, Accept"
Header always set Access-Control-Allow-Origin %{ACAO}e env=ACAO
</Location>

With these in place, we satisfy requests with the GET method.

Handling POSTs

For handling POSTs, we need to know that browsers may decide to preflight the request. A pre-flight request is a preliminary inquiry to ensure the actual request is safe to be sent. So the browser first sends a request with the OPTIONS method, and the server replies back with the appropriate CORS headers, either allowing or denying the request.

Verify in this Firefox screenshot how an OPTIONS request is sent right before the actual POST, basically asking for authorization to submit the POST. This is evidenced by “Access-Control-Request-Method” request header. The server authorizes by issuing back the “Access-Control-Allowed-Methods” response header.

OPTIONS Request

OPTIONS Request

Now, a little devil here: do notice that OAM may also be protecting OPTIONS requests as well. As such, it would expect cookies in the request. The thing is that preflight requests, per CORS specification, exclude user credentials (including HTTP cookies). That basically means an anonymous request to an OAM-proteced resource, initiating an authentication flow, definitely not what our application expects. The solution to this is simply excluding OPTIONS as one of the supported methods in the protected resource. In fact, there’s no need for us to worry about the OPTIONS method in OAM, as long as the backend REST services either don’t support the OPTIONS method or don’t implement it incorrectly.

For excluding the OPTIONS method from OAM oversight, edit the corresponding resource in OAM Console:

 

Options OFF

Options OFF

We can even take the precaution of handling OPTIONS in OHS, thus preventing the request hitting the REST service endpoint in Weblogic server altogether. This can be implemented with the following directives within <Location /services> in mod_wl_ohs.conf:

RewriteEngine On
RewriteCond %{REQUEST_METHOD} OPTIONS
RewriteRule ^(.*) $1 [R=200,L]

We’re essentially returning a 200 HTTP status code for any OPTIONS request.

Handling OAM Timeouts

It might be the case that the end user leaves the application idle for some time. Upon her return, OAM might have timed out, either due to OAM idle timeout or OAM session expiration. The act of clicking some UI element (like a button or link) that triggers a REST service call must be handled in context by the application. One approach is forcing a new user login for the application as whole. Some people may feel this as disruptive. I like to think that an OAM-protected REST service is just another OAM-protected server-side resource within traditional web applications. And given the intrinsic stateless orientation of REST-based services, it should be fine that the user interface calls out a relogin.

Upon timeout, OAM does an HTTP redirect to http://<oam_server>:<oam_port>/oam/server/obrareq.cgi, which in turn brings in the SSO login page. Therefore, we have to handle this in OHS. And this has been already taken care when we dealt with the redirects that takes place during normal processing of REST services requests in the OAM front-end host.

# Handling OAM redirects in the context of XML HTTP Requests
<Location /oam>
SetHandler weblogic-handler
WebLogicHost oamserver.us.oracle.com
WeblogicPort 14100

SetEnvIf Origin "(http://myapp.ateam.com:7777|null)" ACAO=$0

Header always set Access-Control-Allow-Origin %{ACAO}e env=ACAO
Header always set Access-Control-Allow-Credentials "true"
Header always set Access-Control-Allow-Methods "GET, POST, OPTIONS"
Header always set Access-Control-Allow-Headers "Origin, Content-Type, Accept"
</Location>

The AngularJS code must capture the redirect outcome (which is the SSO page) and preferrably redirect the browser window to the protected server-side resource in which the call is being executed. Yes, I agree that figuring out this context may vary in complexity depending on how the application has been designed. My use case here is the simplest, since I have only one HTML page serving the AngularJS code.

My sample employs an AngularJS interceptor, as follows. It basically looks for a specific string that I know is present in OAM’s login page. In finding it, it redirects the browser window to partsInventory.html location, the resource that is actually embedding the AngularJS code. Just remember to register the interceptor with AngularJS $httpProvider.

partsInvApp.factory('redirectInterceptor', function($q,$location,$window){
    return {
        'response': function(response){
            if (typeof response.data === 'string' && response.data.indexOf("Enter your Single Sign-On credentials below") > -1) {
                $window.location = 'partsInventory.html';
                return $q.reject(response);
            }
            else {
              return response || $q.when(response);
            }  
        }
    }
});

partsInvApp.config(['$httpProvider', function($httpProvider) {
    $httpProvider.defaults.withCredentials = true;
    $httpProvider.interceptors.push('redirectInterceptor');
  }]);

Notice the line

$httpProvider.defaults.withCredentials = true;

This is what makes the browser sending over cookies along with HTTP requests in the context of XHR in AngularJS, a key requirement for OAM-protected applications.

Sample Code

Here’s the HTML code of my suboptimal Inventory application:

<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>
    <script src="./jquery-1.12.3.min.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="30%">Description</th>
            <th width="15%">Price</th>
            <th width="10%">Quantity</th>
            <th width="15%"> </th>
          </tr>
        </thead>  
        <tbody>
          <tr ng-repeat="part in parts">
          	<td width="15%">{{part.uniqueid}}</td>
            <td width="15%">{{part.name}}</td>
            <td width="30%">{{part.desc}}</td>
            <td width="15%">{{part.price}}</td>
            <td width="10%" valign="top">
                <input type="text" name="amt" ng-model="amt" size="3"> 
            </td> 
            <td width="15%" valign="top">
              <button ng-click="orderPart(part.uniqueid, amt)" class="btn btn-sm btn-primary" ng-disabled="orderForm.$invalid">Order</button>
            </td>  
          </tr>
        </tbody>  
      </table>
      <h4 align="center" ng-if="PostDataResponse">
        <span class="label label-success">
          {{PostDataResponse}}
        </span>  
      </h4>  
    </div>  
  </body>
</html>

And its AngularJS code:

var partsInvApp = angular.module('partsInvApp', []);

partsInvApp.factory('redirectInterceptor', function($q,$location,$window){
    return {
        'response': function(response){
            if (typeof response.data === 'string' && response.data.indexOf("Enter your Single Sign-On credentials below") > -1) {
                $window.location = 'partsInventory.html';
                return $q.reject(response);

            }
            else {
              return response || $q.when(response);
            }  
        }
    }
});

partsInvApp.config(['$httpProvider', function($httpProvider) {
    $httpProvider.defaults.withCredentials = true;
    $httpProvider.interceptors.push('redirectInterceptor');
  }]);


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

    $scope.orderPart = function(partId,amt) {

      if (!amt) {
        alert ("Please inform quantity.");
        return;
      }

      console.log("Placing part order for " + amt + " items of part " + partId);

      var data = $.param({partId: partId,amount: amt});

      console.log(data);
        
      var config = {
        headers : {
          'Content-Type': 'application/x-www-form-urlencoded;charset=utf-8;'
        }
      };

      $http.post('http://myservice.ateam.com:7777/services/partsinventory/order', data, config)

      .success(function (data, status, headers, config) {
        $scope.PostDataResponse = data.result;
      })
      .error(function (data, status, header, config) {
        $scope.ResponseDetails = "Data: " + data +
          "<hr/>status: " + status +
          "<hr/>headers: " + header +
          "<hr/>config: " + config;
      });
    };    
});

partsInvApp.controller('userProfileController', function ($scope, $http) {

      $http.get('http://myservice.ateam.com:7777/services/userprofile/userinfo').success(function(data) {
        userinfo = data.result;
        $scope.firstName = userinfo[0].firstName;
        $scope.lastName = userinfo[0].lastName;
      });
});

Conclusion

In this post I’ve demonstrated how to handle the Same-Origin policy implemented by web browsers in the context of an SPA under the protection of Oracle Access Manager by using CORS headers plus the understanding of OAM-specific behavior. Combined with the usage of OAM’s Identity Assertion for secure identity propagation, this can be used in real world implementation scenarios, showcasing OAM again as a powerful tool for building modern and secure web applications.

Identity and Cloud Security A-Team at Oracle Open World

$
0
0

I just wanted to let everyone know that Kiran and I will be presenting with our good friend John Griffith from Regions Bank at Oracle Open World next week.

Our session is Oracle Identity Management Production Readiness: Handling the Last Mile in Your Deployment [CON6972]

It will take place on Wednesday, Sep 21, 1:30 p.m. – 2:15 p.m.  at  Moscone West – 2020.

In this session we will be discussing tips and techniques for a successful deployment of Oracle Identity Management. Learn about best practices for performance testing and tuning of Oracle Identity Manager and Oracle Access Manager, setting up production, ready monitoring, and failover and disaster recovery testing.

I encourage everyone to come by and participate.

Also we will be at Open World throughout the week and are always happy to have a conversation on Identity, Access, and Cloud Security with any and all comers.

Hope to see you there!

The importance of “orclguid” in Oracle Virtual Directory

$
0
0
Introduction This post will discuss the steps to configure the orclguid within Oracle Virtual Directory (OVD).  It is especially important when integrating OVD with Oracle Access Manager (OAM) and Weblogic Server (WLS).  I see many customers omitting this configuration which leads to errors in OAM.   Main Article All Lightweight Directory Access Protocol (LDAP) repositories […]

Overriding default permissions in OAAM Admin Console

$
0
0
OAAM has a predefined set of roles that governs the OAAM Admin Console. There is no “control panel” to add/delete/enable/disable access of different features of OAAM Admin Console to users based on their roles. OAAM defines and requires the following groups OAAMCSRGroup OAAMCSRManagerGroup OAAMEnvAdminGroup OAAMInvestigationManagerGroup OAAMInvestigatorGroup OAAMRuleAdministratorGroup OAAMSOAPServicesGroup More details about these groups can be […]

Part 1: How To Load Test OAM11g using Apache JMeter

$
0
0
Introduction Exciting, it is Go Live day, the system goes online, everything seems ok for a while, and then Kerplunk! Thousands of things could have happened and everyone scrambles to figure it out. What went wrong? My first question is, “Was a proper load test completed?” Yes, Load Test. Functionally the software may have worked, […]

A first look at POST data preservation in OAM 11g R2 PS1

$
0
0
Introduction In this post, we have a quick look at POST data preservation, a new feature introduced in the 11g R2 PS1 (or 11.1.2.1) version of Oracle Access Manager. We’ll explain the problem that this feature solves and walk through a simple example explaining how to configure and use the feature. This post is part […]

OAAM Admin Console Dashboard Update Frequency

$
0
0
There are three sections in the dashboard in OAAM Admin Console. The refresh time in section 1 and section 2 shown above can be configured by selecting the appropriate duration from the dropbox. There is no provision to select the update frequency of the items in section 3. This is actually controlled by different monitors […]

Webgate Reverse Proxy Farm

$
0
0
Introduction Some of our larger deployments are seeing the benefits of centralizing their Webgate deployments onto a server farm. This post discusses some of the architecture and recommendation when deploying such an architecture. Main Article First, what is a Webgate farm or Webgate Reverse Proxy farm? A Webgate farm is: A series of web servers […]

OAAM OTP code generation configuration

$
0
0
OAAM refers to the following properties for the One Time Password code generation bharosa.uio.default.otp.generate.code.length = 5 bharosa.uio.default.otp.generate.code.characters = 1234567890 The property bharosa.uio.default.otp.generate.code.length defines the length of the OTP code to generate. The property bharosa.uio.default.otp.generate.code.characters contains a string of characters that the OTP code can contain. OAAM API randomly chooses any character from the string defined […]

How to (correctly) make manual edits to oam-config.xml

$
0
0
Introduction Occasionally, it is necessary to make changes to OAM 11g configuration by directly updating the oam-config,xml file, rather than using the OAM console.  In this post, we describe the correct way to make changes to this file. This post is part of a larger series on Oracle Access Manager 11g called Oracle Access Manager […]

OAM WebGate connections through firewalls

$
0
0
Introduction In this post, we investigate a complication that can occur if you require a firewall between your WebGate agents and your OAM 11g servers within your deployment topology. We provide some guidance related to how to configure your WebGates in this case. This post is part of a larger series on Oracle Access Manager 11g […]

OAM LDAP connections through firewalls

$
0
0
Introduction In a previous post, we discussed some of the complications that can occur when a firewall is placed between WebGates and OAM Servers in a typical deployment. This post follows on from that discussion, to explore an analogous topic- firewalls between the OAM Server and the LDAP Identity Store. This post is part of […]

Multi-Data Center Implemenation in Oracle Access Manager

$
0
0
For obvious reasons, there is a high demand for Multi-Data Center (MDC) topology; which is now supported in Oracle Access Manager (OAM) 11g.  This post discusses some of the features of MDC as well as provide some detail steps on how to clone a secondary data center.  This post is based on R2PS1 code base.  […]

Oracle Access Manager – What’s new in PS2

$
0
0
Introduction Oracle Access Manager 11gR2 – PS2 is now out!  This post will cover some of the new features in PS2. There are six new features I will discuss: Dynamic Authentication Persistent Login (Remember Me) Policy Evaluation Ordering Delegated Administration Unified Administration Console Session Management Granular Idle Timeout Client Cookie based Session Main Article Dynamic […]

Strategies for managing OAAM to OAM connections in production

$
0
0
Many Oracle Access Management 11g customers opt to deploy a combination of Oracle Access Manager and Oracle Adaptive Access Manager using the Advanced Integration option. This combination of product features can provide strong, adaptive authentication and fraud mitigation for online applications. In this post, we examine a number of strategies for configuring the connectivity between […]

Part 2: Advanced Apache JMeter Stress Testing OAM and LDAP

$
0
0
Introduction In “Part 1: How To Load Test OAM11g using Apache JMeter” I talked about an example plan that could be used to load test OAM11g, which included some common configuration elements, some samplers for login, authorization, logout, and some listeners that provided result analysis.   In Part 2, I wanted to expand on an […]

Presenting the new IDM Deployment Wizard

$
0
0
Introduction With the recent IDM 11gR2PS2 release Oracle has developed a new deployment tool that aims to automate and reduce the time required to install and configure Oracle Identity and Access Management Components. In this post we are going to present the benefits, supported topologies and components and key points to keep in mind to […]

How To Display A Custom Error Page When the Access Server Is Down?

$
0
0
I have been asked several times over the years if there is a way to customize the following error message a User is presented in their Internet browser when the WebGate fails to contact any of the Access Servers. Oracle Access Manager Operation Error The WebGate plug-in is unable to contact any Access Servers. Contact […]

Identity Propagation from OAG to REST APIs protected by OWSM

$
0
0
Introduction This post describes the necessary configuration for propagating an end user identity from OAG (Oracle API Gateway) to REST APIs protected by OWSM (Oracle Web Services Manager). The requirements are: 1) Have a Java Subject established in the REST API implementation. 2) Prevent direct access to the REST API, i.e., only OAG should be […]
Viewing all 95 articles
Browse latest View live