cancel
Showing results for 
Show  only  | Search instead for 
Did you mean: 

OAuth 2.0 (client credentials) as a token-based protocol for API authentication

OAuth 2.0 (client credentials) as a token-based protocol for API authentication

 

Overview

The IdentityIQ API provides access to the IdentityIQ platform, allowing new opportunities for expanded innovation.

You can use your API to access IdentityIQ API endpoints, which allows to programmatically invoke IdentityIQ API to interact with objects within IdentityIQ.

This document provide insight to use OAuth 2.0 Authentication.

 

SCIM protocol

SCIM stands for System for Cross-Domain Identity Management, and it is an HTTP-based protocol that makes managing identities in multi-domain scenarios easier to support through a standardized RESTful API service. It provides a platform neutral schema and extension model for representing users, groups and other resource types in JSON format.

 

Supported HTTP Methods

  • GET
  • POST
  • PUT
  • DELETE

 

Authentication

Basic authentication

Beginning in IdentityIQ version 7.0, Patch 2, basic authentication is used to allow access to the API. Basic authentication is a simple technique for enforcing access controls to API resources because it doesn’t require session IDs, cookies, or login pages but instead uses standard fields in the HTTP header. For more information on basic authentication, please  see https://tools.ietf.org/html/rfc1945#section-11 and https://www.ietf.org/rfc/rfc2617.txt. Support for basic authentication will continue to exist in future releases.

 

OAuth 2.0

OAuth 2.0 Authentication will be supported in IdentityIQ version 7.1. Versions prior to 7.1 only support basic authentication.

 

OAuth client management page

The OAuth Client Management page has the following tabs and options:

• OAuth Client Management tab — displays a list of the current OAuth clients.

     - Create Button — creates an OAuth client that has a proxy user with an associated secret.

     - Secret Details icon — displays the secret for the an OAuth client.

     - Actions icons — Edit, Delete, Regenerate Secret

• General Settings tab

     - Access Token Expiration In Seconds

 

How to create an OAuth client

     1. From the top menu, navigate to the Gear icon > Global Settings > API Authentication.

     2. On the OAuth Client Management tab, click Create.

     3. In the OAuth Client dialog enter a unique name for Client Name and then enter a user name or select a user from the drop-down list for the Proxy User.

     4. Click Save to save your new OAuth client.

After your create an OAuth client, you can use it with the associated secret to log in and access the token for that proxy user.

 

How to get access token for OAuth

When we are enabling OAuth authentication, we should get access token before invoking IdentityIQ API.

We should use the following details to get access token from IdentityIQ:

 

Sample REST Client to get access token

  Client client = ClientBuilder.newClient();

  MultivaluedMap<String, String> formData = new MultivaluedHashMap();

  formData.add("grant_type", grantType);

  String secret = "Basic "+Base64.encodeBase64String(new String(clientID+":"+clientSecret).getBytes()); // we should use Base64 encode to encode client id and client secret

  Response  response = (Response) client.target(tokenURL). // token URL to get access token

  request(MediaType.APPLICATION_JSON). // JSON Request Type

  header( "Authorization", secret ) // Authorization header goes here

  .post(Entity.form(formData))  ;   // body with grant type

  String output = response.readEntity(String.class); // reading response as string format

 

Sample output

"expires_in": 1200,

  "token_type": "bearer",

  "access_token":"original token"

 

Access IdentityIQ API

When OAuth authentication is enabled for IdentityIQ API, we should get access token before consuming API. After we receive access token from IIQ, will access API with access token.

Sample REST client to access IdentityIQ API

Client client = ClientBuilder.newClient();

  Response response = (Response)client.target(apiURL). // API URL goes here (e.g. http://localhost:8080/identityiq/scim/v2/Applications/{Application id/Name}

  request(MediaType.APPLICATION_JSON). //Request type

  accept(accepType). // Response access type - application/scim+json

  header("Authorization", token).get(); // header with access token as authorization value

  String output = response1.readEntity(String.class); // reading response as string format

 

Sample output

{

"id": "2c9084ee5571ab87015571ac44810319",

"schemas": [

"urn:ietf:params:scim:schemas:sailpoint:1.0:Application"

],

"identAttr": {},

"applicationSchemas": [

{

"value": "2c9084ee5571ab87015571ac4482031b",

"$ref": "http://localhost:8080/iiq/scim/v2/Schemas/urn:ietf:params:scim:schemas:sailpoint:1.0:Application:Sch...",

"type": "account"

}

],

"name": "HR_Employees",

"features": [

"DIRECT_PERMISSIONS",

"NO_RANDOM_ACCESS",

"DISCOVER_SCHEMA"

],

"owner": {

"value": "2c9084ee5571ab87015571ac426d0316",

"$ref": "http://localhost:8080/iiq/scim/v2/Users/2c9084ee5571ab87015571ac426d0316",

"displayName": "HR_Employees App Owners"

},

"type": "Delimited File Parsing Connector",

"meta": {

"lastModified": "2016-06-21T01:42:49.362-05:00",

"created": "2016-06-21T01:36:03.074-05:00",

"location": "http://localhost:8080/iiq/scim/v2/Applications/2c9084ee5571ab87015571ac44810319",

"resourceType": "Application",

"version": "W/\"1466491369362\""

}

}

 

Troubleshooting

If you run into a problem, post it to the forum for help from the community. 

User does not have access error

Invalid_grant error

Labels (2)
Comments

We have written customer connector for connecting Rest webservice. Can we use OAuth2.0 for authentication service?

Thanks @hitesh_bhutani .  Sorry for miss-communication.  We have SOAP based web service not the REST webservice. Can we still do OAuth 2.0 for it? If yes can you please let us know what all changes we have to do.

Thanks in advance.

@narayan08 

I haven't worked on using oAuth with SOAP since it is mostly used for REST but i believe you can send the authorization token "Bearer {oAuth Token} in the SOAP header to get the token from your client and validate at your end if it is a valid token or not.

In your custom class you need to extended this OOB Class AuthenticationFilter and use it methods to validate the token and allow the user to access the API.

 

Validating if it is an oAuth Request:

You can use the OOB API(Blue Highlighted) from the extended class to validate if it is an oAuth token based request or not. Below example is extracting it from httpRequest but in your case it will from SOAP Header. The token needs to be sent by the client like below format in the header:

 

hitesh_bhutani_0-1582047008160.png

 

Read my comment in read below.

public boolean isAuthRequest(HttpServletRequest httpRequest)
{

boolean isBearerAuth = false;

//Instead of using getAuthHeader OOB method which takes httpRequest you need to get it from your SOAP request and set it as a string

String authHeader = getAuthHeader(httpRequest);
isBearerAuth = AuthenticationUtil.isBearerAuth(authHeader);
return isBearerAuth;
}

Once you have the token you need check if it is valid or not.

public Map<String,Object> bearerAuthenticate(HttpServletRequest httpRequest)
{
log.debug("Inside bearerAuthenticate");
boolean success = false;
Map<String,Object> result = new HashMap<String,Object>();
AuthenticationResult.Reason reason = AuthenticationResult.Reason.UNSPECIFIED;

result.put("success", success);

//Instead of using getAuthHeader OOB method which takes httpRequest you need to get it from your SOAP request and set it as a string
String authHeader = getAuthHeader(httpRequest);
OAuthAccessToken token = null;
try
{
log.debug("Creating context for MyContext");
ctx = SailPointFactory.createContext("MyContext"); //Create a new context if not available else use the available context

OAuthTokenValidator validator = new OAuthTokenValidator(ctx);
token = validator.authenticate(authHeader);

if (null != token)
{
String proxyUser = token.getIdentityId();

Identity user = ctx.getObjectByName(Identity.class, proxyUser);
OAuthClientService oAuthClientSvc = new OAuthClientService(ctx);
OAuthClient client = oAuthClientSvc.getClient(token.getClientId());
log.debug("Proxy Identity: "+user+"Client name: "+client.getName());

if(user != null) {
result.put("success", true);
ctx.setUserName(user.getName());
LoginBean.writeIdentitySession(new HttpSessionStorage(session), user);
Auditor.log("login", user.getName());
result.put("apiClient", client.getName());
}else {
result.put("success", false);
result.put("reason", "Unable to resolve proxy user");
}

}
}
catch (OAuthTokenExpiredException e)
{
reason = AuthenticationResult.Reason.OAUTH_TOKEN_EXPIRED;
result.put("success", false);
result.put("reason", reason.toString());
log.error("OAuth Token Expired: "+reason.toString());
}
catch (GeneralException localGeneralException) {

result.put("success", false);
result.put("reason", localGeneralException.getMessage());
log.error("Local general Exp occured: "+localGeneralException);
}catch (GeneralSecurityException e)
{
result.put("success", false);
result.put("reason", e.getMessage());
log.error("Unable to authenticate using Bearer Authentication: ", e);
}
return result;
}

 

I hope this helps!

Hi,

Is there any reference available for Java Script code ? We are building a plugin and invoking SCIM API to fetch some identity details . We want to to use OAUTH for authorization.

Hi @zali03

All you got to do is call a rest service from your JavaScript code to generate oAuth token and then using that using that token to call your SCIM API for users which follows the URL pattern: "http://localhost:8080/iiq/scim/v2/Users" . Refer to the below documentation on how to call OOB SCIM APIs using oAuth

 

https://community.sailpoint.com/t5/Product-Manuals/7-3-IdentityIQ-SCIM-API-Reference-pdf/ta-p/80756

 

Thanks,

Hitesh

Hi everyone,

I try to apply solution of extending AuthenticationFilter. For Some reason its never triggered. 

The question is: what am i missing

 

Here is my web.xml: 

<web-app>
<display-name>RESTful Jersey Hello World</display-name>
<servlet>
<servlet-name>jersey-serlvet</servlet-name>
<servlet-class>org.glassfish.jersey.servlet.ServletContainer</servlet-class>
<init-param>
<param-name>jersey.config.server.provider.packages</param-name>
<param-value>myRestIIQ4</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>

<servlet-mapping>
<servlet-name>jersey-serlvet</servlet-name>
<url-pattern>/oauth2/*</url-pattern>
</servlet-mapping>

<filter>
<filter-name>AuthenticationFilter</filter-name>
<filter-class>myRestIIQ4.MyAuthenticationFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>AuthenticationFilter</filter-name>
<url-pattern>/oauth2/*</url-pattern>
</filter-mapping>

</web-app>

 

@markg 

Are you trying to authenticate custom REST APIs in IIQ using oAuth2? If yes, can you tell me what will be the URL of your custom APIs your client will be calling?

Hi  If you want your clients to call your custom API which are oAuth2 protected, it will be a 2 step process.

Step 1: Client will call an API to request an oAuth token.

Step 2: Server will send the token generated in step 1 in header along with an API call. IIQ will validate the token and will give access to your custom API.

 

Step 1: Requesting an oAuth Token:

The below wrapper class will generate oAuth token for your client. You will have to provide them with a pair of client id and secret from iiq.(goto Global Settings->API Authenication)

package com.mycompany.test;

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;
import javax.ws.rs.Consumes;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.client.Client;
import javax.ws.rs.client.ClientBuilder;
import javax.ws.rs.client.Entity;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedHashMap;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.Response;

import org.apache.axis.encoding.Base64;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.codehaus.jackson.JsonGenerationException;
import org.codehaus.jackson.JsonParseException;
import org.codehaus.jackson.map.JsonMappingException;
import org.codehaus.jackson.map.ObjectMapper;
import org.codehaus.jackson.type.TypeReference;

@Path("/")
public class oAuthTokenResourceWrapper {

private static Log log = LogFactory.getLog(oAuthTokenResourceWrapper.class);

@Context private HttpServletRequest httpRequest;
@POST
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
@Path("oauth2/generateToken")

public Response getOAuthToken(Map<String,Object> m){
log.debug("***Entering getOAuthToken******");
String clientId = (String)m.get("clientId");
String secret = (String) m.get("secret");
String output = null;
HashMap<String,String> responseMap = null;
String context = httpRequest.getContextPath();
String url = httpRequest.getRequestURL().toString();
String arr [] = url.split(context);
String serverName = arr[0];
log.debug("Server name: "+serverName);

String targetDomain = serverName+context;
log.debug("Target Domain: "+targetDomain);
try {
Client client = ClientBuilder.newClient();
MultivaluedMap<String, String> formData = new MultivaluedHashMap();
formData.add("grant_type", "client_credentials");
String saltedSecret = "Basic "+Base64.encode(new String(clientId+":"+secret).getBytes());
Response response = (Response) client.target(targetDomain+"/oauth2/token"). // token URL to get access token

request(MediaType.APPLICATION_JSON). // JSON Request Type
header( "Authorization", saltedSecret ) // Authorization header goes here
.post(Entity.form(formData)) ; // body with grant type
output = response.readEntity(String.class); // reading response as string format
if(output.startsWith("<html>")){

for(int iCount=0;iCount<3;iCount++){
log.info("Token not generated for client id: "+clientId);
Thread.sleep(1000);
response = (Response) client.target(targetDomain+"/oauth2/token"). // token URL to get access token

request(MediaType.APPLICATION_JSON). // JSON Request Type
header( "Authorization", saltedSecret ) // Authorization header goes here
.post(Entity.form(formData)) ; // body with grant type
output = response.readEntity(String.class);
if(!output.startsWith("<html>"))
break;
}
}
log.debug("Client Validated: Token Generated Successfully: Not Pritned in the Logs: "+output);
responseMap = convertJSONToMap(output);

if(responseMap.get("error")!=null) {
log.error("oAuth Response: "+output);
return Response.serverError().entity(responseMap).build();

}

}catch (JsonGenerationException e) {
log.error("JSON Generation Expection: "+e);
return Response.serverError().entity("JSON Generation Expection: Error Occured while generating token: "+e.getMessage()).build();
} catch (JsonMappingException e) {
log.error("JsonMappingException: "+e);
return Response.serverError().entity("JsonMappingException: Error Occured while generating token: "+e.getMessage()).build();
} catch (IOException e) {
log.error("IOException: "+e);
return Response.serverError().entity("IOException: Error Occured while generating token: "+e.getMessage()).build();
}catch(Exception exp) {
log.error("Error Occured while generating token: "+exp);
return Response.serverError().entity("Error Occured while generating token: "+exp.getMessage()).build();
}
return Response.status(200).type(MediaType.APPLICATION_JSON).entity(responseMap).build();
}

public static HashMap<String,String> convertJSONToMap(String json) throws JsonParseException, JsonMappingException, IOException{
HashMap<String,String> map = new HashMap<String,String>();
ObjectMapper mapper = new ObjectMapper();
//Removing invisible chars causing error while converting
json = json.trim().replaceFirst("\ufeff", "");
// convert JSON string to Map
map = mapper.readValue(json, new TypeReference<HashMap<String,String>>(){});

log.debug("String JSON to Map: Not printing since it has token info: "+map);

return map;
}

}

 

Step 2:

         2.1  Class that will Validate the generated token.

package com.mycompany.test;

import java.io.IOException;
import java.security.GeneralSecurityException;
import java.util.HashMap;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import sailpoint.api.SailPointContext;
import sailpoint.api.SailPointFactory;
import sailpoint.integration.AuthenticationUtil;
import sailpoint.object.Identity;
import sailpoint.object.OAuthClient;
import sailpoint.rest.AuthenticationFilter;
import sailpoint.rest.AuthenticationResult;
import sailpoint.rest.HttpSessionStorage;
import sailpoint.server.Auditor;
import sailpoint.service.oauth.OAuthAccessToken;
import sailpoint.service.oauth.OAuthClientService;
import sailpoint.service.oauth.OAuthTokenExpiredException;
import sailpoint.service.oauth.OAuthTokenValidator;
import sailpoint.tools.GeneralException;
import sailpoint.web.LoginBean;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import com.google.gson.Gson;

public class ValidateToken extends AuthenticationFilter{

final static Log log = LogFactory.getLog(ValidateToken.class);

public Map<String,Object> validateToken(HttpServletRequest req) throws IOException{

Map<String,Object> result = null;
if(isAuthRequest(req)) {
log.debug("Is an auth request");
result = bearerAuthenticate(req);
log.debug("Authentication Result: "+result);
}

return result;
}
public boolean isAuthRequest(HttpServletRequest httpRequest)
{
boolean isBearerAuth = false;

String authHeader = getAuthHeader(httpRequest);
isBearerAuth = AuthenticationUtil.isBearerAuth(authHeader);
return isBearerAuth;
}

public Map<String,Object> bearerAuthenticate(HttpServletRequest httpRequest){

log.debug("Inside bearerAuthenticate");
boolean success = false;
Map<String,Object> result = new HashMap<String,Object>();
AuthenticationResult.Reason reason = AuthenticationResult.Reason.UNSPECIFIED;

result.put("success", success);
String authHeader = getAuthHeader(httpRequest);
OAuthAccessToken token = null;
SailPointContext ctx = null;
HttpSession session = null;
try
{

ctx = SailPointFactory.getCurrentContext();
log.debug("Getting Context");

OAuthTokenValidator validator = new OAuthTokenValidator(ctx);
token = validator.authenticate(authHeader);

if (null != token)
{
String proxyUser = token.getIdentityId();

Identity user = ctx.getObjectByName(Identity.class, proxyUser);
OAuthClientService oAuthClientSvc = new OAuthClientService(ctx);
OAuthClient client = oAuthClientSvc.getClient(token.getClientId());
log.debug("Proxy Identity: "+user+"Client name: "+client.getName());

if(user != null) {
result.put("success", true);
ctx.setUserName(user.getName());
session = httpRequest.getSession();
LoginBean.writeIdentitySession(new HttpSessionStorage(session), user);
Auditor.log("login", user.getName());
result.put("apiClient", client.getName());
result.put("proxyUser", user);
result.put("context", ctx);
}else {
result.put("success", false);
result.put("reason", "Unable to resolve proxy user");
}

}
}catch (OAuthTokenExpiredException e)
{
reason = AuthenticationResult.Reason.OAUTH_TOKEN_EXPIRED;
result.put("success", false);
result.put("reason", reason.toString());
log.error("OAuth Token Expired: "+reason.toString());
}
catch (GeneralException localGeneralException) {

result.put("success", false);
result.put("reason", localGeneralException.getMessage());
log.error("Local general Exp occured: "+localGeneralException);
}catch (GeneralSecurityException e)
{
result.put("success", false);
result.put("reason", e.getMessage());
log.error("Unable to authenticate using Bearer Authentication: ", e);
}
return result;
}

public static String convertToJson(String error) {

String strResp = null;
HashMap<String,String> errResp = new HashMap<String,String>();
errResp.put("error", error);
Gson gson = new Gson();
strResp = gson.toJson(errResp);

return strResp;

}

}

 

Step 2.2: Your Custom API Class

 

 

package com.mycompany.test;

import java.io.IOException;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;
import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import com.mycompany.test.ValidateToken;


@Path("myOAuthAPI")
public class myOauthAPI {
final static Log log = LogFactory.getLog(myOauthAPI.class);

@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
@GET
public Response myOAuthAPI(HttpServletRequest req) throws IOException{
ValidateToken vt = new ValidateToken();
Map<String,Object> result = vt.validateToken(req);

boolean success = (Boolean) result.get("success");
log.debug("Authentication Result: "+result);

if(!success) {
log.error("Access Restricted: "+result.get("reason"));

return Response.status(401).type(MediaType.APPLICATION_JSON).entity(result).build();

}
//YOUR BUSINESS LOGIC GOES BELOW THIS LINE AND YOU RETURN SUCESS ALONG WITH THE RESPONSE JSON
return Response.status(200).type(MediaType.APPLICATION_JSON).entity(result).build();
}

}

 

Next step is how to register your custom API to follow the url http://localhost:8080/identityiq/oauth2/myOAuthAPI

 

Create another class oAuthRestResources and register your custom api class to this class as below. Going forward if you create any new rest service class all you need to do is register the new class to this oAuthRestResources class.

package com.mycompany.test;

import sailpoint.rest.oauth.SailPointOAuthRestApplication;

public class oAuthRestResources extends SailPointOAuthRestApplication {

public oAuthRestResources() {
// TODO Auto-generated constructor stub
super();
register(myOauthAPI.class);
}

}

 

Final Step:

Find the below servlet entry in web.xml and replace sailpoint.rest.oauth.SailPointOAuthRestApplication with 

com.mycompany.test.oAuthRestResources

<servlet>
<servlet-name>JAX-RS OAuth REST Servlet</servlet-name>
<servlet-class>org.glassfish.jersey.servlet.ServletContainer</servlet-class>
<init-param>
<param-name>javax.ws.rs.Application</param-name>
<param-value>sailpoint.rest.oauth.SailPointOAuthRestApplication</param-value>
</init-param>
<!--
Disable WADL generation by default
-->
<init-param>
<param-name>jersey.config.server.wadl.disableWadl</param-name>
<param-value>true</param-value>
</init-param>
</servlet>

Good Luck!

Thanks.

@markg

Version history
Revision #:
6 of 6
Last update:
‎May 23, 2023 11:45 PM
Updated by:
 
Contributors