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
- Authenticate first: obtain an access token (login or OAuth) and send it as `Authorization: Bearer <token>` on the GraphQL request.
- Check token expiry and refresh/re-login before retrying the mutation.
- Confirm the GraphQL controller actually populates context[:current_user] (session or token auth) in your environment.
- In the UI, ensure the invitation link routes through login before executing the mutation.
- 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
- Send Authorization header on every GraphQL request
- Refresh tokens proactively before invitation flows
- Route invitation deep links through login when no session exists
- Verify GraphQL context auth setup in custom middleware
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
- All ConversationMessages must exist within the same…
- An unexpected error occurred while grading.
- An unexpected error occurred while submitting feedback.
- Assignment not found
- Assignment not found
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)