.   .   .
We're always looking for great talent‼️ 🚀😄 And we're passionate about driving innovation and improving lives. Join us as we help social impact organizations and enterprise customers build their web apps. Apply today!
.   .   .

I love using Devise on my Rails projects and love ReactJS but figuring out how to hook the two together was more complicated than I would have liked. So I figured I’d share my findings! Comments and criticisms are always welcome.

To preface this post, it makes sense to describe my setup. I’m running a Rails app that is primarily handling the backend API and serving the ReactJs app through a catch-all route. ReactJS handles all routes not explicitly granted to Rails. With this setup, ReactJS always has access to the correct CSRF token which it can grab from the head of the HTML document. I could explain further but the exact setup I am using is described in this post so I would recommend giving that a quick read or just taking a look at the example app I put together if you feel so inclined. Note that I linked to an older version that matches this post.

I’m going to assume you know how to set up a basic Devise configuration and will also assume that you already have Devise up and running. Lastly, all files in the examples below should be relative to your rails root. With all of that being said, lets rock and roll.

Some needed Rails Stuff

Step 1: Make sure devise accepts json requests.

You will need the following:

# config/application.rb
...
config.to_prepare do
  DeviseController.respond_to :html, :json
end
...

Step 2: Make some minor changes to your app/controllers/application.rb

Add the following somewhere at the top of your file replacing where it says ‘protectfromforgery with: :exception’:

#app/controllers/application.rb
...
protect_from_forgery with: :null_session
 
before_action :configure_permitted_parameters, if: :devise_controller?
...

Add the following somewhere in your file to make sure that devise allows the setting of some more parameters.

# app/controllers/application.rb
def configure_permitted_parameters
  devise_parameter_sanitizer.for(:sign_up) << :name
  devise_parameter_sanitizer.for(:sign_up) << :provider
  devise_parameter_sanitizer.for(:sign_up) << :uid
end

Create an endpoint that returns false if a user is not logged in and useful data otherwise

Create the following controller

#app/controllers/auth_controller.rb
class AuthController < ApplicationController
  def is_signed_in?
    if user_signed_in?
      render :json => {"signed_in" => true, "user" => current_user}.to_json()
    else
      render :json => {"signed_in" => false}.to_json()
    end
 
  end
end

Add the following to your routes file

#config/routes.rb
 
scope :auth do
  get 'is_signed_in', to: 'auth#is_signed_in?'
end

Some needed React Stuff

This is where it gets a little more tricky. We will have the following objectives:

  1. To have a way of determining whether the current user is SignedIn from the ReactJs App.
  2. To make sure we can access and send the correct CSRF token with each request.
  3. To be able to Sign in from the ReactJs App.
  4. To be able to Sign out from the ReactJs App.
  5. To be able to register from the ReactJs app.
    The purpose of this post isn’t to teach you how to tie this all together but how to get each of the needed parts in place so my apologies if anything is confusing!

Step 1: Connect your app’s entry point to the “issignedin” endpoint

In my case, the entry point is at react/components/App.js

// react/components/App.js
var React        = require('react');
var Router       = require('react-router');
var RouteHandler = Router.RouteHandler;
var $            = require('jquery');
 
var App =
  React.createClass({
    componentWillMount: function() {
      $.ajax({
        method: "GET",
        url: "/auth/is_signed_in.json"
      })
      .done(function(data){
        this.setState({ signedIn: data.signed_in });
      }.bind(this));
    },
    getInitialState: function() {
      return { signedIn: null };
    },
    render:function(){
      return <RouteHandler signedIn={this.state.signedIn}/>;
    }
});
module.exports = App;

Step 2: Make sure we can access and send the correct CSRF token with each request

This was a bit tricky, but due to my setup, I was able to grab the CSRF token from the head section of the page using the following.

//react/utils/Functions.js
var Functions = {
  getMetaContent: function(name) {
    var metas = document.getElementsByTagName('meta');
 
    for (var i=0; i<metas.length; i++) {
      if (metas[i].getAttribute("name") == name) {
        return metas[i].getAttribute("content");
      }
    }
 
    return "";
  }
}
 
module.exports = Functions;

That’s all you need for now. We’ll come back to that later. It will be needed for all PUT, POST and DELETE requests we make with ajax.

Step 3: Be able to sign in from the ReactJS App.

// react/components/auth/SignInForm.js
var React          = require('react');
var Functions      = require('../../utils/Functions.js');
var _              = require('lodash');
var $              = require('jquery');
 
var SignInForm =
  React.createClass({
    _handleInputChange: function(ev) {
      // Get a deep clone of the component's state before the input change.
      var nextState = _.cloneDeep(this.state);
 
      //Update the state of the component
      nextState[ev.target.name] = ev.target.value;
   
      // Update the component's state with the new state
      this.setState(nextState);
    },
    getInitialState: function() {
      return {
        email: '',
        password: ''
      };
    },
    _handleSignInClick: function(e) {
      $.ajax({
        method: "POST",
        url: "/users/sign_in.json",
        data: {
          user: {
            email: this.state.email,
            password: this.state.password
          },
          authenticity_token: Functions.getMetaContent("csrf-token")
        }
      })
      .done(function(data){
        location.reload();
      }.bind(this));
    },
    render:function(){
      return (
          <form>
              <input type='email'
                name='email'
                placeholder='email'
                value={this.state.email}
                onChange={this._handleInputChange} />
              <input type='password'
                name='password'
                placeholder='password'
                value={this.state.password}
                onChange={this._handleInputChange} />
              <input type='submit' onClick={this._handleSignInClick} defaultValue='login' />
          </form>
      )
    }
  });
module.exports = SignInForm;

Step 4: Be able to sign out from the ReactJS App.

//react/components/auth/SignOutLink.js
var React     = require('react');
var $         = require('jquery');
var Functions = require('../../utils/Functions.js');
 
var SignOutLink =
  React.createClass({
    render:function(){
      return (
        <a href="#" onClick={this._signOut}>Sign out</a>
      )
    },
    _signOut: function(){
      $.ajax({
        method: "DELETE",
        url: "/users/sign_out.json",
        data: {
          authenticity_token: Functions.getMetaContent("csrf-token")
        }
      }).done(function(){
        location.reload();
      });
    }
  });
module.exports = SignOutLink;

Step 5: Be able to register from the ReactJS App.

//react/components/auth/SignUpForm.js
var React          = require('react');
var _              = require('lodash');
var Functions      = require('../../utils/Functions.js');
 
var SignUpForm =
  React.createClass({
    _handleInputChange: function(ev) {
      // Get a deep clone of the component's state before the input change.
      var nextState = _.cloneDeep(this.state);
 
      //Update the state of the component
      nextState[ev.target.name] = ev.target.value;
   
      // Update the component's state with the new state
      this.setState(nextState);
    },
    getInitialState: function() {
      return {
        email: '',
        password: '',
        password_confirmation: '',
        name: ''
      };
    },
    _handleRegistrationClick: function(e) {
      $.ajax({
        method: "POST",
        url: "/users.json",
        data: {
          user: {
            email: this.state.email,
            uid: this.state.email,
            password: this.state.password,
            password_confirmation: this.state.password_confirmation,
            name: this.state.name,
            provider: "email"
          },
          authenticity_token: Functions.getMetaContent("csrf-token")
        }
      })
      .done(function(data){
        location.reload();
      }.bind(this));
    },
    render:function(){
      return (
          <form>
              <input type='text'
                name='name'
                placeholder='name'
                value={this.state.name}
                onChange={this._handleInputChange} />
 
              <input type='email'
                name='email'
                placeholder='email'
                value={this.state.email}
                onChange={this._handleInputChange} />
 
              <input type='password'
                name='password'
                placeholder='password'
                value={this.state.password}
                onChange={this._handleInputChange} />
 
              <input type='password'
                name='password_confirmation'
                placeholder='re-type password'
                value={this.state.password_confirmation}
                onChange={this.handleInputChange} />
            </div>
            <input onClick={this._handleRegistrationClick} defaultValue="sign up"/>
          </form>
      )
    }
  });
module.exports = SignUpForm;

And that’s it! Assuming you understand the basics of React, you should be able to use this tutorial to start whipping some stuff together. Let me know your thoughts and if you need me to elaborate on anything! If you’re still having trouble, feel free to reach out to my team at labs@chiedo.com.

.   .   .

We're Hiring‼️ ?? Looking to join our team of web developers? We're passionate about innovation, family, and community. Apply today!