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

  1. Log in as the exact invited user before executing the mutation.
  2. Cross-check enrollment.user_id against the current user client-side and redirect to a login/account-switch screen on mismatch.
  3. Stop forwarding invitation links; re-invite the correct user from the course People page instead.
  4. Verify no session-impersonation (as_user_id) is active when accepting invitations.
  5. 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

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.

Related errors


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 => e

View on GitHub (pinned to 1c9f0bb801)