IBM Verify

IBM Verify

Join this online user group to communicate across Security product users and IBM experts by sharing advice and best practices with peers and staying up to date regarding product enhancements.

 View Only
  • 1.  JWT Module issue mode

    Posted 08/13/20 08:10 AM
    Hi,

    Regarding the claim aud in the jwt it says in the RFC https://tools.ietf.org/html/rfc7519#section-4.1.3
    In the general case, the "aud" value is an array of case-sensitive strings, each containing a StringOrURI value

    But when you generate the jwt it's always a string. Is there some magic separator to make it an array? Or do i need to take care of this in the JavaScript mapping?

    Doc: https://www.ibm.com/support/knowledgecenter/SSPREK_9.0.7/com.ibm.isam.doc/config/concept/con_jwt_issue_mode.html

    ------------------------------
    Regards Mikael
    ------------------------------


  • 2.  RE: JWT Module issue mode

    Posted 08/14/20 02:39 AM
    Hi Mikael,

    The way I solved this was indeed in the Mapping Rule itself, I'll only go about the "extraction" phase where I retrieve the stored attribute and transform such that it is always an array:
    // Map for claim types
    const CUSTOM_ATTRIBUTES_TO_ACCESS_TOKEN = {
      "aud": {
        "claim_type": "array",
        "claim_name": "aud"
      },
      "azp": {
        "claim_type": "string",
        "claim_name": "azp"
      }
    };
    
    /**
     * Collects the configured attributes for the specific client, this happens when you exchange an "Authorization Code" for tokens, this is most probably a call to /token endpoint.
     * @param cid the client id
     */
    function collect_wanted_attributes(cid) {
      let ret_obj = {};
      if (cid == null) return ret_obj;
      // Collect stored attributes, this gives an iterator
      let attr_itr = stsuu.getAttributes();
      // Iterate all attributes
      while (attr_itr.hasNext()) {
        let attr = attr_itr.next();
        let attr_name = "" + attr.getName();
        if (CUSTOM_ATTRIBUTES_TO_ACCESS_TOKEN.hasOwnProperty(attr_name)) {
          // Check if the custom attribute should be an array
          if (CUSTOM_ATTRIBUTES_TO_ACCESS_TOKEN[attr_name]["claim_type"] === "array") {
            logmsg(MRN, "DEBUG", "Mapping attribute values into an array");
            claim_value = [];
            let attr_vals = attr.getValues();
            // No values, ignore this
            if (attr_vals.length == 0) continue;
            // There are two cases here, either it returns a String[], or a String which was joined with ","
            if (attr_vals.length > 1) {
              // This is a Java String[], so we loop it, and push each value into the claim
              logmsg(MRN, "SENSITIVE", "Transforming Java String[]: " + JSON.stringify(claim_value, null, 2));
              for (let i = 0; i < attr_vals.length; i++) {
                claim_value.push("" + attr_vals[i]);
              }
              logmsg(MRN, "SENSITIVE", "Resulting array: " + JSON.stringify(claim_value, null, 2));
            } else {
              // This is the joined string, so we split it and assign the result to the claim
              logmsg(MRN, "SENSITIVE", "Array attribute value = " + attr.getValues()[0]);
              if ("" + attr.getValues()[0] != "") {
                claim_value = ("" + attr.getValues()[0]).split(",");
                logmsg(MRN, "SENSITIVE", "Resulting array: " + JSON.stringify(claim_value, null, 2));
              }
            }
          } else {
            logmsg(MRN, "DEBUG", "Mapping attribute value into regular js string.");
            // probably not safe here
            claim_value = "" + attr.getValues()[0];
          }
          if (attr_name == "oidc_username") {
            // In the refresh grant, we must source "sub" from oidc_username attribute, so first check if "sub" is not already set
            if (sub == null) {
              ret_obj[CUSTOM_ATTRIBUTES_TO_ACCESS_TOKEN[attr_name]["claim_name"]] = claim_value;
            }
          } else {
            ret_obj[CUSTOM_ATTRIBUTES_TO_ACCESS_TOKEN[attr_name]["claim_name"]] = claim_value;
          }
        }
      }
      logmsg(MRN, "SENSITIVE", "Collected the following additional claims: " + JSON.stringify(ret_obj));
      return ret_obj;
    }

    And before constructing the JWT, I "collect' my attributes first:

    let additional_claims = collect_wanted_attributes(client_id);
    let at = buildJwtAccessToken(stsuu, oauth_client, additional_claims);


    And in the phase where you build the JWT Token (in my case the access token), I provided an additional parameter (this is built on top of Leo's code):

    function buildJwtAccessToken(stsuu, oauth_client, provided_claims) {
      // redacted ...
      let standard_claims = {
        "exp": expire,
        "iat": now,
        "sub": "" + sub,
        "aud": "" + oauth_client.getClientId(),
      }
    
      // Claims is the final object
      let claims = standard_claims;
      // Merge the standard claims with the provided claims into the final object
      for (let provided_claim in provided_claims) {
        claims[provided_claim] = provided_claims[provided_claim];
      }
      // redacted ...
    }

    The resulting JWT should contain an array, always.

    Keep in mind this is an extract from my code, functions such as "logmsg()" will probably not work for you.

    Hope it helps.



    ------------------------------
    Dries Eestermans
    IS4U
    ------------------------------



  • 3.  RE: JWT Module issue mode

    Posted 08/14/20 02:44 AM
    Hi Dries,

    Thanks for the reply.

    I solved it in a similar way. I was just hoping in this case that ISAM would follow the rfc so you only code where you need custom stuff.



    ------------------------------
    Regards Mikael
    ------------------------------