lynndylanhurley/devise_token_auth · error · ActionController::RoutingError
Not Found
Error message
Not Found
What it means
Raised by the GET /auth/confirmation endpoint (ConfirmationsController#show, confirmations_controller.rb:32). The action runs Devise's confirm_by_token; when the confirmation_token is blank, unknown, already consumed, or older than Devise.confirm_within, the resource comes back with errors. If no redirect_url is available (neither the redirect_url param nor the DeviseTokenAuth.default_confirm_success_url initializer setting), the controller raises ActionController::RoutingError so Rails renders its standard 404 page; with a redirect_url present it would instead redirect with account_confirmation_success=false. The raise is a deliberate dead-end for token-less API traffic, not a routing misconfiguration.
Source
Thrown at app/controllers/devise_token_auth/confirmations_controller.rb:32
if signed_in?(resource_name)
token = signed_in_resource.create_token
signed_in_resource.save!
redirect_headers = build_redirect_headers(token.token,
token.client,
redirect_header_options)
redirect_to_link = signed_in_resource.build_auth_url(redirect_url, redirect_headers)
else
redirect_to_link = DeviseTokenAuth::Url.generate(redirect_url, redirect_header_options)
end
redirect_to(redirect_to_link, redirect_options)
else
if redirect_url
redirect_to DeviseTokenAuth::Url.generate(redirect_url, account_confirmation_success: false), redirect_options
else
raise ActionController::RoutingError, 'Not Found'
end
end
end
def create
return render_create_error_missing_email if resource_params[:email].blank?
@email = get_case_insensitive_field_from_resource_params(:email)
@resource = resource_class.dta_find_by(uid: @email, provider: provider)
return render_not_found_error unless @resource
@resource.send_confirmation_instructions({
redirect_url: redirect_url,
client_config: resource_params[:config_name]
})
View on GitHub (pinned to b02076a930)
Solutions
- Set config.default_confirm_success_url in config/initializers/devise_token_auth.rb (or always send a redirect_url param) so failed confirmations redirect with account_confirmation_success=false instead of raising 404
- If the token is expired or already used, resend instructions via POST /auth/confirmation with the account email and use the link from the newest email
- Verify the token arrived intact: compare it against User.find_by(email: ...).confirmation_token in the Rails console to catch truncation or escaping by the mail client
- For API-only apps, override DeviseTokenAuth::ConfirmationsController#show in the host app to render a JSON error body instead of raising ActionController::RoutingError
Example fix
# before - config/initializers/devise_token_auth.rb DeviseTokenAuth.setup do |config| # default_confirm_success_url never set: a bad token plus no redirect_url param raises RoutingError end # after DeviseTokenAuth.setup do |config| config.default_confirm_success_url = 'https://app.example.com/auth/confirm-success' end
Defensive patterns
Strategy: validation
Validate before calling
// before opening or forwarding the confirmation link
function buildConfirmationUrl(baseUrl, token) {
if (!token || typeof token !== 'string') {
throw new Error('confirmation_token is missing or empty');
}
const url = new URL('/auth/confirmation', baseUrl);
url.searchParams.set('confirmation_token', token);
url.searchParams.set('redirect_url', 'https://app.example.com/auth/confirm-success');
return url.toString();
} Type guard
// Devise tokens are URL-safe base64-style strings; reject garbage before the request
const isPlausibleConfirmationToken = (t) =>
typeof t === 'string' && /^[A-Za-z0-9_-]{10,}$/.test(t); Try / catch
// server-side caller (Rails proxy or request spec) around the endpoint
begin
get '/auth/confirmation', params: { confirmation_token: token, redirect_url: redirect }
rescue ActionController::RoutingError
# invalid, expired, or consumed token: resend instructions, never retry the same token
end Prevention
- Always include a whitelisted redirect_url when generating or forwarding confirmation links
- Set DeviseTokenAuth.default_confirm_success_url so token failures degrade to a redirect with account_confirmation_success=false instead of a 404
- Treat confirmation links as single-use: never prefetch them with link previews or fire them twice
- Requesting new instructions regenerates the token: always start from the most recent confirmation email
When it happens
Trigger: GET /auth/confirmation?confirmation_token=X where X is expired (past Devise.confirm_within), already used (tokens are single-use and cleared after confirmation), truncated or HTML-escaped by a mail client, or missing entirely -- AND the request carries no redirect_url param AND DeviseTokenAuth.default_confirm_success_url is unset (the default).
Common situations: API or mobile clients that hit the confirmation endpoint directly without a redirect_url always receive the HTML 404 instead of JSON; users re-clicking an old confirmation link after the account is already confirmed; email links mangled by mail software; test suites reusing a consumed token; apps that never configured default_confirm_success_url in config/initializers/devise_token_auth.rb.
Related errors
AI-assisted analysis of lynndylanhurley/devise_token_auth@b02076a930 (2026-08-23).
Data as JSON: /api/errors/dd47f7eca1716f99.
Report an issue: GitHub.