instructure/canvas-lms · error · GraphQL::ExecutionError

Must be logged in

Error message

Must be logged in

What it means

AcceptEnrollmentInvitation mutation requires context[:current_user]; when the GraphQL context carries no authenticated user it raises I18n.t("Must be logged in") as a GraphQL::ExecutionError. The mutation deliberately checks authentication before touching the enrollment record so anonymous tokens fail fast with a clear message.

Solutions

  1. Authenticate first: obtain an access token (login or OAuth) and send it as `Authorization: Bearer <token>` on the GraphQL request.
  2. Check token expiry and refresh/re-login before retrying the mutation.
  3. Confirm the GraphQL controller actually populates context[:current_user] (session or token auth) in your environment.
  4. In the UI, ensure the invitation link routes through login before executing the mutation.
  5. Test with a known-good token to distinguish auth failure from mutation bugs.

Example fix

// before
fetch("/api/graphql", { method: "POST", body: JSON.stringify({ query, variables }) })
// after
fetch("/api/graphql", { method: "POST", headers: { Authorization: `Bearer ${token}` }, body: JSON.stringify({ query, variables }) })
Defensive patterns

Strategy: try-catch

Validate before calling

// before calling the mutation
if (!accessToken || isExpired(accessToken)) await refreshTokenOrRedirectToLogin()
// and ensure request headers include Authorization: Bearer <token>

Type guard

const hasAuth = (ctx) => typeof ctx?.currentUser?.id !== "undefined" && ctx.currentUser.id !== null

Try / catch

try {
  const res = await client.request(ACCEPT_INVITATION_MUTATION, { enrollmentUuid })
} catch (e) {
  if (/Must be logged in/i.test(e.message)) return redirectToLogin()
  throw e
}

Prevention

When it happens

Trigger: Calling mutation acceptEnrollmentInvitation with an unauthenticated token: missing/expired access token, token not sent in the Authorization header, or a public/anonymous GraphQL request resolving this mutation.

Common situations: Invitation-acceptance flows in email clients where the deep link opens the app before login; expired OAuth tokens after inactivity; missing Authorization header in a script hitting /api/graphql; incorrectly configured middleware that fails to populate context[:current_user].

Related errors


AI-assisted analysis of instructure/canvas-lms@1c9f0bb801 (2026-09-15). Data as JSON: /api/errors/42df537cba7d2b87. Report an issue: GitHub.

Appendix: source

Thrown at app/graphql/mutations/accept_enrollment_invitation.rb:30

# Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
# A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
# details.
#
# You should have received a copy of the GNU Affero General Public License along
# 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
          {

View on GitHub (pinned to 1c9f0bb801)