{"record":{"id":"2655ae0d44d19979","repo":"instructure/canvas-lms","slug":"incorrect-client","errorCode":"incorrect_client","errorMessage":"incorrect_client","messagePattern":"incorrect_client","errorType":"error_code","errorClass":"Canvas::OAuth::RequestError","httpStatus":null,"severity":"error","filePath":"lib/canvas/oauth/grant_types/authorization_code.rb","lineNumber":17,"sourceCode":"# frozen_string_literal: true\n\nmodule Canvas::OAuth\n  module GrantTypes\n    class AuthorizationCode < BaseType\n      def supported_type?\n        true\n      end\n\n      private\n\n      def validate_type\n        raise Canvas::OAuth::RequestError, :authorization_code_not_supplied unless @opts[:code]\n\n        @_token = @provider.token_for(@opts[:code])\n        raise Canvas::OAuth::RequestError, :invalid_authorization_code unless @_token.is_for_valid_code?\n        raise Canvas::OAuth::RequestError, :incorrect_client unless [@_token.key.global_id, @_token.key.id].include? @_token.client_id.to_i\n      end\n\n      def generate_token\n        @_token.create_access_token_if_needed(replace_tokens: Canvas::Plugin.value_to_boolean(@opts[:replace_tokens]))\n        Canvas::OAuth::Token.expire_code(@opts[:code])\n        @_token\n      end\n    end\n  end\nend\n","sourceCodeStart":1,"sourceCodeEnd":28,"githubUrl":"https://github.com/instructure/canvas-lms/blob/1c9f0bb8013ed69c4f2efe11fd483025469b7e6c/lib/canvas/oauth/grant_types/authorization_code.rb#L1-L28","documentation":"Canvas::OAuth::RequestError :incorrect_client is raised in AuthorizationCode#validate_type when the client_id presented at the token endpoint does not match the developer key that originally issued the authorization code. The check verifies that @_token.client_id matches either the global or local id of @_token.key (the key that created the code). It prevents a different OAuth client from redeeming another client's authorization code.","triggerScenarios":"POST to /login/oauth2/token with grant_type=authorization_code where the authorization code was issued to developer key A, but the request authenticates with client_id/client_secret of developer key B (the include? check on [key.global_id, key.id] fails).","commonSituations":"Multiple Canvas developer keys configured and the app sends the wrong client_id; a key was recreated/rotated (new key id) while old codes were outstanding; copy-pasting credentials from another environment or app; load balancer pointing token requests at a different Canvas instance than the authorize step.","solutions":["Send the same client_id (developer key) in the token exchange that was used to start the /login/oauth2/auth authorization flow.","Verify client_id and client_secret pair matches a single, current developer key in the Canvas account admin settings.","Re-run the authorization flow after any developer key rotation so outstanding codes match the new key.","Confirm multi-tenant/multi-shard routing sends the token request to the same Canvas account where the code was issued."],"exampleFix":"// before: mismatched credentials\ntoken = await fetch(TOKEN_URL, { body: { grant_type: 'authorization_code', code, client_id: OLD_CLIENT_ID, client_secret: OTHER_SECRET } })\n\n// after: use the same key that initiated the authorize redirect\nconst CLIENT_ID = process.env.CANVAS_CLIENT_ID // the key used in /login/oauth2/auth\ntoken = await fetch(TOKEN_URL, { body: { grant_type: 'authorization_code', code, client_id: CLIENT_ID, client_secret: process.env.CANVAS_CLIENT_SECRET } })","handlingStrategy":"validation","validationCode":"function clientMatchesKey(clientId, authorizeClientId) { return String(clientId) === String(authorizeClientId) } // assert before calling the token endpoint","typeGuard":"function hasValidCredentials(cfg) { return typeof cfg.clientId === 'string' && cfg.clientId !== '' && typeof cfg.clientSecret === 'string' && cfg.clientSecret !== '' }","tryCatchPattern":"try {\n  token = await exchangeCode(code, clientId, clientSecret)\n} catch (e) {\n  if (e.body?.error === 'incorrect_client') {\n    // wrong key: fail fast, surface config mismatch to the operator\n    throw new Error('client_id does not match the key that issued this code')\n  }\n  throw e\n}","preventionTips":["Store exactly one (client_id, client_secret) pair per Canvas environment and use it for both authorize and token steps.","Bind the client_id used in the authorize redirect into your session state and verify it at exchange time.","Re-run the authorization flow after any developer key rotation."],"tags":["oauth","client-id-mismatch","token-exchange","canvas-lms"],"backgroundTag":"oauth-token-exchange-failed","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"}