Question
· Oct 14, 2021

Making a JWT/OAuth2.0

Hello all,

First time post and newer to Cache/ObjectScript in general, please keep it in mind! 

Some background: I need to build part of a production whose goal is to send patients emails based on appointment status as a daemon-based approach. I have to use Microsoft Graph to accomplish this, which you need an access token to use. To get the access token, I have to go through the OAuth2.0 client credentials grant flow, which involves the creation of a signed JWT assertion using the RS256 algorithm.  

I was looking at the %OAuth2.JWT class to accomplish this, via its ObjectToJWT() method. My question is in order to use this class does my production have to be set up as an OAuth2.0 client? I've seen some relevant docs into setting that up but am not sure if I necessarily have to configure the production as an OAuth2.0 client just because it's getting an access token. Any other ways to create a JWT would also be appreciated.

Thank you!

Discussion (8)1
Log in or sign up to continue

This is what I do to generate JWTs.

ClassMethod GetJWT(username As %String) As %String
{
// Calculate the issued at and expiration dates
set iat = ##class(%OAuth2.Utils).TimeInSeconds($ztimestamp,0)
set exp = ##class(%OAuth2.Utils).TimeInSeconds($ztimestamp,3600*24*7)
 
// Define token payload
set payload = {
"iat": (iat),
"iss": "zpmhub",
"exp": (exp),
"user": (username)
}
set secret=$get(^JWTSecret)
if secret="" {
set secret=##class(%PopulateUtils).StringMin(64,64)
set secret=$tr($p($SYSTEM.Encryption.Base64Encode(secret,1),"=",1),"+/","-_")
set ^JWTSecret=secret
}
set JOSE("sigalg")="HS256"
do ##class(%OAuth2.JWKS).AddOct("HS256",secret,.RemotePrivate)
do ##class(%OAuth2.JWT).ObjectToJWT(.JOSE,payload,,.RemotePrivate,.jwt)
quit jwt
}

Hello and thanks for this well thought-out response!

In terms of adding to the headers, do you know what method I would use to add them? The Microsoft identity platform requires three different parameters for what I am trying to do and the research I've done into the %OAuth2.JWT.ObjectToJWT method I can't seem to figure out how to add them, or maybe it doesn't have that capacity? 

If you are talking about header (first part of 3-part JWT token) ObjectToJWT should generate correct values for you with alg, typ and x5t values set correctly.

If you are talking about payload (second part), I construct it after // Define token payload line in the example above and then pass it as 2nd argument to ObjectToJWT - you can add any data you need there.

@Sergei Shutov 

Classmethod GetJWT() {
//Private key file    
    Set tFile=##Class(%File).%New("C:\keys\jwtRS512.key")
    Do tFile.Close()
    Set tStatus=tFile.Open("RU")
    $$$ThrowOnError(tStatus)
    Set tPrivateKey=tFile.Read()
    Do tFile.Close()
    set iat = ##class(%OAuth2.Utils).TimeInSeconds($ztimestamp,0)
    set exp = ##class(%OAuth2.Utils).TimeInSeconds($ztimestamp,300)

    set tClaims = {
        "sub": "LS6Y4hA6KtDgWwTck5STRdGGVPCPV4Cp",
        "iss": "LS6Y4hA6KtDgWwTck5STRdGGVPCPV4Cp",
        "jti": (tGUID),
        "aud": "https://int.api.service.nhs.uk/oauth2/token",
        "exp": (exp)
        }
    Set JOSE("sigalg")="RS512"
    Set tX509=##class(%SYS.X509Credentials).%New()
    Do tX509.PrivateKeySet(tPrivateKey)
    Set tStatus=##class(%OAuth2.JWKS).AddX509("RS512",tX509,.tPrivate)
    Set arr=##class(%DynamicAbstractObject).%FromJSON(tPrivate)
    Set iter=arr.%GetIterator()
    Set newprivate={}
    ///Need to set KID at same index with other keys so re-creating new 
    While iter.%GetNext(.key,.value) {
            Set newprivate.kty=value.keys.kty
            Set newprivate.n=value.keys.n
            Set newprivate.e=value.keys.e
            Set newprivate.d=value.keys.d
            Set newprivate.p=value.keys.p
            Set newprivate.q=value.keys.q
            Set newprivate.dp=value.keys.dp
            Set newprivate.dq=value.keys.dq
            Set newprivate.qi=value.keys.qi
            Set newprivate.alg=value.keys.alg
            Set newprivate.kid=..KID
    }
    Set JWKSPrivate={"keys":[]}
    Do JWKSPrivate.keys.%Push(newprivate)
    Set tPrivate=JWKSPrivate.%ToJSON()
    ///Customize this method to pass KID that will go to ..GetJWK 3rd argument
    Set tStatus= ##class(KCH.TIE.Helper.PDSUtil).ObjectToJWT(.JOSE,tClaims,tPrivate,,.pJWTToken,..KID)
    W !, pJWTToken
    
}

Hi M C,

Currently, you cannot add custom headers to a JWT. On top of this, the x5t header parameter (which, as I'm sure you're aware, is required for Microsoft Identity platform JWT assertions) is not added to JWTs in ObjectToJWT(). Unfortunately, this means that 2 of the 3 options Microsoft gives for the client credentials flow will not work. If you need the client credentials flow and can use the first case in that Microsoft page you linked to (access token request with a shared secret), that's probably your best bet.

(Also, to circle back to your original question, while you do not need to set your production up as an OAuth client to use JWTToObject() and ObjectToJWT(), you should do so in this case because you are using it as an OAuth client.)

Sorry to be the bearer of bad news, but I hope this helps,
McLean