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

Must be logged in

Error message

Must be logged in

What it means

GraphQL mutation DismissAccountNotification raises this when context[:current_user] is nil, meaning the GraphQL request was made without an authenticated session or access token. Canvas GraphQL mutations that act on user state refuse to run anonymously.

Solutions

  1. Authenticate the request: pass a valid Canvas session cookie or 'Authorization: Bearer <token>' header to /api/graphql.
  2. Verify the token is for an active, non-deleted user; regenerate it if expired (Account > Settings > Approved Integrations).
  3. In client code, check authentication before calling the mutation (fetch /api/v1/users/self first) and redirect to login on 401.
  4. If using masquerading, ensure the real user session is still valid.

Example fix

// before
curl -X POST https://canvas/api/graphql -d '{"query":"mutation { dismissAccountNotification(input: {notificationId: \"1\"}) { ... } }"}'
// after
curl -X POST https://canvas/api/graphql -H 'Authorization: Bearer <CANVAS_TOKEN>' -d '{"query":"mutation { dismissAccountNotification(input: {notificationId: \"1\"}) { ... } }"}'
Defensive patterns

Strategy: validation

Validate before calling

const me = await fetch('/api/v1/users/self', { headers }).then(r => r.ok ? r.json() : null);
if (!me) { redirectToLogin(); throw new Error('Not authenticated'); }

Type guard

const isLoggedIn = (ctx) => typeof ctx?.currentUser?.id === 'number' && ctx.currentUser.id > 0;

Try / catch

try { await client.mutate({ mutation: DISMISS, variables }); }
catch (e) { if (e.graphQLErrors?.some(g => g.message === 'Must be logged in')) redirectToLogin(); else throw e; }

Prevention

When it happens

Trigger: Calling the dismissAccountNotification mutation with no valid session cookie or bearer token, or with an expired/revoked token so context[:current_user] resolves to nil.

Common situations: Unauthenticated scripts or curl calls against /api/graphql; expired Canvas session in an SPA; missing Authorization header in a service-to-service call; API token from a user who was since deleted or merged.

Related errors


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

Appendix: source

Thrown at app/graphql/mutations/dismiss_account_notification.rb:27

# the terms of the GNU Affero General Public License as published by the Free
# Software Foundation, version 3 of the License.
#
# 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 DismissAccountNotification < BaseMutation
    argument :notification_id, ID, required: true, prepare: GraphQLHelpers.relay_or_legacy_id_prepare_func("AccountNotification")

    def resolve(input:, **)
      user = context[:current_user]
      raise GraphQL::ExecutionError, I18n.t("Must be logged in") unless user

      notification = AccountNotification.find_by(id: input[:notification_id])
      raise GraphQL::ExecutionError, I18n.t("Notification not found") unless notification

      closed_notifications = user.get_preference(:closed_notifications) || []
      closed_notifications << notification.id unless closed_notifications.include?(notification.id)
      user.set_preference(:closed_notifications, closed_notifications)

      {}
    end
  end
end

View on GitHub (pinned to 1c9f0bb801)