instructure/canvas-lms · error · BasicLTI::Errors::InvalidSourceId
sourcedid_invalid
sourcedid_invalid
Error message
Invalid sourcedid
What it means
Sourcedid.load! raises Errors::InvalidSourceId with :sourcedid_invalid when the sourcedid string is blank, or (via token_from_sourcedid!) when the encrypted JWT payload cannot be parsed as a valid format (JSON::JWT::InvalidFormat rescued and re-raised). It is the generic 'this lis_result_sourcedid is not a decodable token' error.
Solutions
- Check the sourcedid is present and unmodified (no truncation, no double URL-encoding) before calling load!.
- Re-launch the tool to obtain a fresh sourcedid.
- If legacy sourcedids are in play, confirm load_from_legacy_sourcedid! path applies (SOURCE_ID_REGEX match) — legacy format is tried first.
- Log the received value safely (length/prefix only) to detect truncation or escaping corruption.
Example fix
// before
sourcedid = BasicLti::Sourcedid.load!(params[:lis_result_sourcedid])
// after
sd = params[:lis_result_sourcedid].to_s
if sd.blank? || sd.length < 50
return render_error("missing or malformed sourcedid")
end
sourcedid = BasicLti::Sourcedid.load!(sd) Defensive patterns
Strategy: validation
Validate before calling
# ruby
sd = params[:lis_result_sourcedid].to_s
return error("missing sourcedid") if sd.blank? Try / catch
begin
BasicLti::Sourcedid.load!(sd)
rescue BasicLti::Sourcedid::Errors::InvalidSourceId => e
render_bad_request("invalid sourcedid") if e.error_code == :sourcedid_invalid
end Prevention
- Treat sourcedids as opaque; never truncate, escape, or re-encode them.
- Use adequately sized storage columns (TEXT) for sourcedid values.
- Reject blank sourcedids at the controller layer with a clear 400.
When it happens
Trigger: Calling Sourcedid.load!(nil), load!("") , or passing a string that is not base64-encoded encrypted JWT data (e.g. an HTML-escaped, truncated, or legacy-corrupted value) to token_from_sourcedid!.
Common situations: Tool stores lis_result_sourcedid in a column/cookie that truncated it; sending the sourcedid URL-encoded/unescaped incorrectly; passing an old OAuth-signed sourcedid through the JWT path after a format migration.
Understand the failure class
Background: "Invalid ... format", "must be in format X", "does not look like a ..." — invalid argument format errors across CLI tools and libraries — this error's family across 17 libraries.
Related errors
- Access token expired
- Access token invalid - signature likely incorrect
- either the tool proxy or developer key were not found
- iat must be in the past
- Invalid access token field/s: #
AI-assisted analysis of instructure/canvas-lms@1c9f0bb801 (2026-09-15).
Data as JSON: /api/errors/e4e3d054e1338118.
Report an issue: GitHub.
Appendix: source
Thrown at lib/basic_lti/sourcedid.rb:65
assignment_id: assignment.id,
user_id: user.id,
}
end
private :jwt_payload
def validate!
raise Errors::InvalidSourceId.new("Course is invalid", :course_invalid) unless course
raise Errors::InvalidSourceId.new("User is no longer in course", :user_not_in_course) unless user
raise Errors::InvalidSourceId.new("Assignment is invalid", :assignment_invalid) unless assignment
tag = assignment.external_tool_tag
raise Errors::InvalidSourceId.new("Assignment is no longer associated with this tool", :assignment_tool_mismatch) unless tag &&
(tool.matches_url?(tag.url, match_queries_exactly: false) || tool.matches_tool_domain?(tag.url)) &&
tool.workflow_state != "deleted"
end
def self.load!(sourcedid_string)
raise Errors::InvalidSourceId.new("Invalid sourcedid", :sourcedid_invalid) if sourcedid_string.blank?
token = load_from_legacy_sourcedid!(sourcedid_string) ||
token_from_sourcedid!(sourcedid_string)
tool = Lti::ToolFinder.find_by(id: token[:tool_id])
course = Course.active.find_by(id: token[:course_id])
if course
user = course.student_enrollments.active.find_by(user_id: token[:user_id])&.user
assignment = course.assignments.active.find_by(id: token[:assignment_id])
end
sourcedid = new(tool, course, assignment, user)
sourcedid.validate!
sourcedid
end
def self.load_from_legacy_sourcedid!(sourcedid)
token = nilView on GitHub (pinned to 1c9f0bb801)