riosd
Community Participant

Canvas JWK Private Key for OAuth

Jump to solution

Hi!

I'm working on generating an access token in order to utilize the LTI 1.3 Advantage Services, but I'm a bit confused about one aspect of this grant flow and I'm hoping someone can provide some clarity. As described under 'Step 2: Request an access token' here:

https://canvas.instructure.com/doc/api/file.oauth.html#developer-key-setup

I'm trying to 'request an LTi access token using the client_credentials grant'. The link takes me here:

https://canvas.instructure.com/doc/api/file.oauth_endpoints.html#post-login-oauth2-token

Where the instructions state that I must create a request which is 'signed by an RSA256 private key with a public key that is configured on the developer key…'. The problem is, I can't figure out how to access the private key or use it to sign my request. I can create a JWT and an algorithm without issue, and the code looks like this:

myUrl = "https://canvas.test.instructure.com/api/lti/security/jwks";
JwkProvider provider = new UrlJwkProvider(new URL(myUrl));
Jwk jwk2 = provider.get(decodedJWT.getKeyId());
Algorithm algorithm2 = Algorithm.RSA256((RSAPublicKey) jwk2.getPublicKey(), null);
claimToken = JWT.create() .withAudience("https://"+CanvasApi.getCanvasURL()+"/login/oauth2/token") .withSubject(audComp)
.withIssuedAt(jwtDate)
.withIssuer(ltiURL)
.sign(algorithm2);

The problem seems to be that I can't create an algorithm for signing a JWT without a private key, and I can't figure out how to access the private key from the Public JWK found at https://canvas.test.instructure.com/api/lti/security/jwks .

Can anyone please explain to me how I am supposed to use the JWK private key to sign my request for the grant_type 'client_credentials'?

( I should mention that I did try sending along the same JWT I received during step three of the LTI launch (as described here: https://canvas.instructure.com/doc/api/file.lti_dev_key_config.html#step-3 ), but the values for 'sub' and 'aud' were not correct. )

I suspect that I'm missing something obvious here, so please feel free to reply with any tips or hints that might be relevant.

Thanks!
-David

Labels (2)
0 Kudos
1 Solution
matthew_buckett
Community Contributor

Hiya David,

In LTI 1.3 to send a message from one service to another a public/private key-pair is used to sign the message. When the LTI launch happens (user clicks on your tool in the LMS/VLE) Canvas is sending the message to your tool and so it signs the message with its private key and then you verify that the message is correct by checking it against its public key (the message is in a JWT).

When using the LTI advantage services you as the tool are trying to send a message to Canvas and again you need a public/private key-pair. However this time you need the private key (to sign the message) and Canvas needs to know what the public key is to verify the message.

When you initially configured your LTI 1.3 Developer Key it will have asked you to specify a "JWK Method" and then a "Public JWK" or "Public JWK URL". This is how you tell Canvas what the public key is (so that it can verify messages you send it). However this value doesn't need to be correct if you are only handling LTI launches where Canvas is sending the tool a message as it isn't used.

To have LTI advantage services work you need to generate your own public private keypair, then convert the public key part into a JWK and set this in your developer key. You can then use this private key to obtain a token that can then be used to access the LTI Advantage Services. We also cached these tokens so that we didn't have to keep renewing them with Canvas every time we wanted to make a LTI Advantage call.

The specification for obtaining tokens is: http://www.imsglobal.org/spec/security/v1p0/ but it's not very readable so I'd see how you can get on following Instructure's documentation.

Matthew

View solution in original post

3 Replies
matthew_buckett
Community Contributor

Hiya David,

In LTI 1.3 to send a message from one service to another a public/private key-pair is used to sign the message. When the LTI launch happens (user clicks on your tool in the LMS/VLE) Canvas is sending the message to your tool and so it signs the message with its private key and then you verify that the message is correct by checking it against its public key (the message is in a JWT).

When using the LTI advantage services you as the tool are trying to send a message to Canvas and again you need a public/private key-pair. However this time you need the private key (to sign the message) and Canvas needs to know what the public key is to verify the message.

When you initially configured your LTI 1.3 Developer Key it will have asked you to specify a "JWK Method" and then a "Public JWK" or "Public JWK URL". This is how you tell Canvas what the public key is (so that it can verify messages you send it). However this value doesn't need to be correct if you are only handling LTI launches where Canvas is sending the tool a message as it isn't used.

To have LTI advantage services work you need to generate your own public private keypair, then convert the public key part into a JWK and set this in your developer key. You can then use this private key to obtain a token that can then be used to access the LTI Advantage Services. We also cached these tokens so that we didn't have to keep renewing them with Canvas every time we wanted to make a LTI Advantage call.

The specification for obtaining tokens is: http://www.imsglobal.org/spec/security/v1p0/ but it's not very readable so I'd see how you can get on following Instructure's documentation.

Matthew

Thank you very much!  I was on the wrong track from the beginning, thinking that the Canvas JWK had to be configured on the Dev Key, when in fact that space should be dedicated to a JWK I must create, which I am working on now.  Thank you very much for the helpful information and links, I really appreciate it!

Thanks again, and have a great day!
-David

matthew_buckett
Community Contributor

There's some code for doing this in Java in https://github.com/oxctl/spring-security-lti13 with this class getting the token https://github.com/oxctl/spring-security-lti13/blob/master/src/main/java/uk/ac/ox/ctl/lti13/TokenRet... and then this class using the token to call the NRPS LTI Advantage service https://github.com/oxctl/spring-security-lti13/blob/master/src/main/java/uk/ac/ox/ctl/lti13/nrps/Nam...

It's all a little rough and ready but it does work.