How to obtain an access token for accessing LTI Advantage Services?
I am receiving the following error:
{"error":"invalid_client","error_description":"Missing client"}
My curl headers are set:
--request POST
--header "Authorization: Basic B64 Encoded clientID:client secret (this is missing!)"
--header "Content-Type: application/x-www-form-urlencoded"
My URL is set:
https://auth.brightspace.com/core/connect/token?grant_type=client_credentials&client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer&scope=https://purl.imsglobal.org/spec/lti-ags/scope/score https://purl.imsglobal.org/spec/lti-nrps/scope/contextmembership.readonly https://purl.imsglobal.org/spec/lti-ags/scope/lineitem&client_assertion=JWTGOESHERE&redirect_uri=https://testing.com
When registering an LTI Advantage Tool, I cannot find the client secret. It only provides the following registration details:
- Client Id
- Brightspace Keyset URL
- Brightspace OAuth2 Access Token URL
- OpenId Connect Authentication Endpoint
- Brightspace OAuth2 Audience
- Issuer
Answers
-
Hi Jordan,
Thanks for the quick response. That got me through the "Missing client!" error.
Now I am running into a new error:
{"error":"invalid_grant","error_description":"Client is not authorized to set a 'kid' claim"}
If I remove the kid claim from the signed jwt, I get the following error:
{"error":"invalid_grant","error_description":"Error validating assertion: KeyId not found in token"}
We need the kid because we cycle our jwks monthly.
-
Hey Steve,
Thanks for raising this challenge up to our dev community. This was something I circulated internally before wanting to write a reply. What I think may be happening is malformed data being passed to the auth endpoint when requesting your token.
When you register your learning tool, you see all the value you had included in your original post:
- Client Id
- Brightspace Keyset URL
- Brightspace OAuth2 Access Token URL
- OpenId Connect Authentication Endpoint
- Brightspace OAuth2 Audience
- Issuer
And with some of those values you formulate your request. The IMS specification calls out this part of their documentation to define what you need to send: https://www.imsglobal.org/spec/security/v1p0#using-json-web-tokens-with-oauth-2-0-client-credentials-grant. The structure of what you need to send is a request with the following details:
{
"iss" : "",
"sub" : "",
"aud" : "",
"iat" : "",
"exp" : "",
"jti" : ""
}
We believe your error is a result of the values you are passing in the iss and sub fields. Both the iss and sub must equal the value of client_id from the platform. You receive that from the UI of your tool's registration.
For aud, this is also provided by the platform at tool registration. This value must be set to https://api.brightspace.com/auth/token. Please note that while we set that URL for the aud value, you are still sending the auth request as a whole to https://auth.brightspace.com/core/connect/token.
ian = your timestamp for the JWT
exp = the timestamp for JWT expiration
jti = a unique identifier
Try replacing some of the values coming over in your request and let me know if that does the trick for you!
-
Hey Steve,
Hmmm tricky. Have you tried running and comparing your requests to the IMS Reference Implementation, or the 1.3 Tsugi test tool? With those working test tools perhaps it will be easier to get your requests working when compared directly to those.
Tsugi is available --> https://github.com/tsugiproject/tsugi
IMS Reference Implementation --> https://lti-ri.imsglobal.org
For this specifically, can you send us over the full payload, header and request you are sending with this new error?
-
Headers
--header "Content-Type: application/x-www-form-urlencoded"
Request
--request POST
https://auth.brightspace.com/core/connect/token
Payload
'grant_type' => 'client_credentials',
'client_assertion_type' => 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer',
'scope' => 'https://purl.imsglobal.org/spec/lti-ags/scope/score https://purl.imsglobal.org/spec/lti-nrps/scope/contextmembership.readonly https://purl.imsglobal.org/spec/lti-ags/scope/lineitem',
'client_assertion' => ''SIGNED JWT IS USED HERE,
'redirect_uri' => 'OUR DOMAIN IS USED HERE',
-
Can you send me your JWT, directly if necessary? We need to see the contents of your payload as I believe it’s something in there that is causing the errors. Thank you!
-
-
Hey Jordan,
Passing kid in the payload was the issue. Thanks. I removed it and it is working now.
-
Hey Steve,
I tried comparing your request against the reference implementation that I linked in an earlier comment. I am first off noting some differences in the header and payload data which may be causing the issue. I think the biggest being that kid is defined in the header only, and not in the payload data. I am also seeing the kid visually looks like it's in a different format. What value are you using for your kid? JWKs is a good thing, as we do use those as well.
Thank you,
-
-
Hey @Steve Hellauer ,
That is awesome to hear! Really glad moving kid over to the header and out of the JWT header helped resolve the issue. In order to help future vendors avoid a similar issue in the future, you would be willing to share with us your final working request example? I will be sure we carefully documented the smyptoms, root cause, and resolution of the issue to ensure that we are able to help future LTI developers get through similar challenges. We are producing an FAQ for LTI, and would love to include this community post in it
Thank you for helping us to improve our Dev community!
-
Brilliant, thank you so much for sharing with our community! This will go a long way to help many many developers in the future.
-
Not sure if this helps but below shows a PHP snippet of how I am doing it. @Khalil Mirza
$kid = md5();
$config = [
'digest_alg' => 'sha256',
'private_key_bits' => 2048,
'private_key_type' => OPENSSL_KEYTYPE_RSA,
];
$res = openssl_pkey_new($config);
openssl_pkey_export($res, $privKey);
$pubKey = openssl_pkey_get_details($res);
$pubJWK = [
'kty' => 'RSA',
'e' => rtrim(str_replace(['+', '/'], ['-', '_'], base64_encode($pubKey['rsa']['e'])), '='),
'n' => rtrim(str_replace(['+', '/'], ['-', '_'], base64_encode($pubKey['rsa']['n'])), '='),
'kid' => $kid,
'alg' => 'RS256',
'use' => 'sig',
];
$pubKey = $pubKey['key'];
$data = [
'publickey' => $pubKey,
'privatekey' => $privKey,
'publicjwk' => json_encode($pubJWK),
'kid' => $kid,
'timecreated' => time(),
];
-
Hi,
I am able to all steps.
I am right now creating json web token.
Can you point ouy how to use jwks url to construct private and public keys.
I am able to create public keys only from jwks url.
-
Thanks for your help.
I am now getting the current JWT token but I am having another error when making request.
{
"error": "unauthorized_client",
"error_description": "This client does not have a registered JWKS endpoint"
}
I have checked the client id is registered and enabled.
-
Make sure your registered LTI Advantage Tool has a Keyset URL assigned to it, which is a publicly hosted endpoint for your tool's public key access. @Khalil Mirza