Quantcast
Channel: ATeam Chronicles
Viewing all 31 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.


Authenticating to OIM SCIM server using an OAM-generated SAML identity assertion

$
0
0

In a previous post previous post I provided a brief introduction to SCIM. In this post I’m going to dive right in and give an example of using the OIM SCIM services and securing them with OAM.

Why would you want to use OIM SCIM services?

There are many reasons, however I will focus on one particular use case in this post – building a custom UI to access OIM. OIM’s out-of-the-box UI provides a broad set of features to cover most use cases, and includes a customisation facility which enables it to be extended in various ways: you can change the visual appearance (logo, colour scheme), add new fields, change the labels of fields and buttons, add additional instructional text, etc. However, if you have very involved plans for UI customisation – such as changing the flow between screens, etc. – there comes a point when building your own custom UI from scratch works out to be a better solution than customising the out-of-the-box UI.

Now, when it comes to building a custom UI, or indeed any custom code which needs to access OIM services, you have two main options:

  1. Calling the OIM Java APIs
  2. Calling the SCIM web services

Up until 11.1.2.3, (2) was not an option. The advantages of SCIM web services over the OIM Java API:

  • Calling the OIM Java APIs is only supported from clients written in Java. So if you want to write your custom UI in a non-JVM based language, such as node.js or C#, that isn’t possible.
  • Calling the OIM Java APIs works well provided your custom UI is hosted on the same WebLogic version and the same Java version. However, as soon as you try to run your custom UI on a different Java version (e.g. Java 8 instead of Java 7) or a different app server (e.g. Glassfish, Tomcat, JBOSS), or a different version of WebLogic (e.g. WLS 12c instead of WLS 10.3.6), you may run into difficulties which can be difficult or impossible to solve.
  • SCIM is an industry standard technology; the OIM Java API is proprietary.
  • The upcoming Oracle Identity Cloud Service (IDCS) will use SCIM. By writing your custom UI to talk to SCIM, you potentially could switch over the backend from OIM to IDCS in the future (if you decide to migrate your on-premise identity solution to the cloud.)

I should make clear that I am talking here about code which runs external to OIM and which needs to access OIM services. If you are talking about extension code which runs inside OIM – such as a a custom event handler or a custom ICF connector – in those cases you should use the appropriate Java APIs for that type of extension, not SCIM.

How do you authenticate to OIM SCIM REST services?

OIM SCIM REST services relies on OWSM (Oracle Web Services Manager) for security. OWSM is Oracle’s strategic product for securing web services (including REST services) at the endpoint-level.

Central to OWSM is a powerful language for defining policies that control how a web service is secured; many policies are shipped out-of-the-box, and new policies can be defined to meet your specific needs. These policies are then attached to web service endpoints.

OIM ships with a policy known as oracle/multi_token_noauth_rest_service_policy to protect the SCIM REST web services. This policy is an OR combination of two out-of-the-box policies:

  • oracle/multi_token_rest_service_policy: this policy supports protecting REST web services with any of the following authentication mechanisms: HTTP Basic Authentication, SAML 2.0 Bearer Token, OAM Webgate, SPNEGO (Kerberos/WNA), and JWT
  • oracle/no_authentication_service_policy: this policy permits anonymous (unauthenticated) access when none of the above authentication mechanisms are used

Note that even though this policy permits unauthenticated access at the OWSM layer, the OIM SCIM server itself will restrict unauthenticated access to most of its services; the exception is those services specifically designed to be accessed without authentication, such as password reset.

Building a sample SCIM client app

In the following steps, I will walk you through building a very basic client app for the OIM SCIM server. This client app will just be a JSP page which displays some of the attributes of the logged in user. To secure this client app, we will use a SAML assertion generated by the OAM server; we will then configure the OIM SCIM server to accept that assertion.

The sample app in this example is a JSP. I have tested it on WebLogic, but it should run on other Java app servers and servlet containers too. But, even though this example is Java-based, the same basic steps can be followed on any other development platform – e.g. node.js or ASP.NET.

Prerequisites

I assume you have the following installed:

  • OIM 11.1.2.3 and OAM 11.1.2.3, running in separate WebLogic domains
  • OHS instance fronting both OIM and OAM
  • OAM WebGate installed in that OHS instance

Step 1: Creating the example application

First off, we will create a directory to hold our application. I’d generally recommend doing this using an IDE such as JDeveloper or Netbeans or Eclipse, and using a build tool such as Maven or Gradle. However, to keep this tutorial simple, we’ll just manually create the required files and ZIP them up into a WAR by hand, so all you will need to follow this tutorial will be a ZIP utility, your favourite text editor, and admin access to OIM, OAM and OHS instances.

I’m going to refer to the root directory of our sample app as $APP_TOP. Now you need to create subdirectories $APP_TOP/WEB-INF and $APP_TOP/WEB-INF/lib.

Since SCIM is a JSON-based protocol, we are going to need a JSON parsing library. There are many available; for this example, I’ve chosen to use the javax.json API (JSR 353) with the Glassfish implementation. If you prefer to use another JSON library (e.g. Douglas Crockford’s org.json reference implementation), it is not much work to change my example appropriately. Download the following two JAR files and place them in the $APP_TOP/WEB-INF/lib/ directory: http://central.maven.org/maven2/org/glassfish/javax.json/1.0.4/javax.json-1.0.4.jar and http://central.maven.org/maven2/javax/json/javax.json-api/1.0/javax.json-api-1.0.jar.

Next, create a file $APP_TOP/WEB-INF/web.xml and insert the following content:

<?xml version="1.0" encoding="UTF-8"?>
<web-app
        version="2.5"
        xmlns="http://java.sun.com/xml/ns/javaee"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
  <welcome-file-list>
    <welcome-file>index.jsp</welcome-file>
  </welcome-file-list>
</web-app>

Now we create the file $APP_TOP/index.jsp and insert the following content:

<%@page contentType="text/html; charset=UTF-8" %>
<%@ page import="java.io.*" %>
<%@ page import="java.net.*" %>
<%@ page import="java.nio.charset.*" %>
<%@ page import="java.util.*" %>
<%@ page import="java.util.zip.*" %>
<%@ page import="javax.xml.bind.*" %>
<%@ page import="javax.json.*" %>
<%@ page import="javax.json.stream.*" %>
<%!

public static String escapeHTML(String s) {
	return s.replaceAll("&","&amp;").replaceAll("<","&lt;").replaceAll(">","&gt;").replaceAll("\"","&quot;");
}

public static String gzipBase64(String s) throws Exception {
	byte[] b = s.getBytes(StandardCharsets.UTF_8);
	ByteArrayOutputStream baos = new ByteArrayOutputStream();
	GZIPOutputStream gzos = new GZIPOutputStream(baos);
	gzos.write(b);
	gzos.flush();
	gzos.finish();
	return DatatypeConverter.printBase64Binary(baos.toByteArray());
}

public static JsonObject getMyProfile(String token) throws Exception {
	URL url = new URL("http://OIMHOST:14000/idaas/im/scim/v1/Me");
	HttpURLConnection conn = (HttpURLConnection)url.openConnection();
	conn.setRequestProperty("Authorization","oit " + token);
	StringBuilder sb = new StringBuilder();
	JsonReader rdr = Json.createReader(conn.getInputStream());
        try {
		return rdr.readObject();
	} finally {
		rdr.close();
	}
}

public static String prettyPrint(JsonObject obj) {
	Map<String, Object> cfg = new HashMap<String,Object>(1);
	cfg.put(JsonGenerator.PRETTY_PRINTING, true);
	StringWriter sw = new StringWriter();
	JsonWriterFactory wf = Json.createWriterFactory(cfg);
	JsonWriter jw = wf.createWriter(sw);
	jw.writeObject(obj);
	jw.close();
	return sw.toString();
}

public static String htmlesc(String input) {
	return input.replace("&","&amp;").replace("<","&lt;").replace(">","&gt;").replace("\"","&quot;");
}
	
%>
<%
	JsonObject profile = getMyProfile(gzipBase64(request.getHeader("OAM_IDENTITY_ASSERTION")));

%>
<html>
<head>
<title>Show My Profile</title>
</head>
<body>
<h1>Show My Profile</h1>
Find below your user profile.
<table border="1" cellspacing="0">
<tr><th align="left" valign="top">User Name</th><td><%=htmlesc(profile.getString("userName"))%></td></tr>
<tr><th align="left" valign="top">Display Name</th><td><%=htmlesc(profile.getString("displayName"))%></td></tr>
<tr><th align="left" valign="top">User Type</th><td><%=htmlesc(profile.getString("userType"))%></td></tr>
<tr><th align="left" valign="top">Active?</th><td><%=htmlesc(String.valueOf(profile.getBoolean("active")))%></td></tr>
<tr><th align="left" valign="top">JSON Profile</th><td><pre><%=htmlesc(prettyPrint(profile))%></pre></td></tr>
</table>
</body>
</html>

Replace OIMHOST in the above with the hostname of your OIM managed server.

Now we ZIP up our app into a WAR file:
cd $APP_TOP
zip -r ../ShowMyProfile.war *

Then we go to the OIM domain WLS console and deploy our WAR to the OIM managed server (e.g. oim_server1 or wls_oim1). Note that in Production use, it is advisable to deploy the custom app to its own managed servers in a separate WebLogic domain; however, for simplicity we are just using the OIM domain here.

Step 2: Modify the OHS Config

You need to have an OHS instance in front of the OIM WebLogic domain in order to protect it with the OAM WebGate. In this example, we are going to use the same OHS instance and WebGate for our custom UI. You could use a separate OHS for the custom UI instead of relying on the same one used for OIM – that would have the advantage of minimising the dependencies of the custom UI on the OIM environment, which might simplify upgrades and patching. However, to keep things simple, in this example we will use the same OHS.

Steps:

  1. Edit the file $IDM_TOP/config/instances/ohs1/config/OHS/ohs1/moduleconf/idm.conf.
  2. Look for the section <VirtualHost *:7778>.
  3. At the end of that section, just before the closing </VirtualHost>, insert the following block:
    <Location /ShowMyProfile>
       SetHandler weblogic-handler
       WebLogicHost myoimserver1.example.com
       WebLogicPort 14000
    </Location>
  4. Replace myoimserver1.example.com with the hostname of your OIM managed server machine, and if necessary replace 14000 with the correct managed server port. Save the file.
  5. Restart OHS by running $IDM_TOP/config/instances/ohs1/bin/opmnctl stopall followed by $IDM_TOP/config/instances/ohs1/bin/opmnctl startall

Step 3: Protect our custom app in OAM

We need to protect our custom app with OAM. We also need to configure OAM to provide our custom app with a SAML assertion for the authenticated user. Steps:

  1. Go to OAM console, i.e. http://OAMHOST:PORT/oamconsole
  2. Login as OAM system administration user
  3. On application security tab, Launch Pad, select “Application Domains” under the “Access Manager” tile
  4. Click “Search”
  5. Click on the “IAM Suite” link to edit it
  6. Go to Resources tab, click “Create”
  7. Fill out the “Create Resource” screen as follows, then click “Apply”:
    1. Type = HTTP
    2. Host Identifier =
      choose the appropriate host identifier
      (if you are using the default IAM Suite app domain,
      you probably want to choose IAMSuiteAgent as the host identifier)
    3. Resource URL = /ShowMyProfile/.../*
    4. Protection Level = Protected
    5. Authentication Policy = Protected HigherLevel Policy
    6. Authorization Policy = Protected Resource Policy
  8. Go back to you Application Domain tab, e.g. IAM Suite, then select the “Authorization Policies” tab
  9. Click on “Protected Resource Policy” to edit it
  10. Go to the “Responses” tab
  11. Check if the “Identity Assertion” checkbox is ticked
    If it is unticked, tick it then click “Apply” to save

Step 4: Import the OAM SAML certificate into the OIM domain

When the “Identity Assertion” checkbox is enabled in our authorization policy, the OAM WebGate will inject a special HTTP Header called OAM_IDENTITY_ASSERTION containing a SAML assertion. We use this SAML assertion to authenticate to OIM SCIM REST – we GZIP it, Base64 encode it (see the gzipBase64 method in index.jsp), then insert it as an Authorization: oit ... HTTP header in our SCIM request. The SAML assertion generated by OAM is signed using a private key; in order for OIM to trust that digital signature, we need to export the corresponding digital certificate from the OAM domain and import it into the OIM domain:

  1. Find out what your .oamkeystore password is using the following steps:
    1. Start $OAM_ORACLE_HOME/common/bin/wlst.sh
    2. Enter the command connect(), then enter your WebLogic admin credentials (e.g. weblogic user and associated password), and your OAM domain AdminServer URL (e.g. t3://OAMHOST1:7001)
    3. Enter the command print(mbs.invoke(ObjectName('com.oracle.jps:type=JpsCredentialStore'),"getPortableCredential",["OAM_STORE","jks"],["java.lang.String","java.lang.String"]).get("password"))
      (Note this command is all on one line. Also note that all we are doing here is calling a publically documented API using WLST, oracle.security.jps.mas.mgmt.jmx.credstore.JpsCredentialMXBean.getPortableCredential(String,String). See also the My Oracle Support Note Unable To Retrieve .oamkeystore Password In OAM 11.1.2.3.0 Using ListCred (Doc ID 2031132.1) which contains steps to call the same API using the EM System MBean Browser instead of WLST.)
    4. Note down the OAM keystore password printed, for example 3mredj8x4hg506xoifx1sk7snt
  2. Validate the keystore password retrieved is correct by running:
    $JAVA_HOME/bin/keytool -list -keystore $OAM_DOMAIN_HOME/config/fmwconfig/.oamkeystore -storetype JCEKS -storepass 3mredj8x4hg506xoifx1sk7snt -alias assertion-cert
    (Replace 3mredj8x4hg506xoifx1sk7snt with the actual password retrieved above)
  3. To export the assertion-cert certificate from $OAM_DOMAIN_HOME/config/fmwconfig/.oamkeystore, run the following command: $JAVA_HOME/bin/keytool -exportcert -keystore $OAM_DOMAIN_HOME/config/fmwconfig/.oamkeystore -storetype JCEKS -storepass 3mredj8x4hg506xoifx1sk7snt -alias assertion-cert -file /tmp/assertion-cert.cer
  4. Next we need to repeat the steps in (1) for the OIM domain. So start WLST again and connect to the OIM AdminServer instead of the OAM AdminServer, still as the weblogic user. This time the command to enter is: print(mbs.invoke(ObjectName('com.oracle.jps:type=JpsCredentialStore'),"getPortableCredential",["oracle.wsm.security","keystore-csf-key"],["java.lang.String","java.lang.String"]).get("password"))
    (Note this command is all on one line. Also note that it is the same command which we used for the OAM domain, just the map name is oracle.wsm.security instead of OAM_STORE, and the key entry is called keystore-csf-key instead of jks.)
  5. Note down the password printed in the previous step, for example tljig9hest0jpdgwnoqdl8dd45
  6. Validate the keystore password retrieved is correct by running:
    $JAVA_HOME/bin/keytool -list -keystore $OIM_DOMAIN_HOME/config/fmwconfig/default-keystore.jks -storetype JKS -storepass tljig9hest0jpdgwnoqdl8dd45
    (Replace tljig9hest0jpdgwnoqdl8dd45 with the actual password retrieved above)
  7. Import the certificate into the OIM domain keystore: $JAVA_HOME/bin/keytool -importcert -keystore $OIM_DOMAIN_HOME/config/fmwconfig/default-keystore.jks -storetype JKS -storepass tljig9hest0jpdgwnoqdl8dd45 -file /tmp/assertion-cert.cer -alias assertion-cert -trustcacerts

Step 5: Configure OWSM to trust the SAML issuer “OAM User Assertion Token”

OAM issues SAML assertions with the issuer set to OAM User Assertion Token. However, by default, OWSM only accepts www.oracle.com as a valid SAML issuer. So in this step, we need to tell OWSM to accept OAM User Assertion Token as a SAML issuer:

  1. Start $OIM_ORACLE_HOME/common/bin/wlst.sh
  2. Enter the command connect(), then enter your WebLogic admin credentials (e.g. weblogic user and associated password), and your OIM domain AdminServer URL (e.g. t3://OIMHOST1:7101)
  3. Enter the command setWSMTokenIssuerTrust("dns.hok","OAM User Assertion Issuer",[])
    (Note this command is documented in the OWSM 11.1.1.9 Admin Guide, chapter 14, section “Configuring an Issuer and its DN List Using WLST”)
  4. You will need to wait approximately 10 minutes due to caching in OWSM before this setting takes effect. (Alternatively, you can restart your OIM managed server.)

(Acknowledgement: Thanks to my colleagues Jiandong Guo and Andre Correa for recommending this approach to me.)

Step 6: Testing

  1. In your web browser go to http://OHSHOST:OHSPORT/ShowMyProfile/. Replace OHSHOST with your OHS server hostname, and OHS port with your OHS port (e.g. 7778)
  2. You should see the OAM login page. Log in as a valid user (e.g. oamadmin)
  3. You should see a page showing some of your user attributes (“User Name”, “Display Name”, “User Type”, “Active?”), and also the pretty-printed JSON of your full SCIM user profile.

Note

The URLs and file paths in this post are from a LCM-based OAM-OIM non-clustered deployment, please make sure you use the proper URLs and file paths from your environment when implementing the solution describe in this post.

Authenticating to the OIG REST API from an OAM-protected web app

$
0
0

The objective of this post is to describe how a web app protected by an OAM WebGate can authenticate to the OIG REST APIs. In a previous blog post, I provided detailed steps to do the same thing for the SCIM REST APIs; now in this blog post I will explain how the same approach can be applied to the OIG REST APIs too, with only some minor changes. The reason we can use essentially the same approach for both the OIG REST and SCIM REST APIs is that both use the same OWSM policy (oracle/multi_token_noauth_rest_service_policy) for security. You might use these steps if you were building a custom web interface to OIM, or integrating OIM into a portal (as a custom portlet).

Rather than repeat the steps from that post, I will refer you back to it with the following changes:

  1. Firstly, make sure you have applied Bundle Patch 11.1.2.3.161018 (Patch 24326201)
  2. Follow the instructions in the Patch 24326201 README to install OIG REST APIs and test them
  3. Otherwise follow the steps in my original blog post; however, in “Step 1: Creating the example application”, replace the index.jsp with the alternate version given below.

The index.jsp file needs to be changed to point to the OIG REST API instead of the SCIM REST API. Additionally, the example given was retrieving the /Me SCIM resource which represents the authenticated user; however, that operation has no direct equivalent in the OIG REST API. Instead what we do in this example is parse the SAML assertion to find the username, and then pass that as filter criteria to the /iam/governance/selfservice/api/v1/users endpoint.

<%@page contentType="text/html; charset=UTF-8" %>
<%@ page import="java.io.*" %>
<%@ page import="java.net.*" %>
<%@ page import="java.nio.charset.*" %>
<%@ page import="java.util.*" %>
<%@ page import="java.util.zip.*" %>
<%@ page import="javax.json.*" %>
<%@ page import="javax.json.stream.*" %>
<%@ page import="javax.xml.bind.*" %>
<%@ page import="javax.xml.parsers.*" %>
<%@ page import="org.w3c.dom.*" %>
<%@ page import="org.xml.sax.*" %>

<%!
 
public static String escapeHTML(String s) {
    return s.replaceAll("&","&amp;").replaceAll("<","&lt;").replaceAll(">","&gt;").replaceAll("\"","&quot;");
}
 
public static String gzipBase64(String s) throws Exception {
    byte[] b = s.getBytes(StandardCharsets.UTF_8);
    ByteArrayOutputStream baos = new ByteArrayOutputStream();
    GZIPOutputStream gzos = new GZIPOutputStream(baos);
    gzos.write(b);
    gzos.flush();
    gzos.finish();
    return DatatypeConverter.printBase64Binary(baos.toByteArray());
}

public static String getUserName(String samlAssertion) throws Exception {
    StringReader sr = new StringReader(samlAssertion);
    InputSource is = new InputSource(sr);
    DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
    dbf.setNamespaceAware(true);
    DocumentBuilder db = dbf.newDocumentBuilder();
    Document doc = db.parse(is);
    org.w3c.dom.Element root = doc.getDocumentElement();
    NodeList nl = root.getElementsByTagNameNS("urn:oasis:names:tc:SAML:2.0:assertion", "NameID");
    String userName = ((org.w3c.dom.Element)nl.item(0)).getAttribute("SPProvidedID");
    return userName;
}
 
public static JsonObject getMyProfile(String token, String userName) throws Exception {
    URL url = new URL("https://OIMHOST:14001/iam/governance/selfservice/api/v1/users?q=User::Login eq " + userName);
    HttpURLConnection conn = (HttpURLConnection)url.openConnection();
    conn.setRequestProperty("Authorization","oit " + token);
    StringBuilder sb = new StringBuilder();
    JsonReader rdr = Json.createReader(conn.getInputStream());
        try {
        return rdr.readObject();
    } finally {
        rdr.close();
    }
}
 
public static String prettyPrint(JsonObject obj) {
    Map<String, Object> cfg = new HashMap<String,Object>(1);
    cfg.put(JsonGenerator.PRETTY_PRINTING, true);
    StringWriter sw = new StringWriter();
    JsonWriterFactory wf = Json.createWriterFactory(cfg);
    JsonWriter jw = wf.createWriter(sw);
    jw.writeObject(obj);
    jw.close();
    return sw.toString();
}
 
public static String htmlesc(String input) {
    return input.replace("&","&amp;").replace("<","&lt;").replace(">","&gt;").replace("\"","&quot;");
}
     
%>
<%
    String samlAssertion = request.getHeader("OAM_IDENTITY_ASSERTION");
    String token = gzipBase64(samlAssertion);
    String userName = getUserName(samlAssertion);
    JsonObject profile = getMyProfile(token, userName);
 
%>
<html>
<head>
<title>Show My Profile</title>
</head>
<body>
<h1>Show My Profile</h1>
Find below your user profile.
<pre><%=htmlesc(prettyPrint(profile))%></pre>
</body>
</html>

SOA Security – Follow-up Question

$
0
0

I got an email from a response to a post that I did after last year's OOW.http://oracleaccessmanagement.blogspot.com/2008/09/soa-security-adt-or-crocodile-filled.htmlThe question is basically - "How do I propagate the identity from inside an EJB to a W...

How to Debug SAML Token Profile on WLS

$
0
0

K asks:On the client site I have two Credential Mapping Provider (SAML and PKI) and two Credential Mapping (Key Pair and certificate) configured and the request produced well (I monitor it with wireshark). But the server side returns with error (Caused...

Teach an Old Dog New Tricks – SAML Name Mappers

$
0
0

A few weeks ago, I said that I was sure that there was some way to get custom attributes passed in and out of SAML Assertions for the purpose of Federated Authorization. Well, at that time I was under the impression that this would require the creatio...

Bearer Confirmation Method (Huh! What is it good for…)

$
0
0

For starters, allow me to introduce myself. My name is Brian Eidelman and I am a new member of the Fusion Middleware Architecture Group (a.k.a the A-Team) and a new contributor to this blog. Since the memo has gone out that in addition to security we will be discussing dogs now, I'd like to introduce my faithful companion Coco.

Now without further ado, my post:

The WSS SAML Token Profile Specification defines several “confirmation methods” by which the contents of the SAML assertion can be linked to the SOAP message content itself. In other words the method of proving that the assertion really goes with the message that it is being sent in.

The “bearer” confirmation method is sort of peculiar in that it defines no process at all for proving the link between the contents of the assertion and the message content. Rather, the link is to be implicitly trusted.

At this point you may be saying to yourself, if there is no means of verifying that the SAML assertion goes with the message, then what good is it?

Well, the trust can be implicit for any number of reasons. It could just be that trusting developers created the service. More likely however, the link between the SAML assertion and the message can be implicitly trusted by the service because the integrity of the link has been delegated to some other external factor; usually to the network level.

In some cases we could be talking about an internal network setup so that all requests to the service are guaranteed to come from a tamper proof trusted client (if you aren’t buying into this, just humor me). In other cases we could be talking about SSL with 2-way authentication. The point is that the service can trust that only a proper trusted client can successfully get a message to it in the first place.

Now at this point you might be thinking to yourself, fine but then how is SAML with bearer confirmation different than just including a username token with no password in the message header.
Well, SAML with bearer confirmation offers a few additional advantages over the plain old username token. Foremost, the assertion can contain not just a username (subject) but also a bundle of attributes that can further serve to identify the user, define a users’ roles, or be otherwise consumed by the service. In addition, the assertion does capture the notion of what entity is “issueing” the assertion (asserting the user identity). Lastly, some SOA stacks may not be able to handle a username token with no password.
So after all that, what is the bearer confirmation good for? Given that it allows us to utilize assertions without the hassles and costs that come with the signing and key references that are a part of the other confirmation methods, bearer is the perfect confirmation method for basic identity propagation to or between internal services. A similar use case where bearer may fit the bill is identity propagation from trusted intermediary that maybe be doing the real authentication to the service over a securely established network connection.

SAML Bearer Confirmation – An example using OWSM Client Policy

$
0
0

This is an extension of the discussion started by Brian in his inaugural post here at the FusionSecurity blog. Brian and I, along with other members of the A-Team were out at HQ getting some training on the SOA Security capabilities in 11g, and I wante...


OWSM Client Policies and SAML – Simpler is Better

$
0
0

Classic example of me being "too clever by half".

From the OWSM Documentation

Looks like you can just set the username as a property


URL localURL =
new URL("https://.../MyWebServicePort?WSDL");

QName name = new QName("http://view.team2.oracle.com/","MyWebServiceService");

myWebServiceService =
new MyWebServiceService(localURL,name);

final weblogic.wsee.jws.jaxws.owsm.SecurityPolicyFeature securityFeature =
new weblogic.wsee.jws.jaxws.owsm.SecurityPolicyFeature("policy:oracle/wss_saml_token_bearer_over_ssl_client_policy");

WebServiceFeature[] features =
new WebServiceFeature[] {securityFeature};

MyWebService myWebService =
myWebServiceService.getMyWebServicePort(features);


Map<String,Object> reqContext = ((BindingProvider) myWebService).getRequestContext();
reqContext.put( BindingProvider.USERNAME_PROPERTY, "jdoe");


// Add your code to call the desired methods.
System.out.println(myWebService.helloThere("From a Java client - JDOE"));

This "AH HA" eliminates the need for the code to get the JAAS Subject and the configuration of the jps-config.xml and system-jazn-data.xml. Much better.

Just to round out, here is the client for SAML sender-vouches.


URL localURL = new URL("http://oamwindows:7001/Team2-ViewController-context-root/MyWebServicePort?WSDL");

QName name = new QName("http://view.team2.oracle.com/","MyWebServiceService");

myWebServiceService =
new MyWebServiceService(localURL,name);

final weblogic.wsee.jws.jaxws.owsm.SecurityPolicyFeature securityFeature =
new weblogic.wsee.jws.jaxws.owsm.SecurityPolicyFeature("policy:oracle/wss10_saml_token_with_message_integrity_client_policy");

WebServiceFeature[] features =
new WebServiceFeature[] {securityFeature};

MyWebService myWebService =
myWebServiceService.getMyWebServicePort(features);

Map<String,Object> reqContext = ((BindingProvider) myWebService).getRequestContext();
reqContext.put( BindingProvider.USERNAME_PROPERTY, "jdoe");


reqContext.put(ClientConstants.WSSEC_KEYSTORE_LOCATION, "c:/wstest/alice.jks");
reqContext.put(ClientConstants.WSSEC_KEYSTORE_PASSWORD, "password" );
reqContext.put(ClientConstants.WSSEC_SIG_KEY_ALIAS, "alice" );
reqContext.put(ClientConstants.WSSEC_KEYSTORE_TYPE, "JKS" );
reqContext.put(ClientConstants.WSSEC_SIG_KEY_PASSWORD, "password" );
reqContext.put(ClientConstants.WSSEC_ENC_KEY_ALIAS, "alice" );
reqContext.put(ClientConstants.WSSEC_ENC_KEY_PASSWORD, "password" );
reqContext.put(ClientConstants.WSSEC_RECIPIENT_KEY_ALIAS,"bob");

System.out.println(myWebService.helloThere("From a Java client"));

I did all of my testing with the demo CA that ships with WebLogic Server. I find the CertGen and the ImportPrivateKey
tools to be really useful in building small samples. I created two keystores - alice.jks and bob.jks. Alice for the client and Bob for the server. This is all standard stuff, but one thing to remember that OWSM has only a single identity and trust store...not separate stores as in J2SE or WLS, so I had to add the CertGenCA.der to both keystores.

Beyond on that, really pretty simple. I configured the bob.jks keystore for the WebLogic Server Domain from EM - Domain (Right Click) -> Security -> Security Provider Configuration. Scroll down to Keystore...configure bob.jks as the store. Restart the server. Send the message and it just works!

OWSM stand-alone Client to OWSM Server doing SAML Bearer and SAML Sender-Vouches - check!

Identity Cloud Services and Weblogic Federation with Virtual Users and Groups

$
0
0
Introduction Federation is a well-known pattern and has been discussed at length on this blog. Almost every vendor or cloud provider out there supports Federation and it’s been around for quite some time now. In this blog post, I will talk about Federation again, but this time in combination with Weblogic’s Virtual Users and Groups. […]

Silently federate from your SAML IdP or OpenID Connect Provider to IDCS

$
0
0
Introduction As you may know IDCS can operate as both a SAML IdP and a SAML SP at the same time – a use case known as an IdP Proxy or IdP Chaining. This is useful in a bunch of situations, but the most common is when you want users to login to your on […]
Viewing all 31 articles
Browse latest View live