{"record":{"id":"d17f3fe77220913a","repo":"instructure/canvas-lms","slug":"invalid-client-secret","errorCode":"invalid_client_secret","errorMessage":"invalid_client_secret","messagePattern":"invalid_client_secret","errorType":"error_code","errorClass":"Canvas::OAuth::RequestError","httpStatus":null,"severity":"error","filePath":"lib/canvas/oauth/grant_types/base_type.rb","lineNumber":39,"sourceCode":"      # allow public clients as defined in RFC 6749.\n      def allow_public_client?\n        false\n      end\n\n      def supported_type?\n        false\n      end\n\n      private\n\n      def validate_client_id_and_secret\n        raise Canvas::OAuth::RequestError, :invalid_client_id unless @provider.has_valid_key?\n\n        # Issue an access token if the grant type supports public client and the\n        # DeveloperKey identifies a public client. Otherwise, the client must must\n        # provide a client secret.\n        return if allow_public_client? && @provider.key&.public_client? && @secret.blank?\n        raise Canvas::OAuth::RequestError, :invalid_client_secret unless @provider.is_authorized_by?(@secret)\n      end\n\n      def validate_type\n        raise \"Abstract Method\"\n      end\n\n      def generate_token\n        raise \"Abstract Method\"\n      end\n    end\n  end\nend\n","sourceCodeStart":21,"sourceCodeEnd":52,"githubUrl":"https://github.com/instructure/canvas-lms/blob/1c9f0bb8013ed69c4f2efe11fd483025469b7e6c/lib/canvas/oauth/grant_types/base_type.rb#L21-L52","documentation":"Canvas::OAuth::RequestError :invalid_client_secret is raised in BaseType#validate_client_id_and_secret when the client_id is valid but @provider.is_authorized_by?(@secret) fails — the supplied client_secret does not match the developer key's secret (or a valid Canvas-issued access token). The error is skipped only when the grant type allows public clients, the key is marked public_client?, and the secret is blank.","triggerScenarios":"POST to /login/oauth2/token with a correct client_id but a wrong, stale, or omitted client_secret on a key that is not a public client; sending a secret belonging to a different key; using client_credentials/authorization_code grant with a key flagged public_client? while also sending a non-matching secret.","commonSituations":"Secret rotated in the Canvas admin UI but the app still deploys the old value; whitespace/quoting damage when storing the secret in env vars; mixing credentials of two keys (right client_id, other key's secret); expecting public-client behavior without marking the key public_client? in Canvas.","solutions":["Re-copy the current client_secret from Canvas account > Developer Keys and update the app's configuration/env.","Confirm client_id and client_secret belong to the same developer key.","If the client is truly public (SPA/mobile), mark the developer key as a public client in Canvas and omit the secret entirely.","Check for env-var issues: trailing whitespace, missing quotes, or stale secrets in deploy environments."],"exampleFix":"// before: stale secret after rotation\nbody: { grant_type: 'authorization_code', code, client_id, client_secret: OLD_SECRET }\n\n// after: read the rotated secret from config, or omit it for a public client\nbody: { grant_type: 'authorization_code', code, client_id, client_secret: process.env.CANVAS_CLIENT_SECRET }","handlingStrategy":"try-catch","validationCode":"function hasSecretOrPublicClient(cfg) { return (typeof cfg.clientSecret === 'string' && cfg.clientSecret.trim() !== '') || cfg.isPublicClient === true }","typeGuard":"function hasClientSecret(cfg) { return typeof cfg.clientSecret === 'string' && cfg.clientSecret.trim() !== '' }","tryCatchPattern":"try {\n  token = await exchangeCode(code, clientId, clientSecret)\n} catch (e) {\n  if (e.body?.error === 'invalid_client_secret') {\n    throw new Error('client_secret rejected: rotate/redeploy secret from Canvas Developer Keys')\n  }\n  throw e\n}","preventionTips":["Store client_secret in a secret manager and redeploy immediately after rotating it in Canvas.","Trim and quote secret values in env files to avoid whitespace damage.","Keep client_id and client_secret as a single versioned credential pair.","Use public_client? keys (no secret) only for genuine public clients."],"tags":["oauth","client-secret","authentication","canvas-lms"],"backgroundTag":"missing-credentials","analyzedSha":"1c9f0bb8013ed69c4f2efe11fd483025469b7e6c","analyzedAt":"2026-09-15T20:33:18.891Z","contentChangedAt":"2026-09-15T20:33:18.891Z","schemaVersion":2},"datasetVersion":"2026-09-23T02:17:17.105Z"}