OAuth & scopes
Authorization problems — scopes that do not appear in the Canvas UI, repeated authorization prompts, and students who cannot connect Canvas.
These appear during the Canvas OAuth "Connect Canvas" flow, or when the tool tries to use the Canvas API on someone's behalf.
A scope does not appear in the Developer Keys UI
Some Canvas environments do not surface every endpoint scope in the UI. This most
often affects url:GET|/api/v1/login/session_token, which the SEB session handoff
requires.
Use the API OAuth key, not the LTI key
Update the API OAuth Developer Key configured as CANVAS_API_CLIENT_ID — not
the separate LTI 1.3 Developer Key. The exact scope is
url:GET|/api/v1/login/session_token. Do not substitute
url:GET|/login/session_token, a logins scope, or a generic login permission.
Canvas's normal editor can omit this scope. Use the root-account Developer Keys
REST API to read the existing key, merge the required scope into its current list,
and write that list back. Canvas documents
PUT /api/v1/developer_keys/:id
and developer_key[scopes] for this purpose.
Create a temporary administrator token
Have a root-account Canvas administrator create a short-lived personal access
token with permission to manage Developer Keys. In Terminal, start bash, then
run the following preflight. Replace the Canvas origin and API OAuth Developer Key
ID; 1 is the usual root account ID but must match your Canvas root account.
set -euo pipefail
export CANVAS_ORIGIN='https://your-school.instructure.com'
export ROOT_ACCOUNT_ID='1'
export DEVELOPER_KEY_ID='REPLACE_WITH_THE_API_OAUTH_KEY_ID'
export SESSION_SCOPE='url:GET|/api/v1/login/session_token'
read -r -s -p 'Temporary root-admin Canvas token: ' CANVAS_ADMIN_TOKEN
printf '\n'
KEY_JSON="$(
curl --fail-with-body --silent --show-error \
-H "Authorization: Bearer ${CANVAS_ADMIN_TOKEN}" \
"${CANVAS_ORIGIN}/api/v1/accounts/${ROOT_ACCOUNT_ID}/developer_keys?per_page=100" |
jq -ce --arg id "${DEVELOPER_KEY_ID}" '
map(select((.id | tostring) == $id)) |
if length == 1 then .[0] else error("Developer Key not found") end
'
)"
printf '%s\n' "${KEY_JSON}" | jq '{id, name, is_lti_key, require_scopes, scopes}'Confirm the key before changing it
Confirm that the displayed key is the intended API OAuth key and that
"is_lti_key": false. If it is an LTI key, stop and locate the API OAuth key
instead.
Merge the scope, update the key, and verify
Run this in the same bash session. It changes only the key's scopes property
and preserves every scope already present.
EXISTING_SCOPES="$(printf '%s' "${KEY_JSON}" | jq -c '.scopes // []')"
UPDATED_SCOPES="$(
printf '%s' "${EXISTING_SCOPES}" |
jq -c --arg scope "${SESSION_SCOPE}" '
if index($scope) then . else . + [$scope] end
'
)"
PAYLOAD="$(jq -cn --argjson scopes "${UPDATED_SCOPES}" '{developer_key: {scopes: $scopes}}')"
UPDATED_KEY="$(
curl --fail-with-body --silent --show-error \
-X PUT \
-H "Authorization: Bearer ${CANVAS_ADMIN_TOKEN}" \
-H 'Content-Type: application/json' \
--data "${PAYLOAD}" \
"${CANVAS_ORIGIN}/api/v1/developer_keys/${DEVELOPER_KEY_ID}"
)"
printf '%s\n' "${UPDATED_KEY}" |
jq '{id, name, require_scopes, scopes}'
printf '%s\n' "${UPDATED_KEY}" |
jq -e --arg scope "${SESSION_SCOPE}" '.scopes | index($scope) != null' >/dev/null
echo 'Verified: session-token scope is present.'
unset CANVAS_ADMIN_TOKEN KEY_JSON EXISTING_SCOPES UPDATED_SCOPES PAYLOAD UPDATED_KEYRevoke the temporary token and reconnect users
Immediately revoke the temporary administrator token in Canvas. Do not reopen and save this key through the normal scope UI afterward; that UI may omit the hidden scope again.
Every user who connected before this change — administrators, instructors, and students — must select Reconnect Canvas and authorize once. Existing OAuth tokens do not gain new Developer Key scopes automatically. See Canvas Developer Key scopes.
The application already requests this exact scope in its API OAuth configuration. Canvas uses it for the official session-start endpoint, which returns a short-lived browser session URL for quiz taking. See Canvas OAuth session-token documentation and the full API OAuth key scope list.
Course reset repeatedly asks to reconnect
Confirm that the same API OAuth Developer Key also permits the administrator-only
scope url:GET|/api/v1/courses/:course_id/quizzes/:id. A root administrator must
select Reconnect Canvas once after that scope is added; instructor and student
connections do not need to change for this administrator capability. The reset
uses this exact read scope to record each Classic Quiz access code before it makes
any change.
The authorization window does not return to the tool
Authorization must start from the Safe Online Exam page opened by a signed LTI launch. The service opens Canvas authorization in a popup and, after the callback, resumes the original role-appropriate page through an exact-origin popup handshake; the callback itself does not render a privileged workspace.
Fix: allow the popup for the tool and Canvas origins, begin again from the course or account-navigation tool entry, and do not bookmark or open the callback URL directly. If the browser cannot complete the popup exchange, reopen the tool from Canvas and retry the connection.
Instructor is asked to authorize repeatedly
The OAuth callback or key configuration does not match.
Fix — confirm all three:
- The API OAuth redirect URI exactly matches
${TOOL_URL}/api/oauth2callback. - The OAuth key is enabled.
- The configured
CANVAS_API_CLIENT_ID/CANVAS_API_CLIENT_SECRETare the API-key values — not the LTI key's.
If you changed the redirect URI or scope set, affected users must reauthorize once.
Student cannot connect Canvas or the configuration download fails
Usually a missing scope or a Canvas-environment mismatch.
Fix:
- Confirm the exact
session_tokenscope is allowed on the API key. - Confirm the student is authorizing the same Canvas environment as the LTI launch — connecting a different Canvas will not satisfy the launch.
- Have the student run the setup check to test the missing/revoked-scope recovery path, which requests a fresh Canvas connection.
Why every connection requests the same scopes
By design, every Canvas connection requests the same complete application scope set regardless of the user's role in the initiating course. This keeps one durable grant valid for someone who is an instructor in one course and a student in another. Canvas still enforces their actual permissions, so a broad scope request does not grant broad access — it only avoids incompatible role-specific grants. An administrator's grant is the same record, upgraded in place with the account scopes.
Where tokens live
One OAuth grant is stored per Canvas user in canvas_oauth_tokens; the service
refreshes it as needed, and a 401 from Canvas triggers at most one safe
refresh/retry. If authorization is badly wedged for one user, having them
Reconnect Canvas issues a fresh grant. Rotating STATE_ENCRYPTION_KEY
invalidates in-flight OAuth state (not stored grants) — see
Configuration → Secret rotation.
Canvas launch
Launch and identity failures — refused connections, signing-key errors, invalid launches, deployment-ID rejections, and a denied administrator dashboard.
Detector
Problems on the Canvas quiz page — the detector not loading, the HTTP 422 theme-file case, the access code not filling, and tools or exit not appearing.