Search code examples
ruby-on-railsformsdevise

How can I get devise errors for a sign in form?


I'll preface this by saying that I have read the duplicate questions. They seem to be pretty old, and their solutions have not worked.

I have the following code for a custom sign in form in views/devise/sessions/new.html.erb:

<%= form_with model: resource, url: session_path(resource_name), as: resource_name do |form| %>
    <%= form.text_field :email, class: "input" %>
    <%= form.password_field :password, class: "input" %>
    <%= tag.button(class: "btn-gradient input-button", type: "submit") do %>
        <span>Sign In</span>
    <% end %>
<% end %>

I am trying to pull errors and display them, and the following does not work:

<% if resource.errors.any? %>
    <div class="error">
        <% resource.errors.full_messages.each do |message| %>
            - <%= message %></br>
        <% end %>
    </div>
<% end %>

I've tried a few other variants online, but it does not seem that any errors are caught. If I type an incorrect email password combination, nothing happens. I've used the same syntax in other forms (like creating or editing a model), and it works fine.

Not sure why it's different with a devise sign in.

How can I get errors in a devise form?


Solution

  • Your expectations are wrong.

    The speedrun explaination of how authentication in Devise works is that Devise::SessionsController#create calls self.resource = warden.authenticate!(auth_options) and if Warden cannot find a user with the given email/password it will throw(:warden). The throw is then caught by calling Devise::FailureApp which redirects the user to the sign in path.

    This is a more complex operation then your average scaffolded Rails controller that just renders the new view when the validations fail.

    There are no errors on the resource object because:

    • resource in your form is not the same instance as when authentication was performed in a completely different request cycle.
    • There were no errors added in the first place because this isn't the case of simple validations failing. Nor should there be.

    You Ain't Gonna Need It.

    When Devise performs validation it fails it just sets a flash message which by default is "Invalid email or password.". Remember that flash messages are passed through a cookie from one request to the next - model instances are not.

    This is also the only feedback you should actually give. If you for example added a separate "Invalid email" message it lets an attacker performing a brute force attack determine if an email exists and you just made thier job a whole lot easier.

    You can customize the flash messages simply by defining your own locale file and if you want to you can display the flash messages on the form.