M C · Oct 14

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!



2 0 7 247
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

In your case sigalg will be RS256 instead of HS256 and you'll need to call ##class(%OAuth2.JWKS).AddX509 instead of ##class(%OAuth2.JWKS).AddOct to use your private RSA keys.

It's definitely a bit of a challenge, but should be doable!

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

IS this solution worked for you ?

I am also trying the same with RSA512 but the JWT token has invalid signature

The one I posted above with hs256 did work, I didn't try RSA ones. Can you post your code that produces a wrong signature?

@Sergei Shutov 

Classmethod GetJWT() {
//Private key file    
    Set tFile=##Class(%File).%New("C:\keys\jwtRS512.key")
    Do tFile.Close()
    Set tStatus=tFile.Open("RU")
    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": "",
        "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