skip to Main Content

I’m using Rails 6.0.3.4, Devise 4.7.3 for authentication and testing my app on Google Chrome. I have the application containerised using Docker and Docker Compose and I’m using PostgreSQL for the database, ElasticSearch for search and NGINX for the web server.

I can Log In successfully. However when I try to Log Out by clicking on the Log Out button, it doesn’t Log Out and only redirects to the home page.

<%= link_to "Log Out", destroy_user_session_path, method: :delete, :class => 'nav-link' %>

The NGINX server is showing a POST request rather than a DELETE request when trying to Log Out and I’m not sure why this is happening.

37.228.235.151 - - [23/Jan/2021:19:50:12 +0000] "POST /users/sign_out HTTP/1.1" 302 97 "https://myapp.ie/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.141 Safari/537.36" "-"

When I run rake routes, it shows destroy_user_session as:

destroy_user_session DELETE /users/sign_out(.:format)                                                                devise/sessions#destroy

My devise.rb initializer has:

config.sign_out_via = :delete

My Devise controllers are all standard (I haven’t edited them).

Here’s my Application Controller

class ApplicationController < ActionController::Base
  
    protect_from_forgery prepend: true

    # To enable sign in to function correctly.
    skip_before_action :verify_authenticity_token, :only => :create

    before_action :configure_permitted_parameters, if: :devise_controller?

    def index
        
    end
    
    protected

    # Restrict parameters for sign up input.
    def configure_permitted_parameters
        added_attrs = [:first_name, :last_name, :email, :encrypted_password, :password_confirmation, :remember_me]
        devise_parameter_sanitizer.permit(:sign_up, keys: added_attrs)
        devise_parameter_sanitizer.permit(:account_update, keys: added_attrs)
        devise_parameter_sanitizer.permit(:sign_in, keys: added_attrs)
    end

end

Here’s my Application.html.erb

<!DOCTYPE html>
<html>
  <head>
    <title>MyApp</title>
    <link rel="manifest" href="/manifest.webmanifest" crossorigin="use-credentials">
    <link rel="apple-touch-icon" href="/apple-touch-icon.png">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <meta name="theme-color" content="#ff8a00">
    <meta name="msapplication-navbutton--color" content="#ff8a00">
    <meta name="apple-mobile-web-app-capable" content="yes">
    <meta name="apple-mobile-web-app-status-bar-style" content="default">
    <%= csrf_meta_tags %>
    <%= csp_meta_tag %>
    <%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %>
    <%= javascript_pack_tag 'application', 'data-turbolinks-track': 'reload' %>
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.16.0/umd/popper.min.js"></script>
    <script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js"></script>
    <link href="https://fonts.googleapis.com/css?family=Text+Me+One&display=swap" rel="stylesheet">
  </head>

  <body id="<%= controller.controller_name %>" class="<%= controller.action_name %> <%= "show-sidebar" if @show_sidebar.present? %>">
    <div class="wrapper">
      <% if @show_sidebar.nil? %>
        <header>
          <%= render "layouts/nav/public" %>
        </header>
        <%= yield %>
      <% else %>
        <%= render "layouts/nav/dashboard" %>

        <div class="container-fluid">
          <div class="row">
            <%= render "layouts/nav/sidebar" %>
            <main role="main" class="col-md-9 ml-sm-auto col-lg-10 px-md-4 mt-4">
              <%= yield %>
            </main>
          </div>
        </div>
      <% end %>
      <div class="push"></div>
    </div>

    <%= render "layouts/footer" %>

    <div id="notifications"><%= notice %></div>

    <!-- PWA Step 1 - Registration -->
    <script type="text/javascript">
    if ('serviceWorker' in navigator) {
      window.addEventListener('load', function() {
        navigator.serviceWorker.register('/service-worker.js').then(function(registration) {
          // Registration was successful
          console.log('ServiceWorker registration successful with scope: ', registration.scope);
        }, function(err) {
          // registration failed :(
          console.log('ServiceWorker registration failed: ', err);
        });
      });
    }
    </script>
    <script src="https://code.jquery.com/jquery-3.5.1.slim.min.js" integrity="sha384-DfXdz2htPH0lsSSs5nCTpuj/zy4C+OGpamoFVy38MVBnE+IbbVYUew+OrCXaRkfj" crossorigin="anonymous"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/feather-icons/4.9.0/feather.min.js"></script>
    <script>feather.replace()</script>
    <%= javascript_pack_tag "counter" %>
  </body>
</html>

Here’s the part of the view that displays the Log In / Log Out link.

<ul class="navbar-nav mr-auto" id="right-nav-menu">
      <li class="nav-item">
      <% if notice %>
        <p class="nav-link"><%= notice %></p>
      <% end %>
      <% if alert %>
        <p class="nav-link"><%= alert %></p>
      <% end %>
      </li>
      <li class="nav-item">
      <% if user_signed_in? %>
        <strong class="nav-link"><p><%= current_user.first_name %> <%= current_user.last_name %></p></strong>
      <% end %>
      </li>
      <% if user_signed_in? %>
        <li class="nav-item">
          <strong><%= link_to 'Edit Profile', edit_user_registration_path, :class => 'nav-link' %></strong>
        </li>
        <li class="nav-item">
          <strong><%= link_to "Log Out", destroy_user_session_path, method: :delete, :class => 'nav-link' %></strong>
        </li>
      <% else %>
        <li class="nav-item">
          <strong><%= link_to "Log In", new_user_session_path, :class => 'nav-link' %></strong>
        </li>
        <li class="nav-item">
          <strong><%= link_to "Sign Up", new_user_registration_path, :class => 'nav-link'  %></strong>
        </li>
      <% end %>
    </ul>

Appreciate any help.

3

Answers


  1. In your initializer/devise.rb, try to replace delete with get on config.sign_out_via = :delete

    Login or Signup to reply.
  2. I understand that Rails doesn’t do a real DELETE request when using a link_to with method: :delete. Check the rails-ujs code here: https://github.com/rails/rails/commit/ad3a47759e67a411f3534309cdd704f12f6930a7#diff-d9f2caa2a91064bb77ada650abdc7085044dffb4e4e35715e8ac64b1c72a0d69R16-R25

    It creates a form with method POST and a hidden field _method. Nginx will see a POST request but rails knows how to read it as a DELETE request.

    What do you see on the Rails logs? do you get the request log in the console?

    Login or Signup to reply.
  3. TLDR; Don’t worry. Its perfectly fine.

    HTML forms can only send GET and POST requests. When you use link_to ... method: :delete with Rails UJS or just plain old forms with button_to ... method: :delete a POST request is used. While modern browsers will let you send DELETE and PATCH requests with XHR requests this does not apply to Rails UJS as the data-method handler actually creates a hidden form element and submits it.

    Rack (the Ruby CGI that Rails is built on top of) gets around this limitation by using a special parameter called _method which will make POST requests appear to be DELETE, PUT, PATCH etc. You might have noticed that strange hidden input in the output ofform_for/form_with. You can also use the HTTP_X_HTTP_METHOD_OVERRIDE header. See Rack::MethodOverride which is the middleware that transforms the request before it reaches your Rails application.

    Of course NGINX knows nothing about Rack’s shenanigans and its logs will just show this as regular POST request. The same applies if you use the inspector in your browser to check the traffic.

    But if you check the Rails logs you can see by the time the request makes it down the middleware stack to your Rails app Rails will think it was a DELETE request all along.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search