instructure/canvas-lms · error · GraphQL::ExecutionError
Unauthorized
Error message
Unauthorized
What it means
After locating the enrollment, the mutation verifies it belongs to the caller: unless enrollment.user == user it raises 'Unauthorized'. This prevents accepting someone else's invitation even if you know its uuid — enrollment invitations are non-transferable.
Solutions
- Log in as the exact invited user before executing the mutation.
- Cross-check enrollment.user_id against the current user client-side and redirect to a login/account-switch screen on mismatch.
- Stop forwarding invitation links; re-invite the correct user from the course People page instead.
- Verify no session-impersonation (as_user_id) is active when accepting invitations.
- In tests/scripts, use the invited user's token, not an admin's.
Example fix
// before
acceptEnrollmentInvitation({ enrollmentUuid }) // any logged-in user
// after
if (enrollmentUserId !== currentUser.id) redirect("/login?switch_user=true")
else acceptEnrollmentInvitation({ enrollmentUuid }) Defensive patterns
Strategy: validation
Validate before calling
// before accepting, confirm the invitation belongs to the logged-in user
const invitedUserId = decodeInvitationUserId(enrollmentUuid) // or fetched enrollment.user_id
if (String(invitedUserId) !== String(currentUser.id))
throw new Error("this invitation belongs to a different user; log in as the invited account") Type guard
const ownsEnrollment = (enrollment, user) => String(enrollment.user_id) === String(user.id)
Try / catch
try {
await client.request(ACCEPT_INVITATION, { enrollmentUuid })
} catch (e) {
if (/^Unauthorized$/.test(e.message)) return showWrongAccountScreen()
throw e
} Prevention
- Never forward invitation links between accounts
- Avoid accepting invitations while impersonating another user
- Show which account is logged in on invitation landing pages
- Use the invited user's token in automation
When it happens
Trigger: Accepting an invitation while logged in as a different user than the invited one: a parent/admin logged into an admin session clicking a teacher's invitation link; two accounts in one browser (impersonation or shared machine); forwarding an invitation email to a colleague who accepts it under their own login.
Common situations: Shared workstations with multiple Canvas accounts; admins impersonating users whose sessions carry the wrong current_user; automation replaying invitation uuids captured from another user's email.
Understand the failure class
Background: Permission denied / not authorized / 403 Forbidden: access-control rejections when the caller lacks the required role, grant, or ownership — this error's family across 18 libraries.
- Authentication and authorization failures — expired tokens, bad credentials, and missing scopes.
Related errors
- Enrollment invitation not found
- Enrollment is not in invited state
- feature flag is disabled
- insufficient permission
- insufficient permission
AI-assisted analysis of instructure/canvas-lms@1c9f0bb801 (2026-09-15).
Data as JSON: /api/errors/f0ca09096fdbff91.
Report an issue: GitHub.
Appendix: source
Thrown at app/graphql/mutations/accept_enrollment_invitation.rb:36
# with this program. If not, see <http://www.gnu.org/licenses/>.
#
module Mutations
class AcceptEnrollmentInvitation < BaseMutation
argument :enrollment_uuid, String, required: true
field :enrollment, Types::EnrollmentType, null: true
field :success, Boolean, null: false
def resolve(input:, **)
user = context[:current_user]
raise GraphQL::ExecutionError, I18n.t("Must be logged in") unless user
enrollment = Enrollment.where(uuid: input[:enrollment_uuid]).first
raise GraphQL::ExecutionError, I18n.t("Enrollment invitation not found") unless enrollment
# Verify the enrollment belongs to the current user
raise GraphQL::ExecutionError, I18n.t("Unauthorized") unless enrollment.user == user
# Verify the enrollment is in invited state
raise GraphQL::ExecutionError, I18n.t("Enrollment is not in invited state") unless enrollment.invited?
begin
if enrollment.accept!
{
enrollment:,
success: true
}
else
{
enrollment: nil,
success: false,
errors: [{ message: I18n.t("Failed to accept enrollment invitation") }]
}
end
rescue => eView on GitHub (pinned to 1c9f0bb801)