Categories: spring-security

AngularJS+Spring Security using Basic Authentication

This post shows how an AngularJS application can consume a REST API which is secured with Basic authentication using Spring Security.

Post Secure Spring REST API with Basic Authentication shows in great details how to secure a REST API using Basic authentication with Spring Security. That application will serve as a Back-end for this example. Although we will touch the main concepts here, complete code for the back-end will not be repeated here again. Please download, install and start that application locally in order to test the AngularJS app from this post.

We will mainly focus onto Front-end side which is pure AngularJS based application, communicating nicely with our REST-API. Let’s get started.


What is Basic Authentication?

Traditional authentication approaches like login pages or session identification are good for web based clients involving human interaction but does not really fit well when communicating with [REST] clients which may not even be a web application. Think of an API over a server which tries to communicate with another API on a totally different server, without any human intervention.

Basic Authentication provides a solution for this problem, although not very secure. With Basic Authentication, clients send it’s Base64 encoded credentials with each request, using HTTP [Authorization] header . That means each request is independent of other request and server may/does not maintain any state information for the client, which is good for scalability point of view.

Front-end

1. Send Authorization header with each request in AngularJS

Since Authentication header needs to be sent with Each request, Interceptors are a good choice to handle that instead of manually specifying the header in all $http methods.

authInterceptor.js

angular.module('myApp')
.factory('AuthInterceptor', [function() {  
    return {
 // Send the Authorization header with each request
        'request': function(config) {
   config.headers = config.headers || {};
   var encodedString = btoa("bill:abc123");
   config.headers.Authorization = 'Basic '+encodedString;
           return config;
        }
    };
}]);

Notice how we have used btoa() function to get the Base64 encoded string from user-credentials. That’s all we need to enable basic authentication. Rest of the application is typical AngularJS app communicating with REST API on server. Now this interceptor needs to be registered with AngularJS application, as shown below.

2. Application

app.js

'use strict';

var App = angular.module('myApp',[]);

App.config(['$httpProvider', function($httpProvider) {
  $httpProvider.interceptors.push('AuthInterceptor');
}]);

3. Service to communicate with REST API on server

Below service does not refer to any security related stuff. Thanks to interceptors, it is managed seperately.
UserService.js

'use strict';

angular.module('myApp').factory('UserService', ['$http', '$q', function($http, $q){

    var REST_SERVICE_URI = 'http://localhost:8080/SecureRESTApiWithBasicAuthentication/user/';

    var factory = {
        fetchAllUsers: fetchAllUsers,
        createUser: createUser,
        updateUser:updateUser,
        deleteUser:deleteUser
    };

    return factory;

    function fetchAllUsers() {
        var deferred = $q.defer();
        $http.get(REST_SERVICE_URI)
            .then(
            function (response) {
                deferred.resolve(response.data);
            },
            function(errResponse){
                console.error('Error while fetching Users');
                deferred.reject(errResponse);
            }
        );
        return deferred.promise;
    }

    function createUser(user) {
        var deferred = $q.defer();
        $http.post(REST_SERVICE_URI, user)
            .then(
            function (response) {
                deferred.resolve(response.data);
            },
            function(errResponse){
                console.error('Error while creating User');
                deferred.reject(errResponse);
            }
        );
        return deferred.promise;
    }


    function updateUser(user, id) {
        var deferred = $q.defer();
        $http.put(REST_SERVICE_URI+id, user)
            .then(
            function (response) {
                deferred.resolve(response.data);
            },
            function(errResponse){
                console.error('Error while updating User');
                deferred.reject(errResponse);
            }
        );
        return deferred.promise;
    }

    function deleteUser(id) {
        var deferred = $q.defer();
        $http.delete(REST_SERVICE_URI+id)
            .then(
            function (response) {
                deferred.resolve(response.data);
            },
            function(errResponse){
                console.error('Error while deleting User');
                deferred.reject(errResponse);
            }
        );
        return deferred.promise;
    }

}]);

4. Controller

user_controller.js

'use strict';

angular.module('myApp').controller('UserController', ['$scope', 'UserService', function($scope, UserService) {
    var self = this;
    self.user={id:null,username:'',address:'',email:''};
    self.users=[];

    self.submit = submit;
    self.edit = edit;
    self.remove = remove;
    self.reset = reset;


    fetchAllUsers();

    function fetchAllUsers(){
        UserService.fetchAllUsers()
            .then(
            function(d) {
                self.users = d;
            },
            function(errResponse){
                console.error('Error while fetching Users');
            }
        );
    }

    function createUser(user){
        UserService.createUser(user)
            .then(
            fetchAllUsers,
            function(errResponse){
                console.error('Error while creating User');
            }
        );
    }

    function updateUser(user, id){
        UserService.updateUser(user, id)
            .then(
            fetchAllUsers,
            function(errResponse){
                console.error('Error while updating User');
            }
        );
    }

    function deleteUser(id){
        UserService.deleteUser(id)
            .then(
            fetchAllUsers,
            function(errResponse){
                console.error('Error while deleting User');
            }
        );
    }

    function submit() {
        if(self.user.id===null){
            console.log('Saving New User', self.user);
            createUser(self.user);
        }else{
            updateUser(self.user, self.user.id);
            console.log('User updated with id ', self.user.id);
        }
        reset();
    }

    function edit(id){
        console.log('id to be edited', id);
        for(var i = 0; i < self.users.length; i++){
            if(self.users[i].id === id) {
                self.user = angular.copy(self.users[i]);
                break;
            }
        }
    }

    function remove(id){
        console.log('id to be deleted', id);
        if(self.user.id === id) {//clean form if the user to be deleted is shown there.
            reset();
        }
        deleteUser(id);
    }


    function reset(){
        self.user={id:null,username:'',address:'',email:''};
        $scope.myForm.$setPristine(); //reset Form
    }

}]);

5. View

index.html

<html>
<head>
<title>Form Demo</title>
<style>
.username.ng-valid {
background-color: lightgreen;
}
.username.ng-dirty.ng-invalid-required {
background-color: red;
}
.username.ng-dirty.ng-invalid-minlength {
background-color: yellow;
}

.email.ng-valid {
background-color: lightgreen;
}
.email.ng-dirty.ng-invalid-required {
background-color: red;
}
.email.ng-dirty.ng-invalid-email {
background-color: yellow;
}

</style>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css">
<link rel="stylesheet" href="./css/app.css">
</head>
<body ng-app="myApp">
<div class="generic-container" ng-controller="UserController as ctrl">
<div class="panel panel-default">
<div class="panel-heading"><span class="lead">User Registration Form </span></div>
<div class="formcontainer">
<form ng-submit="ctrl.submit()" name="myForm" class="form-horizontal">
<input type="hidden" ng-model="ctrl.user.id" />
<div class="row">
<div class="form-group col-md-12">
<label class="col-md-2 control-lable" >

Back-end

1. REST API

Shown below is the REST API our Angular app will be communicating with.

package com.websystique.springmvc.controller;
 
import java.util.List;
 
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.util.UriComponentsBuilder;
 
import com.websystique.springmvc.model.User;
import com.websystique.springmvc.service.UserService;
 
@RestController
public class HelloWorldRestController {
 
    @Autowired
    UserService userService;  //Service which will do all data retrieval/manipulation work
 
     
    //-------------------Retrieve All Users--------------------------------------------------------
     
    @RequestMapping(value = "/user/", method = RequestMethod.GET)
    public ResponseEntity<List<User>> listAllUsers() {
        List<User> users = userService.findAllUsers();
        if(users.isEmpty()){
            return new ResponseEntity<List<User>>(HttpStatus.NO_CONTENT);//You many decide to return HttpStatus.NOT_FOUND
        }
        return new ResponseEntity<List<User>>(users, HttpStatus.OK);
    }
 
 
    //-------------------Retrieve Single User--------------------------------------------------------
     
    @RequestMapping(value = "/user/{id}", method = RequestMethod.GET, produces = {MediaType.APPLICATION_JSON_VALUE,MediaType.APPLICATION_XML_VALUE})
    public ResponseEntity<User> getUser(@PathVariable("id") long id) {
        System.out.println("Fetching User with id " + id);
        User user = userService.findById(id);
        if (user == null) {
            System.out.println("User with id " + id + " not found");
            return new ResponseEntity<User>(HttpStatus.NOT_FOUND);
        }
        return new ResponseEntity<User>(user, HttpStatus.OK);
    }
 
     
     
    //-------------------Create a User--------------------------------------------------------
     
    @RequestMapping(value = "/user/", method = RequestMethod.POST)
    public ResponseEntity<Void> createUser(@RequestBody User user, UriComponentsBuilder ucBuilder) {
        System.out.println("Creating User " + user.getName());
 
        if (userService.isUserExist(user)) {
            System.out.println("A User with name " + user.getName() + " already exist");
            return new ResponseEntity<Void>(HttpStatus.CONFLICT);
        }
 
        userService.saveUser(user);
 
        HttpHeaders headers = new HttpHeaders();
        headers.setLocation(ucBuilder.path("/user/{id}").buildAndExpand(user.getId()).toUri());
        return new ResponseEntity<Void>(headers, HttpStatus.CREATED);
    }
 
     
    //------------------- Update a User --------------------------------------------------------
     
    @RequestMapping(value = "/user/{id}", method = RequestMethod.PUT)
    public ResponseEntity<User> updateUser(@PathVariable("id") long id, @RequestBody User user) {
        System.out.println("Updating User " + id);
         
        User currentUser = userService.findById(id);
         
        if (currentUser==null) {
            System.out.println("User with id " + id + " not found");
            return new ResponseEntity<User>(HttpStatus.NOT_FOUND);
        }
 
        currentUser.setName(user.getName());
        currentUser.setAge(user.getAge());
        currentUser.setSalary(user.getSalary());
         
        userService.updateUser(currentUser);
        return new ResponseEntity<User>(currentUser, HttpStatus.OK);
    }
 
    //------------------- Delete a User --------------------------------------------------------
     
    @RequestMapping(value = "/user/{id}", method = RequestMethod.DELETE)
    public ResponseEntity<User> deleteUser(@PathVariable("id") long id) {
        System.out.println("Fetching & Deleting User with id " + id);
 
        User user = userService.findById(id);
        if (user == null) {
            System.out.println("Unable to delete. User with id " + id + " not found");
            return new ResponseEntity<User>(HttpStatus.NOT_FOUND);
        }
 
        userService.deleteUserById(id);
        return new ResponseEntity<User>(HttpStatus.NO_CONTENT);
    }
 
     
    //------------------- Delete All Users --------------------------------------------------------
     
    @RequestMapping(value = "/user/", method = RequestMethod.DELETE)
    public ResponseEntity<User> deleteAllUsers() {
        System.out.println("Deleting All Users");
 
        userService.deleteAllUsers();
        return new ResponseEntity<User>(HttpStatus.NO_CONTENT);
    }
 
}

2. Enable Basic Authentication in Spring Security

With two steps, you can enable the Basic Authentication in Spring Security Configuration.
1. Configure httpBasic : Configures HTTP Basic authentication. [http-basic in XML]
2. Configure authentication entry point with BasicAuthenticationEntryPoint : In case the Authentication fails [invalid/missing credentials], this entry point will get triggered. It is very important, because we don’t want [Spring Security default behavior] of redirecting to a login page on authentication failure [ We don’t have a login page]. Additionally, we want to remain stateless [no session information needs to be maintained on server]. Shown below is the complete Spring Security configuration with httpBasic and entry point setup.

package com.websystique.springmvc.security;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;

@Configuration
@EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {

 private static String REALM="MY_TEST_REALM";
 
 @Autowired
 public void configureGlobalSecurity(AuthenticationManagerBuilder auth) throws Exception {
  auth.inMemoryAuthentication().withUser("bill").password("abc123").roles("ADMIN");
  auth.inMemoryAuthentication().withUser("tom").password("abc123").roles("USER");
 }
 
 @Override
 protected void configure(HttpSecurity http) throws Exception {
 
   http.csrf().disable()
    .authorizeRequests()
    .antMatchers("/user/**").hasRole("ADMIN")
  .and().httpBasic().realmName(REALM).authenticationEntryPoint(getBasicAuthEntryPoint())
  .and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);//We don't need session.
  }
 
 @Bean
 public CustomBasicAuthenticationEntryPoint getBasicAuthEntryPoint(){
  return new CustomBasicAuthenticationEntryPoint();
 }
 
    /* To allow Pre-flight [OPTIONS] request from browser */    @Override
    public void configure(WebSecurity web) throws Exception {
        web.ignoring().antMatchers(HttpMethod.OPTIONS, "/**");
    }
}
package com.websystique.springmvc.security;

import java.io.IOException;
import java.io.PrintWriter;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.www.BasicAuthenticationEntryPoint;

public class CustomBasicAuthenticationEntryPoint extends BasicAuthenticationEntryPoint {

    @Override
    public void commence(final HttpServletRequest request, 
      final HttpServletResponse response, 
      final AuthenticationException authException) throws IOException, ServletException {
     
     response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
     response.addHeader("WWW-Authenticate", "Basic realm=" + getRealmName() + "");
        
        PrintWriter writer = response.getWriter();
        writer.println("HTTP Status 401 : " + authException.getMessage());
    }
    
    @Override
    public void afterPropertiesSet() throws Exception {
        setRealmName("MY_TEST_REALM");
        super.afterPropertiesSet();
    }
}

Running the application

Back-end : Build and deploy the Backend from Post Secure Spring REST API with Basic Authentication.
Front-end : Download the AngularJS app from this post, deploy it [Put it in htdocs folder of your Apache server e.g, and start Apache]. You could have run FE, without even putting it behind Apache Server, but as your FE app grows with templates , it becomes a necessity to use a server.

Browse to http://localhost/AngularClientWithBasicAuth/

Open the Developer tools and verify the request, a Basic Authentication header is sent.

You might have notice that there is one more request towards server before the actual GET. It’s a OPTION request fired by browser itself, not under application control.

In order to allow this pre-flight request, we have adopted our Security configuration with web.ignoring().antMatchers(HttpMethod.OPTIONS, "/**");.

Download Source Code

Front-end Application:

Back-end Application:

References

View Comments

  • Thank you for the auspicious writeup. It in fact was a amusement account it. Look advanced to more added agreeable from you! By the way, how could we communicate?

  • مع مجموعة واسعة من التركيبات ، بما في ذلك الأكواع ، المحملات ، الوصلات ، وأكثر من ذلك ، يقدم مصنع إيليت بايب Elite Pipe حلولًا شاملة لأنظمة وتركيبات الأنابيب المختلفة.

  • I am new to Angular and Spring, and learning it. I have a doubt that whether authInterceptor.js file will be visible publicly in browser, as JS can be viewed by any one on browser. If yes, than how can spring REST webservice can be secure as anyone can know username and password for the authentication.

  • Hi Websystique,

    Thanks for your great article, I read one of your article angular + spring MVC and implemented to my project. now i am trying to learn Spring Security + angular, when i downloaded your sample code both angular & backend and tried to run it, i am getting error - Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource.
    Although i am adding CORSFilter as per your code. Please help me

    • Hi Atul, sorry i was out for sometime.Regarding your issue,you should not get this error if you have correctly added the CORS filter with Access-Control-Allow-Origin header for all.

Share
Published by

Recent Posts

Spring Boot + AngularJS + Spring Data + JPA CRUD App Example

In this post we will be developing a full-blown CRUD application using Spring Boot, AngularJS, Spring Data, JPA/Hibernate and MySQL,…

7 years ago

Spring Boot Rest API Example

Spring Boot complements Spring REST support by providing default dependencies/converters out of the box. Writing RESTful services in Spring Boot…

7 years ago

Spring Boot WAR deployment example

Being able to start the application as standalone jar is great, but sometimes it might not be possible to run…

7 years ago

Spring Boot Introduction + hello world example

Spring framework has taken the software development industry by storm. Dependency Injection, rock solid MVC framework, Transaction management, messaging support,…

7 years ago

Secure Spring REST API using OAuth2

Let's secure our Spring REST API using OAuth2 this time, a simple guide showing what is required to secure a…

8 years ago

Secure Spring REST API using Basic Authentication

So You've got the REST API for your application, and now you want to secure it. How to do that?…

8 years ago