instructure/canvas-lms · error · Canvas::OAuth::RequestError
invalid_redirect
invalid_redirect
Error message
invalid_redirect
What it means
Canvas raises Canvas::OAuth::RequestError with :invalid_redirect in the /login/oauth2/auth flow when the redirect_uri parameter does not exactly match a redirect URI registered on the developer key (provider.has_valid_redirect? returns false). This prevents open-redirect/token interception per the OAuth2 spec.
Solutions
- Add the exact redirect_uri your app uses to the developer key's Redirect URIs list in Canvas admin
- Compare byte-for-byte: scheme, host, port, path and trailing slash of the sent redirect_uri vs the registered one
- Register both http://localhost:PORT/... and production URIs if you develop locally
- Re-check after environment/proxy changes that the app's computed redirect_uri did not change (e.g. https offloading)
Example fix
// before
const redirectUri = 'https://myapp.example.com/oauth/callback' // not registered
// after
// register 'https://myapp.example.com/oauth/callback' on the developer key first
const redirectUri = new URL('/oauth/callback', window.location.origin).href // exact match saved in Canvas admin Defensive patterns
Strategy: validation
Validate before calling
function matchesRegisteredRedirect(uri) {
const u = new URL(uri);
return registeredRedirects.some(r => { const v = new URL(r); return v.protocol === u.protocol && v.host === u.host && (v.pathname.replace(/\/$/, '') === u.pathname.replace(/\/$/, '') || v.pathname === u.pathname); });
}
if (!matchesRegisteredRedirect(redirectUri)) console.warn('redirect_uri not registered on developer key:', redirectUri); Try / catch
try { await startOAuth({ redirectUri }); } catch (e) { if (e.error === 'invalid_redirect') { console.error('redirect_uri mismatch; registered URIs:', registeredRedirects); } throw e; } Prevention
- Register both localhost-dev and production redirect URIs on the key
- Build redirect_uri from a single config value, not from request headers
- Re-verify after proxy/TLS changes that scheme/host/port are unchanged
- Avoid ad-hoc trailing-slash differences between code and registration
When it happens
Trigger: Calling /login/oauth2/auth with a redirect_uri that is absent from the key's registered redirect URIs, omitted when the key requires one, or differing only in trailing slash, scheme (http vs https), port, or case.
Common situations: Local development on localhost:3000 while the key only lists the production URL, adding/removing a trailing slash, switching from http to https behind a proxy, or forgetting to save the redirect URI when creating the developer key.
Understand the failure class
Background: "Invalid URL" errors: why new URL(), URI.parse, and reqwest::Url reject your string — missing scheme, whitespace, and bad path format — this error's family across 39 libraries.
Related errors
- invalid_client_id
- assertion method not supported for this grant_type
- invalid_client_id
- invalid_refresh_token
- @provider.error_message
AI-assisted analysis of instructure/canvas-lms@1c9f0bb801 (2026-09-15).
Data as JSON: /api/errors/5f1047bb2d704bca.
Report an issue: GitHub.
Appendix: source
Thrown at app/controllers/oauth2_provider_controller.rb:53
# 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",
error_description: "Client does not have access to the specified Canvas account.")View on GitHub (pinned to 1c9f0bb801)