Get Even More Visitors To Your Blog, Upgrade To A Business Listing >>

Authentication for Node.js App with Angular.js and iOS Clients

Authentication for Node.js App with Angular.js and iOS Clients

Problem

I've tried to read as many different answers and posts as possible, but I still can't quite settle on a solution that fits my needs. I'm trying to work out the best (most efficient, but mostly more secure) way to handle user Authentication, log in, etc.

I have a Node.js server, running on Express; I have an Angular.js web app; and I have an iOS app. I expose a RESTful API with Express/Node.js.

Cookies

The first things I read said to use cookies, and to store a session id/login Token on the server side (hashed) and on the client side (unhashed). The client would transfer this id with each request, the server would hash it, parse it and process the request accordingly. This does not feel RESTful (not a huge issue), but more importantly, would I have to duplicate my API: one for username/password authentication (e.g. done via curl) and one for cookie-based authentication (e.g. my web app)?

Another problem with this: what I would do if I had multiple connections from the one user, e.g. they're logged in in two browsers, an iPhone and an iPad. Would my storage of their session ids need to now be an array?

HTTP Basic Auth

The next idea was to use HTTP Basic Auth (with SSL), which seems easy enough, but is not recommended because you need to transfer a username and password with each request. If I were to do it with HTTP Basic Auth, would I then store the username and password in cookies (or HTML local storage) to allow for 'Remember Me' functionality? Or could I combine the two: use HTTP Basic Auth for the actual requests (post a new post, etc.) and just use a session id stored in a cookie for the initial log in sequence/remember me aspects?

Is transmitting a session id more secure than just transmitting the user's password? How? The session id is going to act ostensibly as a password, so to me transmitting it would have the same security issues as transmitting a password.

Basic Auth seems to be supported across all platforms, which is ideal. The main downside seems to be needing to transfer client authentication data with each request. Is there a way to mitigate this issue?

OAuth

OAuth seems like overkill for my needs. I think I would lose the ability to do curl commands to test my API. How is OAuth an improvement over the cookies method?

As you can probably tell, I'm a little confused by the diverse information available, so if you have a set of good links—applicable to this scenario—I would love to read them. I'm trying to find a solution that fits across all platforms, but is still as secure as possible. Also, if I have any of my terminology wrong, please correct me because it will make searching easier for me.

Thanks.

Update:

I've been thinking about this problem, and I've had an idea. Please tell me if this is dumb/insecure/any feedback, because I'm not sure if it's good.

When the user logs in, we generate a random session id (salted etc.). This optional session id is sent to the client, which the client can store (e.g. in cookies) if they choose; the session id is stored in the database.

This session id is then optionally sent with each request as either an HTTP Authentication header or query string, or the client can just send the username and password if they want (which gives us our regular REST API). At the server end, we check first for a session id parameter, if it's not present, we check for username/password. If neither are there—error.

On the server, we check that the session id is associated with the correct username. If it is, we complete the request.

Every time the user logs in, we create a new session id or delete the current one, and send this with the response to the log in request.

I think this lets me use the regular REST API, where appropriate, with Basic Auth, and maintain sessions/remember me functionality. It doesn't solve the multiple log ins issue, but otherwise I think this way should would. Please let me know.

Problem courtesy of: matthewpalmer

Solution

I would use a token based authentication where you can send a token (automatically) with each request. You'll have to log in once, the server will provide you with a token which you can then use to send with each request. This token will be added to the HTML header, so that you don't have to modify each request to the browser.

You can set certain calls in the API so that they always need a token, while others might not be token protected.

For Express, you can use express-jwt (https://www.npmjs.org/package/express-jwt)

var expressJwt = require('express-jwt');

// Protect the /api routes with JWT
app.use('/api', expressJwt({secret: secret}));

app.use(express.json());
app.use(express.urlencoded());

If you want to authenticate you can create this function in your express server:

app.post('/authenticate', function (req, res) {
  //if is invalid, return 401
  if (!(req.body.username === 'john.doe' && req.body.password === 'foobar')) {
    res.send(401, 'Wrong user or password');
    return;
  }

  var profile = {
    first_name: 'John',
    last_name: 'Doe',
    email: '[email protected]',
    id: 123
  };

  // We are sending the profile inside the token
  var token = jwt.sign(profile, secret, { expiresInMinutes: 60*5 });

  res.json({ token: token });
});

And for protected calls something that starts with /api:

app.get('/api/restricted', function (req, res) {
  console.log('user ' + req.user.email + ' is calling /api/restricted');
  res.json({
    name: 'foo'
  });
});

In your Angular application you can login with:

$http
      .post('/authenticate', $scope.user)
      .success(function (data, status, headers, config) {
        $window.sessionStorage.token = data.token;
        $scope.message = 'Welcome';
      })
      .error(function (data, status, headers, config) {
        // Erase the token if the user fails to log in
        delete $window.sessionStorage.token;

        // Handle login errors here
        $scope.message = 'Error: Invalid user or password';
      });

And by creating an authentication interceptor, it will automatically send the token with every request:

myApp.factory('authInterceptor', function ($rootScope, $q, $window) {
  return {
    request: function (config) {
      config.headers = config.headers || {};
      if ($window.sessionStorage.token) {
        config.headers.Authorization = 'Bearer ' + $window.sessionStorage.token;
      }
      return config;
    },
    response: function (response) {
      if (response.status === 401) {
        // handle the case where the user is not authenticated
      }
      return response || $q.when(response);
    }
  };
});

myApp.config(function ($httpProvider) {
  $httpProvider.interceptors.push('authInterceptor');
});

If you have to support old browsers which do not support local storage. You can swap the $window.sessionStorage with a library like AmplifyJS (http://amplifyjs.com/). Amplify for example uses whatever localstorage is available. This would translate in something like this:

    if (data.status === 'OK') {
      //Save the data using Amplify.js
      localStorage.save('sessionToken', data.token);
      //This doesn't work on the file protocol or on some older browsers
      //$window.sessionStorage.token = data.token;
      $location.path('/pep');
    }
  }).error(function (error) {
    // Erase the token if the user fails to log in
    localStorage.save('sessionToken', null);
    // Handle login errors here
    $scope.message = 'Error: Invalid user or password';
  });

And the authintercepter we swap for:

angular.module('myApp.authInterceptor', ['myApp.localStorage']).factory('authInterceptor', [
  '$rootScope',
  '$q',
  'localStorage',
  function ($rootScope, $q, localStorage) {
    return {
      request: function (config) {
        config.headers = config.headers || {};
        config.headers.Authorization = 'Bearer ' + localStorage.retrieve('sessionToken');
        return config;
      },
      response: function (response) {
        if (response.status === 401) {
        }
        return response || $q.when(response);
      }
    };
  }
]);

You can find everything except AmplifyJS in this article:

http://blog.auth0.com/2014/01/07/angularjs-authentication-with-cookies-vs-token/

Solution courtesy of: Daan van Hulst

Discussion

View additional discussion.



This post first appeared on Node.js Recipes, please read the originial post: here

Share the post

Authentication for Node.js App with Angular.js and iOS Clients

×

Subscribe to Node.js Recipes

Get updates delivered right to your inbox!

Thank you for your subscription

×