Angular Authentication - MEAN Machine - A beginner's practical guide to the JavaScript stack (2015)

MEAN Machine - A beginner's practical guide to the JavaScript stack (2015)

Angular Authentication

We now have the foundation for our MEAN stack application. We have talked extensively about the separation between backend server-side and frontend client-side code.

Up to this point, we have created a Node API and used JSON web tokens to authenticate it. This has all been server-side code and now we will integrate it with the frontend using the information we just learned about

The goal here is to start writing an Angular application and not have to edit the backend. Treating your applications like this can simulate similar situations where the backend API is built and maintained by someone other than yourself (like Facebook, Twitter, or GitHub APIs). We only have access to the frontend code.

The first step when building an application that will talk to a backend API is handling authentication. After all, we won’t be able to grab any data from the API unless we are authenticated.

Hooking Into Our Node API

When we created our Node API with JSON Web Token authentication in chapters 9 and 10, we developed an API endpoint (POST http://localhost:8080/api/authenticate) where we could send a username and password to receive a JWT.

Since our backend already has the tools to authenticate and provide a token, all we need to do is wire up the frontend Angular application to hit that endpoint, store the JWT client-side, and then we will be able to access all of the authenticated routes in our API.

Authentication Service

We’ll be building two different services, one for authentication and one for connecting to our API and grabbing user data. As a general rule, if you are grabbing or sending data from/to an outside source, a service will likely be your tool of choice.

To authenticate our users, we will create an authService. This will have 3 main functions:

· main auth functions (login, logout, get current user, check if logged in)

· token auth functions (get the token, save the token)

· auth interceptor (attach the token to HTTP requests, redirect if not logged in)

Each of the three factories has a very specific purpose which is why we can’t just create a single one.

The main auth functions are the ones that will be exposed to our application and usable within controllers.

The auth interceptor will be responsible for attaching the token to all HTTP requests. Remember in token based authentication, the token is required with all authenticated requests; this interceptor is how we will achieve the attachment. This interceptor will also be responsible for redirecting a user to the login page if they are not authenticated.

The token auth functions will be more of private factory for use within the other two auth factories. It will just have to save a token or get a token out of local storage.

Here is a quick overview of how our file will look:

1 angular.module('authService', [])

2

3 // ===================================================

4 // auth factory to login and get information

5 // inject $http for communicating with the API

6 // inject $q to return promise objects

7 // inject AuthToken to manage tokens

8 // ===================================================

9 .factory('Auth', function($http, $q, AuthToken) {

10

11 // create auth factory object

12 var authFactory = {};

13

14 // handle login

15

16 // handle logout

17

18 // check if a user is logged in

19

20 // get the user info

21

22 // return auth factory object

23 return authFactory;

24

25 })

26

27 // ===================================================

28 // factory for handling tokens

29 // inject $window to store token client-side

30 // ===================================================

31 .factory('AuthToken', function($window) {

32

33 var authTokenFactory = {};

34

35 // get the token

36

37 // set the token or clear the token

38

39 return authTokenFactory;

40

41 })

42

43 // ===================================================

44 // application configuration to integrate token into requests

45 // ===================================================

46 .factory('AuthInterceptor', function($q, AuthToken) {

47

48 var interceptorFactory = {};

49

50 // attach the token to every request

51

52 // redirect if a token doesn't authenticate

53

54 return interceptorFactory;

55

56 });

These are the three factories we will be creating all lumped into one Angular module called authService. For each, we are creating a new object, attaching functions to the object, and returning the object.

Notice that we are able to inject a factory into another factory. We are using the AuthToken factory in the other two factories. We are also injecting some Angular modules into our factories like $http, $q, and $location.

$location will be the module that we use to redirect users. This is how we redirect while still using the Angular routing mechanisms. This means that our user will be redirected without a page refresh.

$q is the module used to return promises. It will allow us to run functions asynchronously and return their values when they are done processing. In the previous chapter, we used success(), error(), and then() to act on a promise; we will be able to use those functions when returning a $q.

Let’s go through each of these factories one by one so that we can see how our entire authService will work.

Auth Token Factory

We’ll start with the AuthTokenFactory since that will be used by the other two factories. Essentially what we are doing is setting or getting data from our browser’s local storage. To check on what is in your local storage, just go into the Chrome Dev tools, click Resources, and check the Local Storage tab.

Local Storage

Local Storage

Here is the code for the AuthTokenFactory:

1 // ===================================================

2 // factory for handling tokens

3 // inject $window to store token client-side

4 // ===================================================

5 .factory('AuthToken', function($window) {

6

7 var authTokenFactory = {};

8

9 // get the token out of local storage

10 authTokenFactory.getToken = function() {

11 return $window.localStorage.getItem('token');

12 };

13

14 // function to set token or clear token

15 // if a token is passed, set the token

16 // if there is no token, clear it from local storage

17 authTokenFactory.setToken = function(token) {

18 if (token)

19 $window.localStorage.setItem('token', token);

20 else

21 $window.localStorage.removeItem('token');

22 };

23

24 return authTokenFactory;

25

26 })

We have just two functions here. getToken() and setToken(). $window.localStorage is the way that we can add, set or remove items from local storage.

Next up, let’s create the main Auth factory that we will be using within our application’s controllers.

Auth Factory

The main Auth factory will contain functions needed to log a user in, log a user out, check if a user is logged in, and get the user information.

Let’s see how that factory will look:

1 // ===================================================

2 // auth factory to login and get information

3 // inject $http for communicating with the API

4 // inject $q to return promise objects

5 // inject AuthToken to manage tokens

6 // ===================================================

7 .factory('Auth', function($http, $q, AuthToken) {

8

9 // create auth factory object

10 var authFactory = {};

11

12 // log a user in

13 authFactory.login = function(username, password) {

14

15 // return the promise object and its data

16 return $http.post('/api/authenticate', {

17 username: username,

18 password: password

19 })

20 .success(function(data) {

21 AuthToken.setToken(data.token);

22 return data;

23 });

24 };

25

26 // log a user out by clearing the token

27 authFactory.logout = function() {

28 // clear the token

29 AuthToken.setToken();

30 };

31

32 // check if a user is logged in

33 // checks if there is a local token

34 authFactory.isLoggedIn = function() {

35 if (AuthToken.getToken())

36 return true;

37 else

38 return false;

39 };

40

41 // get the logged in user

42 authFactory.getUser = function() {

43 if (AuthToken.getToken())

44 return $http.get('/api/me');

45 else

46 return $q.reject({ message: 'User has no token.' });

47 };

48

49 // return auth factory object

50 return authFactory;

51

52 })

We have 4 functions that will either return a promise object (through $http or $q) or a simple true/false.

login will create an $http.post() request to the /api/authenticate endpoint on our Node API.

logout will simply use the AuthToken factory to clear the token.

isLoggedIn will return true or false depending on if the token exists in local storage.

getUser will create an $http.get() request to the /me API endpoint to get the logged in user’s information.

Next, we’ll create the interceptor factory.

AuthInterceptor Factory

The AuthInterceptor factory will be responsible for attaching the token to all HTTP requests coming from our frontend application.

1 // ===================================================

2 // application configuration to integrate token into requests

3 // ===================================================

4 .factory('AuthInterceptor', function($q, $location, AuthToken) {

5

6 var interceptorFactory = {};

7

8 // this will happen on all HTTP requests

9 interceptorFactory.request = function(config) {

10

11 // grab the token

12 var token = AuthToken.getToken();

13

14 // if the token exists, add it to the header as x-access-token

15 if (token)

16 config.headers['x-access-token'] = token;

17

18 return config;

19 };

20

21 // happens on response errors

22 interceptorFactory.responseError = function(response) {

23

24 // if our server returns a 403 forbidden response

25 if (response.status == 403) {

26 AuthToken.setToken();

27 $location.path('/login');

28 }

29

30 // return the errors from the server as a promise

31 return $q.reject(response);

32 };

33

34 return interceptorFactory;

35

36 });

An interceptor in Angular will allow us to react to different HTTP request scenarios. When creating an interceptor, we have a few options available to us.

· request lets us intercept requests before they are sent

· response lets us change the response that we get back from a request

· requestError captures requests that have been cancelled

· responseError catches backend calls that fail. In this case, we will use it to catch 403 Forbidden errors if the token does not validate or does not exist.

If we receive a 403 error from our backend, we will be forced to redirect the user to the login page using $location.path('/login'). We will also clear any tokens in storage since those weren’t good enough to validate our user.

Those are the three factories necessary for creating authentication in an Angular application. We are able to communicate with the backend API, get and set tokens client-side, and attach the token to all our requests.

The Entire Auth Service File (authService.js)

1 angular.module('authService', [])

2

3 // ===================================================

4 // auth factory to login and get information

5 // inject $http for communicating with the API

6 // inject $q to return promise objects

7 // inject AuthToken to manage tokens

8 // ===================================================

9 .factory('Auth', function($http, $q, AuthToken) {

10

11 // create auth factory object

12 var authFactory = {};

13

14 // log a user in

15 authFactory.login = function(username, password) {

16

17 // return the promise object and its data

18 return $http.post('/api/authenticate', {

19 username: username,

20 password: password

21 })

22 .success(function(data) {

23 AuthToken.setToken(data.token);

24 return data;

25 });

26 };

27

28 // log a user out by clearing the token

29 authFactory.logout = function() {

30 // clear the token

31 AuthToken.setToken();

32 };

33

34 // check if a user is logged in

35 // checks if there is a local token

36 authFactory.isLoggedIn = function() {

37 if (AuthToken.getToken())

38 return true;

39 else

40 return false;

41 };

42

43 // get the logged in user

44 authFactory.getUser = function() {

45 if (AuthToken.getToken())

46 return $http.get('/api/me');

47 else

48 return $q.reject({ message: 'User has no token.' });

49 };

50

51 // return auth factory object

52 return authFactory;

53

54 })

55

56 // ===================================================

57 // factory for handling tokens

58 // inject $window to store token client-side

59 // ===================================================

60 .factory('AuthToken', function($window) {

61

62 var authTokenFactory = {};

63

64 // get the token out of local storage

65 authTokenFactory.getToken = function() {

66 return $window.localStorage.getItem('token');

67 };

68

69 // function to set token or clear token

70 // if a token is passed, set the token

71 // if there is no token, clear it from local storage

72 authTokenFactory.setToken = function(token) {

73 if (token)

74 $window.localStorage.setItem('token', token);

75 else

76 $window.localStorage.removeItem('token');

77 };

78

79 return authTokenFactory;

80

81 })

82

83 // ===================================================

84 // application configuration to integrate token into requests

85 // ===================================================

86 .factory('AuthInterceptor', function($q, $location, AuthToken) {

87

88 var interceptorFactory = {};

89

90 // this will happen on all HTTP requests

91 interceptorFactory.request = function(config) {

92

93 // grab the token

94 var token = AuthToken.getToken();

95

96 // if the token exists, add it to the header as x-access-token

97 if (token)

98 config.headers['x-access-token'] = token;

99

100 return config;

101 };

102

103 // happens on response errors

104 interceptorFactory.responseError = function(response) {

105

106 // if our server returns a 403 forbidden response

107 if (response.status == 403)

108 $location.path('/login');

109

110 // return the errors from the server as a promise

111 return $q.reject(response);

112 };

113

114 return interceptorFactory;

115

116 });

Conclusion

We now have our two great Angular services that we can use in our application. These two services will be the glue between the frontend Angular application and the backend Node application.

In addition to what we have just created from scratch, there are pre-built solutions on the market as well. A fully-featured Angular module would be the ng-token-auth module. It provides the same options as above but adds some neat features like auth events.

Let’s move forward and use these new files in our User CRM application. This is what we’ve been waiting for - A full MEAN stack application! All of our work in previous chapters has been building up to this point. Roll up your sleeves; we’ve got a lot of work to do in the next chapter.