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

  1. 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
  2. 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
  3. 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
  4. 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

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.