instructure/canvas-lms · error · Canvas::OAuth::RequestError

invalid_client_id

invalid_client_id

Error message

invalid_client_id

What it means

Canvas raises Canvas::OAuth::RequestError with :invalid_client_id in the OAuth2 /login/oauth2/auth flow when the provided client_id does not correspond to a Developer Key with a valid api key (provider.has_valid_key? returns false). It mirrors the OAuth2 spec's invalid_client error for the authorization endpoint. It means the app initiating the login is not a registered/valid Canvas developer key.

Solutions

  1. Verify the developer key exists and is active in Canvas admin (/accounts/self/developer_keys) and copy its exact client ID (the numeric key id)
  2. Check that you are pointing at the correct Canvas base URL for that key (keys do not transfer across environments)
  3. If the key is intentionally new, activate it (and get it allow-listed on self-hosted/managed installs) before starting the auth flow
  4. Correct the client_id value in your OAuth config/env vars; ensure it is sent as the client_id query param on /login/oauth2/auth

Example fix

// before
window.location = canvasUrl + '/login/oauth2/auth?client_id=' + process.env.CANVAS_KEY_ID_TODO
// after
window.location = canvasUrl + '/login/oauth2/auth?client_id=' + CANVAS_DEVELOPER_KEY_ID // verified active in Canvas admin
Defensive patterns

Strategy: validation

Validate before calling

const isValidClientId = (id) => typeof id === 'string' && /^\d{6,}$/.test(id) && Boolean(CANVAS_KEYS[id]);
if (!isValidClientId(config.clientId)) throw new Error('Canvas developer key client_id missing/invalid — check Canvas admin developer keys');

Type guard

const isRegisteredKey = (id) => typeof id === 'string' && id in registeredDeveloperKeys;
if (!isRegisteredKey(clientId)) failFast('invalid client_id before starting OAuth flow');

Prevention

When it happens

Trigger: GET/POST /login/oauth2/auth with a client_id param that is missing, mistyped, points to a deleted developer key, or a key whose API key is blank/inactive.

Common situations: Using a dev key from a different Canvas environment (test vs production), a key that was deleted or deactivated by an admin, a client_id copied from docs/examples, or forgetting to create the developer key at /accounts/self/developer_keys before integrating.

Understand the failure class

Background: "API key is required" / "API key not found" / "No API key was set": the missing-api-key error family across 16 libraries — this error's family across 16 libraries.

Related errors


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

Appendix: source

Thrown at app/controllers/oauth2_provider_controller.rb:52

      # browser should be closed automatically. but we'll at least display
      # something basic.
      return render
    end

    scopes = (params[:scope] || params[:scopes] || "").split

    provider = Canvas::OAuth::Provider.new(
      params[:client_id],
      params[:redirect_uri],
      scopes,
      params[:purpose],
      pkce: {
        code_challenge: params[:code_challenge],
        code_challenge_method: params[:code_challenge_method]
      }
    )

    raise Canvas::OAuth::RequestError, :invalid_client_id unless provider.has_valid_key?
    raise Canvas::OAuth::RequestError, :invalid_redirect unless provider.has_valid_redirect?

    session[:oauth2] = provider.session_hash
    session[:oauth2][:state] = params[:state] if params.key?(:state)
    session[:oauth2][:nonce] = params[:nonce] if params.key?(:nonce)

    if provider.key.require_scopes? && !provider.valid_scopes?
      return redirect_to Canvas::OAuth::Provider.final_redirect(self,
                                                                state: params[:state],
                                                                error: "invalid_scope",
                                                                error_description: "A requested scope is invalid, unknown, malformed, or exceeds the scope granted by the resource owner. " \
                                                                                   "The following scopes were requested, but not granted: #{provider.missing_scopes.to_sentence(locale: :en)}")
    end

    unless provider.key.authorized_for_account?(@domain_root_account)
      return redirect_to Canvas::OAuth::Provider.final_redirect(self,
                                                                state: params[:state],
                                                                error: "unauthorized_client",

View on GitHub (pinned to 1c9f0bb801)