Does Content Server have built in JWT generation library

Hi all,

I have an upcoming project where I need to call a REST service from Oscript. This service uses JWT (JSON Web Token) for authentication and session management. Normally, I'd find one of many Java libraries that do this, and call them through JavaObject methods, but given the extra scrutiny that OpenText is giving to partners around 3rd party libraries, I'm looking for a better way to do this. I see lots of new drop-ins in the Oscript documentation, but just wondered if anyone has already written this in Oscript, better if it's already embedded into core.

Thanks in advance

-Hugh

Comments

  • I have a partial answer. At least in CS 22.4, there is a new drop-in called Cloud. It has a CreateJWT() method. I just tried it out where I pass it stringified headers, claims, and a client secret that I want to sign the token with. When I run it, I get the following errors:

    11:38:31,444 ERROR Cannot Read BIO private key

    11:38:31,444 ERROR Cannot Read BIO private key

    11:38:31,446 INFO Error: BIO Load Key Exception.

    What is the BIO private key requirement? I am passing in my key or does the key need to take on a particular format? My client secret is passed as a base64 string.

    My header looks like {"alg" : "HM256", "typ" : "JWT" }

    I have a payload/claim like: {"sub" : "<my api key>", "iat" : "<unix time now>", "exp" : "<5 minutes from now>"}

    I guess I need to understand what is BIO private key in this case?

    -Hugh

  • Documentum Foundation Classes (DFC) and the Documentum Client Library (DMCL) provide access to the entire set of Content Server features. Whereas JWT() method is used for generating content servers.

    Thanks


  • Hey Hugh,

    WR has a REST Client, you could invoke/call your REST service using that.

    In addition, WR allows you to call a server side EXE (run a small program that you have on the server), such as D:\OPENTEXT\appData\webreports\templates\JPEGCopy\JPEGCopy.bat

    I have personally used both methods and they work great. If you go the EXE way you can write your program in Java (barf) or C# using whatever libraries you want.

    Here is an example of calling an external program from a WR:

    [LL_REPTAG_"JPEGCopy/JPEGCopy.bat [LL_REPTAG_&jpegCopyHost /] [LL_REPTAG_OTCSTICKET ESCAPEURL /] [LL_REPTAG_&jpegID /] [LL_REPTAG_&targetID /]" RUNSHELL /]

    I had to write a small C# program download JPEGs and upload them as JPGs because Blazon doesn't create a compressed PDF if the extension is JPEG. It only works if it is JPG.

    Not sure why anybody would go with a Custom OScript Module these days. To expensive & difficult to maintain .

    Regards,

    -MC

  • My header looks like {"alg" : "HM256", "typ" : "JWT" }

    Hi Hugh - the docs I have over here show three parameters being required for the header (the third being the key ID). On the off chance that your docs don't match mine: here is what I see over here:

    Assoc       checkVal
    Assoc       jwtClaim
    Assoc       jwtHeader
    Assoc       jwtSignature
    
    // NOTE THAT THE PRIVATE KEY HAS BEEN MODIFIED. A VALID KEY MUST BE PROVIDED TO RUN THE CODE.
    
    Assoc credential = Assoc{
    "type": "service_account",
    "project_id": "otcs-cloud",
    "private_key_id": "8f435000a83a2dc6f6befeec468e49b8c3817b72",
    "private_key": "-----BEGIN PRIVATE KEY-----\nMIIEvAIBADANBgkqhkiG9w0BAQEFAASCBK.....6Cg==\n-----END PRIVATE KEY-----\n",
    "client_email": "conetnt-server@otcs-cloud.iam.gserviceaccount.com",
    "client_id": "106117477893356774801",
    "auth_uri": "https://accounts.google.com/o/oauth2/auth",
    "token_uri": "https://oauth2.googleapis.com/token",
    "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
    "client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/conetnt-server%40otcs-cloud.iam.gserviceaccount.com"
    }
    
    Boolean ok = TRUE
    
    jwtHeader.( "alg" ) = "RS256"
    jwtHeader.( "typ" ) = "JWT"
    jwtHeader.( "kid" ) = credential.private_key_id
    
    String      strJwtHeader = $WebLL.JSONUtils.ToJSON( jwtHeader )
    
    jwtClaim.( "iss" ) = credential.client_email
    jwtClaim.( "scope" ) = "https://www.googleapis.com/auth/devstorage.full_control"
    jwtClaim.( "aud" ) = credential.token_uri
    jwtClaim.( "exp" ) = Date.Systime() + 36000 // in seconds; must be less then one hour
    jwtClaim.( "iat" ) = Date.Systime()
    
    String      strjwtClaim = $WebLL.JSONUtils.ToJSON( jwtClaim )
    
    Dynamic result = Cloud.CreateJWT ( credential.private_key, strJwtHeader, strjwtClaim )
    
    
    if ( IsNotError( result ) && IsDefined( result ) )
    
        jwtSignagure = result
    
        echo( 'jwtSignagure:', jwtSignagure )
    
        checkVal = GetToken( jwtSignagure )
    
        ok = checkVal.ok
    
    elseif( IsNotError( result ) )
    
        Echo( Error.ErrorToString( result ) )
    
    end
    
    if ( ok )
    
        Echo( 'success:', checkVal.access_token )
    
    else
    
        Echo( 'failire:', checkVal.errMsg )
    
    end
    
    echo( 'done' )
    
    
    function Assoc GetToken( String jwtSignagure )
    
        Assoc       result      
        Assoc       headers     
        RestClient  rc      
        Assoc       query       
        String      body
    
        Assoc       checkVal = signatureproviders::SignatureRESTUtils.ParseURL( "https://oauth2.googleapis.com/token" )
    
    
        if( checkVal.ok )
    
            rc = RestClient.NewSecure( checkVal.host, checkVal.port, checkVal.path )
    
            headers.( 'Content-Type' ) = "application/x-www-form-urlencoded"
    
            body = "grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer&assertion=" + jwtSignagure
    
            checkVal = rc.POST( query, body, Length( body ), headers )
    
            if ( checkVal.status == 200 )
    
                result = $WebLL.JSONUtils.ParseJSON( checkVal.content )
                result.ok = TRUE
    
            else
    
                result.ok = FALSE
                result.errMsg = checkVal.content
    
            end
    
        end
    
        return result
    
    end
    


    @siegel: vi is an editor with two modes: one which destroys your input and the other which beeps at you

  • Hi Dave,

    I'm using whatever the client gives me. They gave me an API key and a client secret with which to sign the token. I think the client secret is already in Base64. Is the ntent that whatever my client secret is, I need to have it both as a hex string and a base64 equiv? In the meantime, I've been experimenting with a Java library from auth0. It's more ideal to use Oscript drop-ins so I don't have to explain to the Cloud Engineering time about a 3rd party library.

    -Hugh

  • Dave Carpeneto
    edited December 30, 2022 #7

    Is it Google you're connecting to? If so then https://developers.google.com/identity/protocols/oauth2/service-account#httprest provides a overview of the requests / responses, and what is required from the client. This lines up pretty well with the snippet in our docs that I cut & pasted earlier. From there it looks like the "kid" parameter in the header is optional / used if OAuth2 is not in play.

    They gave me an API key and a client secret 

    I'm not sure what these map to; from our example / Google's page it looks like what is needed is private_key, private_key_id, and client_id. However this is not within my purview; I'm just reading docs & trying to figure out our code base 😉

    @siegel: vi is an editor with two modes: one which destroys your input and the other which beeps at you

  • This isn't a Google service but a 3rd party. The API key is part of the payload, the client secret is used to sign the token. For this 3rd party service, there is no requirement for anything in the header except for the alg and typ fields.

    It could be that this particular drop-in is too specific to the big three (AWS, Azure, GCP) so the expectation that you have both a client secret and a base64 encoded version of the same is somehow baked into this drop-in which is too bad.

    I get the feeling that OpenText doesn't intend anyone outside internal OT Dev to use some of these newer drop-ins, although I do have the intention to rewrite some code that currently uses Apache http client via JavaObject using the REST Client class now that it has matured for a few releases.

    -Hugh

  • JWT Generation: Implement the code to generate the JWT token based on your authentication credentials. This typically involves creating a JSON payload, signing it with a secret key, and encoding it as a JWT token.

    REST Service Call: Use Oscript's built-in HTTP functions or libraries to make the REST API call. You can use the HttpRequest and HttpResponse classes to handle HTTP requests and responses.

    Adding Authorization Header: Set the Authorization header in your HTTP request to include the JWT token you generated in step 1. This header will be used for authentication with the REST service.

  • I solved the problem by using a Java library and calling it with JavaObjects. The Cloud drop-in turned out to be too focused on Amazon WS, and not intended for any purpose outside of that.