1

I'm building a single-page application using Devise and Devise Token Auth for authentication. Everything works fine in development, but in production, clicking the email confirmation link results in a 500 Internal Server Error, and the confirmation fails.

In production, the app is running on AWS ECS Fargate. The request flow is as follows:
Route 53 → ALB → Nginx (reverse proxy) → Rails API

Both Nginx and Rails are containerized and deployed together in a single ECS task. They communicate via a shared Unix domain socket.

Here’s the problematic flow:

  1. After signing up, a confirmation email is sent to the user.
  2. The email contains a link to an endpoint like:
    https://api.my-service.net/api/v1/auth/confirmation?config=default&confirmation_token=hogefugabar
  3. When clicked in production, it causes a 500 error and does not redirect to the expected frontend route (https://my-service.net/account_confirmation_success=true).

Some additional details:

  • api.my-service.net is correctly configured in Route 53 with an A record pointing to the ALB.
  • In development, this flow works as expected.
  • I suspect password reset emails may have the same issue, although I haven't verified that yet.
  • The Nginx reverse proxy works correctly for other API requests, so I believe the Rails app is reachable and responsive.

What could be causing this 500 error only in production when hitting the confirmation endpoint?

Any guidance or suggestions would be greatly appreciated.

Ruby: 3.3.7 Rails: 7.2.2.1 Rails is in API mode.

The relevant code is as follows:

# user.rb

devise :database_authenticatable, :registerable,
       :recoverable, :validatable, :confirmable
  # :lockable, :timeoutable, :trackable, :omniauthable, :rememberable

  include DeviseTokenAuth::Concerns::User
# confirmation_instructions.html.erb

<p><%= @email %>, </p>

<p>Thank you for registering your account!</p>

<p>Please click the link below to complete your registration. </p><br>

<p>
  <%= link_to “Verify your account”, confirmation_url(@resource, {
    confirmation_token: @token,
    config: message[‘client-config’].to_s,
  }).html_safe %>
</p>

<p>The link is valid for 3 days. Please respond as soon as possible. </p><br>
# devise_token_auth.rb

# DEFAULT_CONFIRM_SUCCESS_URL and DEFAULT_PASSWORD_RESET_URL are passed as environment variables in the ECS task definition.

config.default_confirm_success_url = ENV["DEFAULT_CONFIRM_SUCCESS_URL"] || "http://localhost:3000"

config.default_password_reset_url = ENV["DEFAULT_PASSWORD_RESET_URL"] || "http://localhost:3000/password-reset"

config.redirect_whitelist = [
    "https://www.my-service.net",
    "https://www.my-service.net/password-reset"
]
# production.rb

config.action_mailer.default_url_options = { host: "https://api.my-service.net" }
config.hosts << "api.my-service.net"

I'm having trouble figuring out why the confirmations_controller.rb in Davise Token Auth is returning a 500 error. The request from Next.js to Rails is successful, and I've double-checked that the prod infrastructure has the correct settings, so I assume that the GET /api/v1/auth/confirmation is reaching its destination but is failing for some reason.

I'll add the following.

# cors.rb

Rails.application.config.middleware.insert_before 0, Rack::Cors do
  allow do
    origins "http://localhost:3000", "https://www.my-service.net", "https://api.my-service.net"

    resource "*",
      headers: :any,
      expose:  [
        "access-token", "expiry", "token-type", "uid", "client",
        "current-page", "page-items", "total-count", "total-pages"
      ],
      methods: [:get, :post, :put, :patch, :delete, :options, :head]
  end
end
# nginx.conf

upstream api {
  server unix:///app/tmp/sockets/puma.sock;
}

server {
  listen 80;
  server_name localhost;

  access_log /var/log/nginx/access.log;
  error_log  /var/log/nginx/error.log;

  root /app/public;

  client_max_body_size 100m;

  keepalive_timeout 5;

  location = /healthcheck {
    access_log off;
    return 200 "OK";
    add_header Content-Type text/plain;
  }

  error_page 404             /404.html;
  error_page 502 503 504 505 /500.html;

  location = /404.html {
    internal;
  }

  location = /500.html {
    internal;
  }

  try_files $uri @api;

  location @api {
    proxy_pass             http://api;
    proxy_set_header       Host $host;
    proxy_set_header       X-Real-IP $remote_addr;
    proxy_set_header       X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_intercept_errors on;
  }
}

nginx container logs on cloudwatch following

"GET /api/v1/auth/confirmation?config=default&confirmation_token=ZhpLxqjmVbs4kgxS_QfH HTTP/1.1" 500 26 "-" "Mozilla/5.0

[error] 14#14: *99 open() "/app/public/404.html" failed (2: No such file or directory), client: 10.0.1.23, server: localhost, request: "GET /favicon.ico HTTP/1.1", upstream: "http://unix:///app/tmp/sockets/puma.sock/favicon.ico", host: "api.my-service.net", referrer: "https://api.my-service.net/api/v1/auth/confirmation?config=default&confirmation_token=ZhpLxqjmVbs4kgxS_QfH"

3
  • When you say A record of ALB, did you created a alias record or manually attaching ALB IPs to Route53? Cause IPs can change if ALB autoscale, better use A alias record of route53 for ALB Commented Jun 9 at 9:32
  • 1
    Thank you for comment. The A record is assigned as an alias. Commented Jun 9 at 12:46
  • The log line with the [error] has something to do with favicon. You should investigate what to do about the favicon thing since it is a browser standard way of getting a small graphical icon file from the server to display in the client. It just makes the call to nginx and nginx and / or rails are probably not configured in the current setup to handle the favicon request. My two cents. Commented Jun 10 at 14:21

1 Answer 1

0

I pretty much think the problem is in the environment configuration where you have put in the secret key for token verification. Code is not issue as it is working in dev.

Once you have checked code, do check for CORS configuration and database connection.

I dont think architecture is at fault here

Sign up to request clarification or add additional context in comments.

3 Comments

I added cors.rb, nginx.conf and logs in CloudWatch. I am able to retrieve data from the DB, so I believe there are no communication issues between Rails and RDS (posgres). Can you suggest a solution based on the additional information I provided?
The token that is being sent, there could be a problem in that. An incorrect format of token. Is it possible to get the stacktrace of the error from server? What I think is happening is, that your Nginx proxy server is not passing the token correctly to the backend API. For testing, remove the proxy server, the issue should resolve.
The cause was ActionController::Redirecting::UnsafeRedirectError. Overriding allow_other_host: true resolved the issue. Thanks.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.