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!
This is what I do to generate JWTs.
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")
$$$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
We have exactly the same need as original poster and this sure is bad news if that's still the case? And I'm so new to this that I don't know of a quick way to test it (yet, but I'm trying to understand this more in depth). Maybe somebody can confirm if newer versions can actually do it better, ie. add custom headers and the x5t header parameter?
Also, @Matthew Cummings if you are still active here, would you like to tell if you at the end managed to create what you needed (and which is probably exactly what we need, too)