OAuth verification for JWT Payload

We are having 2 major issues with setting up the OAuth verification in SailPoint.

  1. Grant_Type. Our Application requires a grant type of ‘urn:ietf:params:oauth:grant-type:jwt-bearer’ and SailPoinit only supplies a grant_type of ‘JWT_BEARER’
  2. Epoch values in the JWT payload. The JWT payload that our application requires 2 values that are epoch values based on the current date. The JWT Payload doesn’t allow any values that aren’t hard coded.

Has anyone else had similar issues, and found a solution?

Hi Chris,

I’m having trouble following what you are trying to accomplish. Our authentication docs make no mention of JWT_BEARER being a grant type. Can you please share more details about what you are trying to accomplish with links to any documentation that you have been using?

Hi Colin,
Sorry my description was a little sparse and I think I may not have been clear. This isn’t for the SailPoint connection, this is for a source system.
We’re setting up a Web Service Source. The OAuth 2.0 connection settings that the source requires a grant type of ‘urn:ietf:params:oauth:grant-type:jwt-bearer’. SailPoint has a Grant Type of JWT Bearer token, which gets sent to the source as ‘JWT_BEARER’(from the log file) The documentation I’m looking at is:
https://documentation.sailpoint.com/connectors/webservices/help/integrating_webservices/connection_settings.html
and the JWT Bearer Token section specifically.

The second issue is that the source requires 2 values in Epoch format, and they need to be:

  1. iat - current epoch value
  2. exp - future epoch value, which we’ve tested as iat+6000
    I’m not sure how to get those values dynamically.

Thanks,
Chris

Hello @chrisp,

Take a look at this rule implemented for custom authentication on a Web Service source. You can use this as a connector rule in the beforeOperationRule on any operation where the custom authentication is needed.

<?xml version='1.0' encoding='UTF-8'?>
<!DOCTYPE Rule PUBLIC "sailpoint.dtd" "sailpoint.dtd">
 <Rule language="beanshell" name="Custom Authentication" type="WebServiceBeforeOperationRule">
    <Description>
      This rule is used by the  Web Services connector before performing any operation like testconnection,
      aggregation etc.
    </Description>
    <Signature returnType="EndPoint">
      <Inputs>
        <Argument name="log">
          <Description>
            The log object associated with the SailPointContext.
          </Description>
        </Argument>
        <Argument name="context">
          <Description>
            A sailpoint.api.SailPointContext object that can be used to query the database if necessary.
          </Description>
        </Argument>
        <Argument name="application">
          <Description>The application whose data file is being processed.</Description>
        </Argument>
        <Argument name="requestEndPoint">
          <Description>The current request information contain header, body, context url, method type, response attribute map,
            successful response code
          </Description>
        </Argument>
        <Argument name="oldResponseMap">
          <Description>earlier response object </Description>
        </Argument>
        <Argument name="restClient">
          <Description>REST Client Object</Description>
        </Argument>
      </Inputs>
      <Returns>
        <Argument name="EndPoint">
          <Description>Updated EndPoint Object</Description>
        </Argument>
      </Returns>
    </Signature>
    <Source><![CDATA[
	  
		import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.util.io.pem.PemObject;
import org.bouncycastle.util.io.pem.PemReader;
import org.jose4j.jws.JsonWebSignature;
import org.jose4j.jwt.JwtClaims;
import org.jose4j.lang.JoseException;
import org.json.JSONException;
import org.json.JSONObject;
import com.squareup.okhttp.MediaType;
import com.squareup.okhttp.OkHttpClient;
import com.squareup.okhttp.Request;
import com.squareup.okhttp.RequestBody;
import com.squareup.okhttp.Response;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.StringReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.interfaces.RSAPrivateKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.Map;

		
		public String generateToken() throws NoSuchAlgorithmException, InvalidKeySpecException, IOException, JoseException {
        java.security.Security.addProvider(new BouncyCastleProvider());
		// Creating instance of PEM reader to get the Secret Key
		PemReader pemReader = new PemReader(new StringReader(SECRET_KEY));
		PemObject pemObject;
		pemObject = pemReader.readPemObject();

		//Getting the RSA instance for KeyFactory
		KeyFactory factory = KeyFactory.getInstance("RSA");
		byte[] content = pemObject.getContent();
		PKCS8EncodedKeySpec privKeySpec = new PKCS8EncodedKeySpec(content);
		RSAPrivateKey privateKey = (RSAPrivateKey) factory.generatePrivate(privKeySpec);

		//Getting the Unix time for UTC
		long unixTime = Instant.now().getEpochSecond();
		//Adding a minute to Unix time UTC

		long unixTime1 = Instant.now().plus(1, ChronoUnit.MINUTES).getEpochSecond();
	
			 JwtClaims claims = new JwtClaims();
			 claims.setClaim("iss","<issuer>");
			 claims.setClaim("aud","<aud_value>");
			 claims.setClaim("scope","<scopes>");
			 claims.setClaim("iat",unixTime);
			 claims.setClaim("exp",unixTime1);
			 
			 JsonWebSignature jws = new JsonWebSignature();
		        jws.setDoKeyValidation(false);
		        jws.setPayload(claims.toJson());
		        jws.setKey(privateKey);
		        jws.setAlgorithmHeaderValue("RS256");

		return jws.getCompactSerialization();
	}

	private  String jsonObjectCreator( String token) throws JSONException {
	log.error("Custom Auth Rule : Inside jsonObjectCreator()" );
		JSONObject obj=new JSONObject();
				obj.put("grant_type", "urn:ietf:params:oauth:grant-type:jwt-bearer");
				obj.put("assertion", token);
		String json = obj.toString();
		log.error("Custom Auth Rule : The json object is " +  json.toString() );
		return json;
	}
	
	
	
			 log.error("Custom Authentication : Rule Started");
	private static MediaType JSON= MediaType.parse("application/json; charset=utf-8");
	private static String SECRET_KEY = "<Secret Key>";

		String token= "";
        String jsonBody ="";
        String access_token="";
Map bodyMap = (Map) requestEndPoint.getBody();
		 try {
            token = generateToken();
            if(!token.isEmpty() || token !=null) {
					 log.error("Custom Authentication : JWT Assertion generated");
            	 jsonBody = jsonObjectCreator(token);
            }
            
            if(!jsonBody.isEmpty() || jsonBody !=null) {			
bodyMap.put("jsonBody", jsonBody);
            }
        }catch (JoseException e) {
			log.error("Custom Auth Rule : Exception occurred : " + e.toString());
		} catch (NoSuchAlgorithmException e) {
            log.error("Custom Auth Rule : Exception occurred : " + e.toString());
        } catch (InvalidKeySpecException e) {
            log.error("Custom Auth Rule : Exception occurred : " + e.toString());
        } catch (IOException e) {
            log.error("Custom Auth Rule : Exception occurred : " + e.toString());
        }catch (JSONException e) {
			 log.error("Custom Auth Rule : Exception occurred : " + e.toString());
		}finally {
           requestEndPoint.setBody(bodyMap);
			 log.error("Custom Authentication : In Finally block");
			return requestEndPoint;
        }
		
		 
		 ]]>
    </Source>
  </Rule>

I see you already have found out my thread from past: WS JWT authentication

It is documented poorly in connector guide but what I mentioned should work.

Thanks @chirag_patel .
I think that will work for the 2 epoch dates, for iat, DO I need to add that in the JWT Payload? And if so, what do I put in for the value?

I think I’m still going to have to do a WebServiceBeforeOperationRule to update the Grant Type. The target system will only allow a grant type of ‘urn:ietf:params:oauth:grant-type:jwt-bearer’ and SailPoint seems to only allow a grant_type of ‘JWT_BEARER’

Thanks,
Chris

@tyler_mairose Thanks, It looks like that’s what I’ll need to use.

Where is that defined in the source? I’m looking at one of the web service sources, and I don’t see beforeOperationRule.

@chrisp,

You can create the rule with the connector rule API using the type WebServiceBeforeOperationRule. create-connector-rule | SailPoint Developer Community

The connector Rule API takes the script as escaped Java code. I use this tool to help me out with the formatting. Free Online Java or .Net Escape / Unescape Tool - FreeFormatter.com

Once created, you can take the name of the rule and use it in the PATCH Source API. update-source | SailPoint Developer Community

Replace [*] with the index of operation. For example 0, 1, 2, etc. To get the index you can call the get Source API on your source and look at the operations under /connectorAttributes/connectionParameters/

[
    {
        "op": "replace",
        "path": "/connectorAttributes/connectionParameters/[*]/beforeRule",
        "value": "Example Rule"
    }
]

Hi Tyler,

Can i ask something? from where do i take these values (issuer,aud_value,scopes and Secret Key)
claims.setClaim(“iss”,“issuer”);
claims.setClaim(“aud”,“aud_value”);
claims.setClaim(“scope”,“scopes”);

private static String SECRET_KEY = “Secret Key”;

Are these values static like plain text or are dynamic and we need to create these inside the code?

Hey, while i did upload and replace the values inside the rule, im still getting that the iat and exp are not being defined nor replaced. Any ideas?

I’m having the same issue Augusto has explained in his reply, the iat and exp are not being defined nor replaced, have any info on this? Thanks.

Hello @Augusto-Dallape @germanduci,

The values for iat and exp are set to unixTime and unixTime1 as shown in the above code.

Are you claiming that the values for unixTime and unixTime1 are not being set? Could you provide more details or error logs?

Thanks,
Tyler

Hello @christosxygalidis,

The issuer, aud_value, scopes, and Secret Key are all static values that you will need to provide in the code.

Examples:

issuer: f687a4310ab23efe5e1ed6bcc98d213a9bffe6d27684db6450e0073e3249aca9
aud_value: https://sailpoint.com
scopes: admin_write admin_read

Tyler,

We are trying to connect to google APIs, as you may know it requires both iat and exp values. While I did upload your code and replaced values: iss, aud, scope with the values i also replaced the secret key with the unecrypted secret key we are using in the json.

Problems arise when trying to execute a test connection. It still asks for the iat and exp values, so what i did is set random values to the iat and exp in the jwt payload just to debug. Error:
Exception occurred in Test Connection. Error: Exception occurred while generating access token: Unable to generate access token. Response returned: { “error”: “invalid_grant”, “error_description”: “Invalid JWT: Token must be a short-lived token (60 minutes) and in a reasonable timeframe. Check your iat and exp values and use a clock with skew to account for clock differences between systems.” }

If i were to remove both iat and exp from the jwt payload i get a null pointer exception.

Here is the modified rule, attached to the test connection operation as a beforeRule: (Secret key is incomplete on purpose)

import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.util.io.pem.PemObject;
import org.bouncycastle.util.io.pem.PemReader;
import org.jose4j.jws.JsonWebSignature;
import org.jose4j.jwt.JwtClaims;
import org.jose4j.lang.JoseException;
import org.json.JSONException;
import org.json.JSONObject;
import com.squareup.okhttp.MediaType;
import com.squareup.okhttp.OkHttpClient;
import com.squareup.okhttp.Request;
import com.squareup.okhttp.RequestBody;
import com.squareup.okhttp.Response;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.StringReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.interfaces.RSAPrivateKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.Map;

		
		public String generateToken() throws NoSuchAlgorithmException, InvalidKeySpecException, IOException, JoseException {
        java.security.Security.addProvider(new BouncyCastleProvider());
		// Creating instance of PEM reader to get the Secret Key
		PemReader pemReader = new PemReader(new StringReader(SECRET_KEY));
		PemObject pemObject;
		pemObject = pemReader.readPemObject();

		//Getting the RSA instance for KeyFactory
		KeyFactory factory = KeyFactory.getInstance("RSA");
		byte[] content = pemObject.getContent();
		PKCS8EncodedKeySpec privKeySpec = new PKCS8EncodedKeySpec(content);
		RSAPrivateKey privateKey = (RSAPrivateKey) factory.generatePrivate(privKeySpec);

		//Getting the Unix time for UTC
		long unixTime = Instant.now().getEpochSecond();
		//Adding a minute to Unix time UTC

		long unixTime1 = Instant.now().plus(1, ChronoUnit.MINUTES).getEpochSecond();
	
			 JwtClaims claims = new JwtClaims();
			 claims.setClaim("iss","iam.gserviceaccount.com");
			 claims.setClaim("aud","https://oauth2.googleapis.com/token");
			 claims.setClaim("scope","https://www.googleapis.com/auth/admin.directory.user");
			 claims.setClaim("iat",unixTime);
			 claims.setClaim("exp",unixTime1);
			 
			 JsonWebSignature jws = new JsonWebSignature();
		        jws.setDoKeyValidation(false);
		        jws.setPayload(claims.toJson());
		        jws.setKey(privateKey);
		        jws.setAlgorithmHeaderValue("RS256");

		return jws.getCompactSerialization();
	}

	private  String jsonObjectCreator( String token) throws JSONException {
	log.error("Custom Auth Rule : Inside jsonObjectCreator()" );
		JSONObject obj=new JSONObject();
				obj.put("grant_type", "urn:ietf:params:oauth:grant-type:jwt-bearer");
				obj.put("assertion", token);
		String json = obj.toString();
		log.error("Custom Auth Rule : The json object is " +  json.toString() );
		return json;
	}
	
	
	
			 log.error("Custom Authentication : Rule Started");
	private static MediaType JSON= MediaType.parse("application/json; charset=utf-8");
	private static String SECRET_KEY = "MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCYLpFaG/1U34So";

		String token= "";
        String jsonBody ="";
        String access_token="";
Map bodyMap = (Map) requestEndPoint.getBody();
		 try {
            token = generateToken();
            if(!token.isEmpty() || token !=null) {
					 log.error("Custom Authentication : JWT Assertion generated");
            	 jsonBody = jsonObjectCreator(token);
            }
            
            if(!jsonBody.isEmpty() || jsonBody !=null) {			
bodyMap.put("jsonBody", jsonBody);
            }
        }catch (JoseException e) {
			log.error("Custom Auth Rule : Exception occurred : " + e.toString());
		} catch (NoSuchAlgorithmException e) {
            log.error("Custom Auth Rule : Exception occurred : " + e.toString());
        } catch (InvalidKeySpecException e) {
            log.error("Custom Auth Rule : Exception occurred : " + e.toString());
        } catch (IOException e) {
            log.error("Custom Auth Rule : Exception occurred : " + e.toString());
        }catch (JSONException e) {
			 log.error("Custom Auth Rule : Exception occurred : " + e.toString());
		}finally {
           requestEndPoint.setBody(bodyMap);
			 log.error("Custom Authentication : In Finally block");
			return requestEndPoint;
        }

image

Hope this helps,

Greetings

@Augusto-Dallape,

Thank you for the additional details!

Looking at the docs for Google oauth2, this error can be related to the system time being off. Can you check your VA system time? Google oauth2

I will continue to dig into why you are seeing this error.

I tried building the connector again but with custom auth, thus the rule is being ran. But its giving me an error on line 80 where the MediaType namespace could not be defined. I removed that line since i believe its not being used in the code, but now the rule just seems to be skipping something, im getting a 401 forbidden from google apis.

Any thoughts?

Thanks!

After more troubleshooting are you sure bouncycastle, jose4j are defined inside IdentityNow? The rule not even seems to be executed because no logging is made in ccg.log

Thanks!