Implementing Authentication in Angular Applications

Ravi
Ravi
Share

Authentication and authorization are important pieces on almost every serious application. Single Page Applications (SPAs) are no exception. The application may not expose all of its data and functionality to just any user. Users may have to authenticate themselves to see certain portions of the application, or to perform certain action on the application. To identify the user in the application, we need get the user logged in.

There is a difference in the way user management is implemented in traditional server-driven applications and Single Page Applications. The only way in which an SPA can interact with its server components is through AJAX. This is true even for logging in and logging out.

The server responsible for identifying the user has to expose an authentication endpoint. The SPA will send the credentials entered by the user to this endpoint to for verification. In a typical token based authentication system, the service may respond with an access token or with an object containing the name and role of the logged in user after validating the credentials. The client has to use this access token in all secured API requests made to the server.

As the access token will be used multiple times, it is better to store it on the client side. In Angular, we can store the value in a service or a value as they are singleton objects on the client side. But, if the user refreshes the page, the value in the service or value would be lost. In such case, it is better to store the token using one of the persistence mechanisms offered by the browser; preferably sessionStorage, as it is cleared once the browser is closed.

Implementing Login

Let’s have a look at some code now. Assume that we have all server side logic implemented and the service exposes a REST endpoint at /api/login to check login credentials and return an access token. Let’s write a simple service that performs the login action by hitting the authentication endpoint. We will add more functionality to this service later:

app.factory("authenticationSvc", function($http, $q, $window) {
  var userInfo;

  function login(userName, password) {
    var deferred = $q.defer();

    $http.post("/api/login", {
      userName: userName,
      password: password
    }).then(function(result) {
      userInfo = {
        accessToken: result.data.access_token,
        userName: result.data.userName
      };
      $window.sessionStorage["userInfo"] = JSON.stringify(userInfo);
      deferred.resolve(userInfo);
    }, function(error) {
      deferred.reject(error);
    });

    return deferred.promise;
  }

  return {
    login: login
  };
});

In actual code, you may want to re-factor the statement storing data to sessionStorage into a separate service, as this service gets multiple responsibilities if we do this. I am leaving it in the same service to keep the demo simple. This service can be consumed by a controller that handles the login functionality for the application.

Securing Routes

We may have a set of secured routes in the application. If a user is not logged in and attempts to enter one of those routes, the user should be directed to the login page. This can be achieved using the resolve block in the routing options. The following snippet gives an idea on the implementation:

$routeProvider.when("/", {
  templateUrl: "templates/home.html",
  controller: "HomeController",
  resolve: {
    auth: ["$q", "authenticationSvc", function($q, authenticationSvc) {
      var userInfo = authenticationSvc.getUserInfo();

      if (userInfo) {
        return $q.when(userInfo);
      } else {
        return $q.reject({ authenticated: false });
      }
    }]
  }
});

The resolve block can contain multiple blocks of statements that have to return promise objects on completion. Just to clarify, the name auth defined above is not defined by the framework; I defined it. You can change the name to anything based on the use case.

There can be multiple reasons to pass or reject the route. Based on the scenario, you can pass an object while resolving/rejecting the promise. We haven’t implemented the getLoggedInUser() method yet in the service. It is a simple method that returns the loggedInUser object from the service.

app.factory("authenticationSvc", function() {
  var userInfo;

  function getUserInfo() {
    return userInfo;
  }
});

The objects sent via the promise in the above snippet are broadcasted via $rootScope. If the route is resolved, the event $routeChangeSuccess is broadcast. However, if the route is failed, the event $routeChangeError is broadcast. We can listen to the $routeChangeError event and redirect the user to the login page. As the event is at $rootScope level, it is better to attach handlers to the event in a run block.

app.run(["$rootScope", "$location", function($rootScope, $location) {
  $rootScope.$on("$routeChangeSuccess", function(userInfo) {
    console.log(userInfo);
  });

  $rootScope.$on("$routeChangeError", function(event, current, previous, eventObj) {
    if (eventObj.authenticated === false) {
      $location.path("/login");
    }
  });
}]);

Handling Page Refresh

When a user hits refresh on a page, the service loses its state. We have to get the data from the browser’s session storage and assign it to the variable loggedInUser. As a factory is invoked only once, we can set this variable in an initialization function, as shown below.

function init() {
  if ($window.sessionStorage["userInfo"]) {
    userInfo = JSON.parse($window.sessionStorage["userInfo"]);
  }
}

init();

Logging Out

When a user logs out of the application, the corresponding API has to be invoked with the access token included in the request headers. Once the user is logged out, we should clear the data in sessionStorage as well. The following example contains the logout function that has to be added to the authentication service.

function logout() {
  var deferred = $q.defer();

  $http({
    method: "POST",
    url: logoutUrl,
    headers: {
      "access_token": userInfo.accessToken
    }
  }).then(function(result) {
    $window.sessionStorage["userInfo"] = null;
    userInfo = null;
    deferred.resolve(result);
  }, function(error) {
    deferred.reject(error);
  });

  return deferred.promise;
}

Conclusion

The approach for implementing authentication in Single Page Applications is quite different from that of traditional web applications. As the majority of the work is carried out on the client side, the state of the user also has to be stored somewhere in the client. It is important to remember that the state should be maintained and validated on the server as well, as a hacker can potentially steal the data stored on the client system.

The source code from this article is available for download on GitHub.

Frequently Asked Questions on Implementing Authentication in Angular Applications

How can I use the withCredentials property in Angular?

The withCredentials property is used to include authentication cookies in the HTTP request. In Angular, you can use it in the HttpClient module. When making a request, you can set the withCredentials property to true. Here’s an example:

this.http.get(url, { withCredentials: true }).subscribe(...);
This will include any cookies that the server might have sent previously in the request.

What is the purpose of HttpInterceptor in Angular?

HttpInterceptor is a feature in Angular that allows you to intercept and modify HTTP requests globally before they are sent to the server. It’s useful for a variety of tasks, such as adding authentication tokens to all requests or handling errors globally.

How can I create a custom HttpInterceptor to handle withCredentials?

To create a custom HttpInterceptor, you need to create a service that implements the HttpInterceptor interface. Here’s an example:

@Injectable()
export class WithCredentialsInterceptor implements HttpInterceptor {
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
const clonedReq = req.clone({ withCredentials: true });
return next.handle(clonedReq);
}
}
This interceptor will clone every request and set the withCredentials property to true.

Why am I getting a CORS error when using withCredentials?

CORS (Cross-Origin Resource Sharing) is a security feature that restricts how resources are shared across domains. If you’re getting a CORS error, it means that the server is not configured to accept requests from your domain. To fix this, you need to configure the server to include your domain in the ‘Access-Control-Allow-Origin’ header and to set ‘Access-Control-Allow-Credentials’ to true.

How can I use the XMLHttpRequest withCredentials property?

The XMLHttpRequest withCredentials property works similarly to the Angular HttpClient withCredentials property. It’s used to include cookies in the request. Here’s an example:

var xhr = new XMLHttpRequest();
xhr.open('GET', url, true);
xhr.withCredentials = true;
xhr.send();
This will send a GET request to the specified URL with cookies included.

How can I handle errors in Angular HttpClient?

You can handle errors in Angular HttpClient by using the catchError operator in the pipe method. Here’s an example:

this.http.get(url, { withCredentials: true }).pipe(
catchError(error => {
// Handle the error here
return throwError(error);
})
).subscribe(...);
This will catch any errors that occur during the request and pass them to the error handling function.

How can I set default headers in Angular HttpClient?

You can set default headers in Angular HttpClient by using the HttpHeaders class. Here’s an example:

const headers = new HttpHeaders({
'Content-Type': 'application/json',
'Authorization': 'Bearer ' + token
});

this.http.get(url, { headers: headers, withCredentials: true }).subscribe(...);
This will set the ‘Content-Type’ and ‘Authorization’ headers for the request.

How can I send a POST request with Angular HttpClient?

You can send a POST request with Angular HttpClient by using the post method. Here’s an example:

const body = { key: 'value' };
this.http.post(url, body, { withCredentials: true }).subscribe(...);
This will send a POST request to the specified URL with the specified body.

How can I use observables with Angular HttpClient?

Angular HttpClient methods return observables. You can subscribe to these observables to handle the response. Here’s an example:

this.http.get(url, { withCredentials: true }).subscribe(response => {
// Handle the response here
});
This will send a GET request to the specified URL and handle the response in the subscribe method.

How can I cancel a request with Angular HttpClient?

You can cancel a request with Angular HttpClient by unsubscribing from the observable. Here’s an example:

const subscription = this.http.get(url, { withCredentials: true }).subscribe(...);
// Later...
subscription.unsubscribe();
This will cancel the request if it hasn’t completed yet.