Search code examples
jsonruby-on-railsdevise

Rails API with devise, can't run POST #create method with JSON call from client


So I'm trying to create a Rails API with devise. My client side works well with GET, but I can't do POST type of calls. If I try to create a user I keep getting the following devise html form as response.

<h2>Sign up</h2>

<form class="new_user" id="new_user" action="/users" accept-charset="UTF-8" method="post"><input name="utf8" type="hidden" value="&#x2713;" /><input type="hidden" name="authenticity_token" value="RGZxLuXcOCbxKVdpFc/wSAKEp3wcxqpfnvmv0nstEcOppPyViWcmdu+dPt84XQeSkCmwRg0REZN+vTdX1j+MOQ==" />
      <div id="error_explanation">
      <h2>2 errors prohibited this user from being saved:</h2>
      <ul><li>Email can&#39;t be blank</li><li>Password can&#39;t be blank</li></ul>
    </div>


  <div class="field">
    <div class="field_with_errors"><label for="user_email">Email</label></div><br />
    <div class="field_with_errors"><input autofocus="autofocus" type="email" name="user[email]" id="user_email" /></div>
  </div>

  <div class="field">
    <div class="field_with_errors"><label for="user_password">Password</label></div>
    <em>(4 characters minimum)</em>
    <br />
    <div class="field_with_errors"><input autocomplete="off" type="password" name="user[password]" id="user_password" /></div>
  </div>

  <div class="field">
    <label for="user_password_confirmation">Password confirmation</label><br />
    <input autocomplete="off" type="password" name="user[password_confirmation]" id="user_password_confirmation" />
  </div>

  <div class="actions">
    <input type="submit" name="commit" value="Sign up" />
  </div>
</form>
  <a href="/users/sign_in">Log in</a><br />



  <a href="/users/confirmation/new">Didn&#39;t receive confirmation instructions?</a><br />

Interestingly, at first I would get a "Can't verify CSRF token authenticity" error, so I added beforeSend to my JSON call. This would than change my call to OPTIONS, even though it was specified as a POST.

My call:

$(document).on('page:change', function(){
  $('body').on('submit', 'form.create_new_user', function(e){
    e.preventDefault();
    var user_data = $(this).serializeJSON();
    $.ajax({
      url: Host.address + '/users',
      beforeSend: function(xhr) {xhr.setRequestHeader('X-CSRF-Token', $('meta[name="csrf-token"]').attr('content'))},
      type: 'post',
      datatype:'json',
      data: {info: {full_name: user_data.full_name,
                    email: user_data.email,
                    password: user_data.password,
                    password_confirmation: user_data.password_confirmation}}
    }).done(function(response){
      console.log(response)
      if (response.success.success) {
        logInGoToWithNotice(response, '/', 'Thank you for signig up');
      } else{
        alert("User wasn't created. Please try again.")
      };
    });
  });
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>

And API does this:

Started OPTIONS "/users" for ::1 at 2015-09-30 14:09:49 +0200

ActionController::RoutingError (uninitialized constant RegistrationsController):
...

If I now remove beforeSend, I'd get the form again, just that "Can't verify CSRF token authenticity" error wouldn't be there anymore, but instead I'd get successful POST, that still returns html form.

Started POST "/users" for ::1 at 2015-09-30 14:25:57 +0200  

Processing by Devise::RegistrationsController#create as */*
Parameters: {"info"=>{"full_name"=>"User Admin", "email"=>"user.admin@email.com", "password"=>"[FILTERED]", "password_confirmation"=>"[FILTERED]"}}
  (0.1ms)  BEGIN
  (0.1ms)  ROLLBACK
Rendered /Users/antonpot/.rbenv/versions/2.2.0/lib/ruby/gems/2.2.0/gems/devise-3.5.2/app/views/devise/shared/_links.html.erb (0.2ms)
Rendered /Users/antonpot/.rbenv/versions/2.2.0/lib/ruby/gems/2.2.0/gems/devise-3.5.2/app/views/devise/registrations/new.html.erb (3.5ms)  
Completed 200 OK in 9ms (Views: 4.7ms | ActiveRecord: 0.2ms)

Now if I look into my Users Controller I can see I'm never even running my #create, as it never prints p "C"*99

class Users::RegistrationsController < Devise::RegistrationsController
  def create
    p "C"*99
    user = User.new
    user.full_name = user_params[:full_name]
    user.email = user_params[:email].downcase
    user.password = user_params[:password]
    user.password_confirmation = user_params[:password_confirmation]
    if user.save
      render json: {success:{success: true, user_id: user.id, errorMessage: nil, errorNumber: 201}}
    else
      render json: {success:{success: false, errorMessage: user.errors.messages.to_s, errorNumber: 400}}
    end
  end
end

Why can't I call it? What do I need to do for this to work?


Solution

  • First

    This issue because of cross-site-scripting problem. The client url is different than the api url. The HTTP method OPTIONS, is a pre-flight request that the browser makes to determine whether a cross-domain AJAX request should be allowed.

    if cross-domain AJAX request is allowed the POST which is the actual request will be sent.

    So you need to allow cross-domain AJAX request on your server.

    Second

    Make sure your routes.rb is set correctly.

    You need to configure your devise and instruct him to use your customized registration_controller instead of default devise registration controller

    adjust devise_for :users in your routes.rb to be something like the following:

    devise_for :users, controllers: { registrations: "users/registrations"}
    

    assuming that the customized registration class exist in the users folder