Tag Archives: grails

Using Grails REST for authentication in an AngularJS SPA

Introduction

I have played around with AngularJS for a few weeks now. I like how it provides for separation of concerns on the client-side that us server-side programmers prefer. I have built a couple of controllers that are retrieving data via REST from a Grails back-end domain. That is all well and good, but I want to protect access to the data, and most web applications have authentication so it is something good to figure out anyways.

I have made a few assumptions about Grails:

  • You are using Grails 2.3.x
  • You are using the asset-pipeline plugin
  • You are using Bower to manage AngularJS modules
  • You already have some AngularJS controllers defined which access data that you would like to protect

Configure Grails for authentication

For Grails, I am familiar with the Spring Security plugin, so I want to find a plugin along those lines. I found a nice plugin which provides “… authentication for REST APIs based on Spring Security …” using “… a token-based workflow.” Sounds exactly like what I need! It is a pretty easy plugin to set up.

  1. Install the plugin via a BuildConfig.groovy dependency
  2. Run grails s2-quickstart com.asoftwareguy.example.auth User Role to create the domain classes for authentication
  3. Create a class to hold the authentication tokens in the database:
    package com.asoftwareguy.example.auth
    
    class AuthenticationToken {
    
        String username
        String token
    }
    
    
  4. Add the proper configurations to Config.groovy:
    // Added by the Spring Security Core plugin:
    grails.plugin.springsecurity.userLookup.userDomainClassName = 'com.asoftwareguy.example.auth.User'
    grails.plugin.springsecurity.userLookup.authorityJoinClassName = 'com.asoftwareguy.example.auth.UserRole'
    grails.plugin.springsecurity.authority.className = 'com.asoftwareguy.example.auth.Role'
    grails.plugin.springsecurity.securityConfigType = 'InterceptUrlMap'
    grails.plugin.springsecurity.interceptUrlMap = [
            '/':                    ['permitAll'],
            '/assets/**':           ['permitAll'],
            '/partials/**':         ['permitAll'],
            '/**':                  ['isFullyAuthenticated()']
    ]
    
    grails.plugin.springsecurity.rememberMe.persistent = false
    grails.plugin.springsecurity.rest.login.useJsonCredentials = true
    grails.plugin.springsecurity.rest.login.failureStatusCode = 401
    grails.plugin.springsecurity.rest.token.storage.useGorm = true
    grails.plugin.springsecurity.rest.token.storage.gorm.tokenDomainClassName = 'com.asoftwareguy.example.auth.AuthenticationToken'
    grails.plugin.springsecurity.rest.token.storage.gorm.tokenValuePropertyName = 'token'
    grails.plugin.springsecurity.rest.token.storage.gorm.usernamePropertyName = 'username'
    

    Notice that access to /partials and /assets is fully permitted; all other access requires authentication. Access to /api is allowed via the plugin itself.
    Also note the entry for grails.plugin.springsecurity.rest.login.failureStatusCode = 401. This is necessary because the AngularJS module uses HTTP status code 403 to verify that authentication is required to access a resource, which is different from an authentication error when attempting to authenticate. We override in Grails to use HTTP status code 401 to indicate that.

  5. Add a user entry or two into Bootstrap.groovy, so you have some credentials set up.
    class BootStrap {
    
        def init = { servletContext ->
    
            User user = new User(username: "test", password: "test123")
            user.save()
    
            Role roleUser = new Role(authority: "ROLE_USER")
            roleUser.save()
    
            new UserRole(user: user, role: roleUser).save()
        }
        def destroy = {
        }
    }
    
  6. Run a few tests against the server to check that when provided a JSON object with username and password to /api/login, the server returns a token that is used to later retrieve data.

Configure AngularJS for authentication

Now that I have a back-end server accepting REST authentication requests and responding with a token I can use for later API calls, I need to set up my AngularJS application to use authentication. I settled on using the HTTP Auth Interceptor Module that is an AngularJS module.

    1. Change directory to grails-app/assets
    2. Run bower install angular-http-auth
    3. Add the require directive to your application manifest:
      //= require angular-http-auth/src/http-auth-interceptor
      
    4. Add the module declaration to your application:
      var exampleApp = angular.module('exampleApp', [
          'http-auth-interceptor',
          'ngRoute',
          'ui.bootstrap',
          'login',
          // others
      ]);
      
    5. Add a directive to your application to show the login form when required:
      exampleApp.directive('showLogin', function() {
          return {
              restrict: 'C',
              link: function(scope, element, attrs) {
                  var login = element.find('#login-holder');
                  var loginError = element.find('#login-error');
                  var main = element.find('#content');
                  var username = element.find('#username');
                  var password = element.find('#password');
      
                  login.hide();
                  loginError.hide();
      
                  scope.$on('event:auth-loginRequired', function() {
                      console.log('showing login form');
                      main.hide();
                      username.val('');
                      password.val('');
                      login.show();
                  });
                  scope.$on('event:auth-loginFailed', function() {
                      console.log('showing login error message');
                      username.val('');
                      password.val('');
                      loginError.show();
                  });
                  scope.$on('event:auth-loginConfirmed', function() {
                      console.log('hiding login form');
                      main.show();
                      login.hide();
                      username.val('');
                      password.val('');
                  });
              }
          }
      });
      function getLocalToken() {
         return localStorage["authToken"];
      }
      
      function getHttpConfig() {
          return {
              headers: {
                  'X-Auth-Token': getLocalToken()
              }
          };
      }
      
      function getAuthenticateHttpConfig() {
          return {
              ignoreAuthModule: true
          };
      }
      

      Notice a couple of other functions defined here as well. These are used later by the login controller(s) to set and retrieve the authentication request header/token combination. Here is the example HTML markup:

      <!DOCTYPE html>
      <html ng-app="exampleApp" lang="en">
      <!--[if lt IE 7 ]> <html lang="en" class="no-js ie6"> <![endif]-->
      <!--[if IE 7 ]>    <html lang="en" class="no-js ie7"> <![endif]-->
      <!--[if IE 8 ]>    <html lang="en" class="no-js ie8"> <![endif]-->
      <!--[if IE 9 ]>    <html lang="en" class="no-js ie9"> <![endif]-->
      <!--[if (gt IE 9)|!(IE)]><!--> <html lang="en" class="no-js"><!--<![endif]-->
      	<head>
      		<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
      		<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
      		<title><g:layoutTitle default="Example App"/></title>
              <meta name="viewport" content="width=device-width, initial-scale=1">
              <asset:link rel="shortcut icon" href="favicon.ico" type="image/x-icon"/>
              <asset:stylesheet href="font-awesome/css/font-awesome.css" />
              <asset:stylesheet href="bootstrap-css/css/bootstrap.css" />
              <asset:javascript src="application.js"/>
      		<g:layoutHead/>
      	</head>
      	<body class="show-login">
              <div class="page-header container">
                  <div class="row">
                      <div class="span6">
                          Welcome to Example App!
                      </div>
                      <div class="span6" style="text-align: right;" ng-controller="logoutController">
                          Welcome, username.
                          <a href="" ng-click="logOut()">(Log out)</a>
                      </div>
                  </div>
                  <div class="row" style="text-align: right;">
      
                  </div>
              </div>
      
              <div id="login-holder" class="container" style="width: 300px;">
                  <div id="login-error" class="alert alert-error">
                      <button type="button" class="close" onclick="$('#login-error').hide();">&times;</button>
                      Username and/or password incorrect.
                  </div>
                  <div id="loginbox">
                      <div id="login-inner" ng-controller="loginController">
                          <form name="loginForm" role="form" ng-submit="logIn()" autocomplete="off">
                              <div class="form-group">
                                  <label for="username">Username</label>
                                  <input id="username" class="form-control" type="text" ng-model="authData.username"/>
                              </div>
                              <div class="form-group">
                                  <label for="password">Password</label>
                                  <input id="password" class="form-control" type="password" ng-model="authData.password"/>
                              </div>
                              <input type="submit" class="btn btn-primary" value="Login"/>
                          </form>
                      </div>
                      <div class="clear"></div>
                  </div>
              </div>
              <div id="content" class="container">
      		    <g:layoutBody/>
              </div>
      	</body>
      </html>
      
    6. Define your login controller(s):
      var login = angular.module('login', []);
      
      login.controller('loginController',
          function ($rootScope, $scope, $http, authService) {
              console.log('loginController called');
      
              $scope.logIn = function() {
                  console.log('logIn called')
      
                  $http.post('api/login', { username: $scope.authData.username, password: $scope.authData.password }, getAuthenticateHttpConfig).
                      success(function(data) {
                          console.log('authentication token: ' + data.token);
                          localStorage["authToken"] = data.token;
                          authService.loginConfirmed({}, function(config) {
                              if(!config.headers["X-Auth-Token"]) {
                                  console.log('X-Auth-Token not on original request; adding it');
                                  config.headers["X-Auth-Token"] = getLocalToken();
                              }
                              return config;
                          });
                      }).
                      error(function(data) {
                          console.log('login error: ' + data);
                          $rootScope.$broadcast('event:auth-loginFailed', data);
                      });
              }
          }
      );
      
      login.controller('logoutController',
          function ($scope, $http, $location) {
              console.log('logoutController called');
      
              $scope.logOut = function() {
                  console.log('logOut called');
      
                  $http.post('api/logout', {}, getHttpConfig()).
                      success(function() {
                          console.log('logout success');
                          localStorage.clear();
                          $location.path("/")
                      }).
                      error(function(data) {
                          console.log('logout error: ' + data);
                      });
              }
          }
      );
      
      console.log('login controllers load complete');
      

Fire up the application and try to navigate to one of your existing AngularJS controllers which requests protected data from the back-end server. If you have not already authenticated, the authentication module broadcasts a login required event, the directive we defined traps that event and overlays the screen with the login form. The login form stays active until you successfully authenticate, at which point the authentication module broadcasts a login confirmed event, and our directive traps that event and hides the login form, and the original data request is replayed with the authentication token as a request header.

Conclusion

The combination of AngularJS for a client with Grails REST APIs on the server is a powerful one with a lot of promise. Hopefully this will help you out if you are trying your hand at building an application along these lines. I have provided an example application using this approach on GitHub.