{"record":{"id":"65586f177aee340c","repo":"instructure/canvas-lms","slug":"invalid-authorization-code","errorCode":"invalid_authorization_code","errorMessage":"invalid_authorization_code","messagePattern":"invalid_authorization_code","errorType":"error_code","errorClass":"Canvas::OAuth::RequestError","httpStatus":null,"severity":"error","filePath":"lib/canvas/oauth/grant_types/authorization_code.rb","lineNumber":16,"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 :invalid_authorization_code is raised in Canvas::OAuth::GrantTypes::AuthorizationCode#validate_type when the authorization code supplied to the OAuth2 token endpoint does not correspond to a valid, unexpired code. The provider looks up the code via @provider.token_for(@opts[:code]); if the resulting token is not for a valid code (@_token.is_for_valid_code? is false), this error is thrown. This protects the token exchange from replayed, expired, or forged authorization codes.","triggerScenarios":"POST to /login/oauth2/token with grant_type=authorization_code where the 'code' param is expired, already redeemed (codes are single-use and expired via Canvas::OAuth::Token.expire_code), belongs to a different shard, was tampered with, or is missing/malformed so token_for returns a token that fails is_for_valid_code?.","commonSituations":"Client retries a token exchange after already successfully redeeming the code; user took too long between authorize redirect and token exchange (code TTL elapsed); environment mismatch (code issued on staging, exchanged on production); clock skew or multi-shard setups where the code was created on a different shard.","solutions":["Request a fresh authorization code by redirecting the user through /login/oauth2/auth again, then exchange it immediately.","Ensure the code is exchanged exactly once and never cached or retried after a successful exchange.","Verify the redirect/token exchange happens against the same Canvas environment (and shard) that issued the code.","Check server clocks and that no proxy is stripping or rewriting the code parameter."],"exampleFix":"// before: reusing a stored code\ntoken = exchangeCode(storedCode) // raises invalid_authorization_code on second use\n\n// after: always exchange a freshly received code, once\nconst code = new URL(redirectUrl).searchParams.get('code')\nif (code && !usedCodes.has(code)) {\n  usedCodes.add(code)\n  token = await exchangeCode(code)\n}","handlingStrategy":"retry","validationCode":"function canExchange(code) { return typeof code === 'string' && code.length > 0 && !usedCodes.has(code) && (codeIssuedAt && Date.now() - codeIssuedAt < CODE_TTL_MS) }","typeGuard":"function hasAuthCode(params) { return typeof params.code === 'string' && params.code.trim() !== '' }","tryCatchPattern":"try {\n  token = await exchangeCode(code)\n} catch (e) {\n  if (e.body?.error === 'invalid_authorization_code') {\n    // code expired/replayed: restart the authorization flow\n    return redirectToAuthorize()\n  }\n  throw e\n}","preventionTips":["Exchange the code immediately upon redirect; never cache or reuse it.","Mark codes as consumed in your own state to avoid duplicate token requests.","Keep authorize and token exchange on the same Canvas host/account."],"tags":["oauth","authorization-code","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"}