Authentication with Devise and AngularJS

Ademar Tutor | Feb 10, 2015




Goal: Integrate Devise with AngularJS.

Demo App on Github: https://github.com/iamademar/rails_angular_devise_demo

Description: This is a step by step guide to help you create a login for users using Devise Gem with AngularJS and Rails.

Step 1: Install the necessary gems. Add the following to your Gemfile.

gem 'devise'  
gem 'angular_rails_csrf'

Step 2: Use angular-devise server. Add the following to your Bowerfile.

asset 'angular-devise'  

Step 3: Make angular-devise is loaded on your application by adding it to your app/assets/javascripts/application.js.

//= require angular-devise

Step 4: Install angular-devise using bower.

rake bower:install

Step 5: Inject angular-devise to your AngularJS application.

( function() {
  'use strict';

  angular.module('admin', [
    // Libraries
    'templates',
    'ngRoute',
    'Devise'
  ])
  .config(['$routeProvider',
    function($routeProvider) {

    }
  ]);
})();

Step 6: Add sign-in page to your AngularJS routes.

( function() {
  'use strict';

  angular.module('admin', [
    // Libraries
    'templates',
    'ngRoute',
    'Devise'
  ])
  .config(['$routeProvider',
    function($routeProvider) {
      $routeProvider
        .when('/sign_in', {
          templateUrl: 'admin/sign_in/sign_in.html'
        });
    }
  ]);
})();

Step 7: Create a sign-in page which contains a form having a ng-controller="signInCtrl as user".

<h2>Sign In</h2>  
<form ng-controller="signInCtrl as user" ng-submit="user.signIn()" novalidate>  
  <div class="form-group">
    <label for="inputEmail">Email Address</label>
    <input type="email" class="form-control" id="inputEmail" placeholder="Enter email" ng-model="user.credentials.email">
  </div>
  <div class="form-group">
    <label for="inputPassword">Password</label>
    <input type="password" class="form-control" id="inputPassword" placeholder="Password" ng-model="user.credentials.password">
  </div> 
  <input type="submit" class="btn btn-default" value="Sign Up" />
</form>  

Step 8: Create the signInCtrl:

( function() {
  'use strict';

  angular.module('public.ctrl.signIn', [])
  .controller('signInCtrl', ['Auth', '$scope', '$location',
    function(Auth, $scope, $location) {
      this.credentials = { email: '', password: '' };

      this.signIn = function() {
        // Code to use 'angular-devise' component
        Auth.login(this.credentials).then(function(user) {
          $location.path("/");
          alert('Successfully signed in user!')
        }, function(error) {
          console.info('Error in authenticating user!');
          alert('Error in signing in user!');
        });
      }
    }

  ]);

})();

Step 9: Try signing in with a any email and password. You should see this error logged in terminal:

Started POST "/users/sign_in.json" for ::1 at 2015-01-28 14:53:24 +0800

ActionController::RoutingError (No route matches [POST] "/users/sign_in.json"):

This error message verified that you are sending the user credentials to rails. Now time to setup devise in rails.

Step 10: Install Devise gem on rails and create the User model

rails generate devise:install 

Create the user mode:

rails generate devise User 

Step 11: Try signing in again on your app. You should see this error on the logs:

Started POST "/users/sign_in.json" for ::1 at 2015-01-28 15:19:22 +0800 Processing by Devise::SessionsController#create as JSON 
Parameters: {"user"=>{}, "session"=>{"user"=>{}}} Completed 401 Unauthorized in 7ms

Step 12: To test a working user account, go to rails console and a user.

User.create({ email: 'test@test.com', password: 'asdfasdfasdf' }) 

Try signing with the credentials you created. If signing is successful, it should redirect you to the landing page.

Step 13: You've already logged in, now it's time to setup up a the logout button. Add a sessions controller for a navbar.

( function() {
  'use strict';

  angular.module('public.ctrl.sessions', [])
  .controller('sessionCtrl', ['Auth', '$scope', '$location',
    function(Auth, $scope) {
      // Check on load if user signed in
      Auth.currentUser().then(function(user) {
        $scope.isAuthenticated = true;
      }, function(error) {
        // Log on console to check out what the error is.
      });

      $scope.$on('devise:login', function(event, currentUser) {
        $scope.isAuthenticated = true;
        // You can get data of current user (getting user's name and etc.)
        console.log(currentUser.inspect);
      });

      $scope.$on('devise:new-session', function(event, currentUser) {
        $scope.isAuthenticated = true;
      });

      $scope.$on('devise:logout', function(event, oldCurrentUser) {
        $scope.isAuthenticated = false;
      });

      $scope.$on('devise:new-registration', function(event, user) {
        $scope.isAuthenticated = true;
      });

      this.logout = function() {
        Auth.logout().then(function(oldUser) {
          alert("Successfully logged out!");
          $location.path("/");
        }, function(error) {
          // An error occurred logging out.
        });
      }
    }
  ]);
})();

Step 14: Edit your navbar

<nav>  
  <ul class="nav nav-pills pull-right" ng-controller="sessionCtrl as session">
    <li role="presentation"><a href="#/">Home</a></li>
    <li role="presentation" ng-hide="isAuthenticated"><a href="#/sign_in">Sign In</a></li>
    <li role="presentation" ng-show="isAuthenticated"><a href ="" ng-click="session.logout()">Logout</a></li>
  </ul>
</nav>  

Now your app has the ability to login and logout.