Sunday, September 16, 2012

An Authentication Service for AngularJS

I am so tired to form up explanatory, coherent sentences, but at the same time I really like to share a user authentication service that I wrote for AngularJS. Hence, pardon my brevity.

In this example I use RailwayJS with CoffeeScript both at the client- and server-side. (See my previous post on auto-compiling assets with connect-assets.) Here is the scenario: You have assignments.html such that only authenticated users are allowed.

First things first, here is our /config/routes.coffee:

Then we implement our controller /app/controllers/home_controller.coffee as follows.

Note that the authenticate used in login action handler is meant to be provided by you.

Later, we write /app/views/home/index.ejs to fire up AngularJS:

We first start by implementing app.js of AngularJS in /assets/js/app.coffee:

The extra bit for listening on $rootScope for $routeChangeStart is to check access to authentication required pages.

After app.js, we implement /assets/js/controllers.coffee:

For each controller, we implement a view, that is, /public/partials/login.html and /public/partials/assignments.html:

And here goes the magic, /assets/js/services.coffee:

Hope it works for you as well.

18 comments:

  1. Thank You for sharing this!

    Just started my voyage with this angular spaceship, and was looking for the very same thing...
    Still figuring out how to implement this in JS, but it is quite self explanatory!

    How about a part two - server-side logic?

    cheers, and happy coding!

    ReplyDelete
    Replies
    1. I am glad you find it useful. OTOH, I couldn't get exactly what you mean by server-side logic. (In case you might have missed it, home_controller.coffee provides the server-side authentication for the client-side AngularJS User service.)

      Delete
    2. Yes, You are right - i missed it completely.
      I'm not familiar with RailwayJS, nor CoffeeScript...

      Delete
  2. I find it very useful and thank you for sharing this.

    By the way, as you store "isAuthenticated" in User service, I wonder if user can change it on client to by-pass your validation in "$routeChangeStart". Not sure if they can open console and change the angular services as they can do with angular scope.

    ReplyDelete
    Replies
    1. I am glad it found use for you. Coming back to your question, since it is stored at the client side, there is no bullet-proof protection to prevent the user from modifying User.isAuthenticated flag. That being said, when a user by-passes the client-side check and browses to partials/assignments.html, trying to load information from the server will result with a access denied message. Hence, no crucial information is leaked, except the display widgets in partials/assignments.html, which is already accessible by anyone in anyway.

      Delete
    2. Thank you for your reply.
      From your reply, I think you meant we double-check on server everytime a user requests data, right?

      Because I couldn't see your full project so I couldn't see clearly where you check for the session on server. Or maybe RailswayJS is doing it for you (I have no experience with RailswayJS, I use ExpressJS).

      In my case, what I did is that on $routeChangeStart, instead of checking for isAuthenticated of client side User module, I make $http.post to server to check for the session.

      This means that instead of 1 request per user's action, my app makes 2 requests (1 to check authentication and 1 to get data).
      Not sure if my solution is a good way to implement authentication with AngularJS...

      Delete
    3. But you are still making the check at the client side. Users can still by-pass your check and access the data. What you need to do is, while processing an incoming request from the server-side for an access to a restricted area, validate the session of the user just before doing rest of the work. If it doesn't have a valid session, return an access denied error message. (Pay attention that, all I am talking is about server-side validation, no Javascript or other client-side work is involved.)

      Delete
  3. What do you have in place on the server side that prevents the client side from spoofing the fact that they're logged in?

    ReplyDelete
    Replies
    1. You can make check against request.session contents. (E.g., isAuthenticated = request.session.user?.name?)

      Delete
  4. Hi,
    i implemented my authentication in JS the same way, i have only one problem:
    - if user is not authenticated, the redirect happens BUT the controller body is executing(and template loadaded).

    Try to log some info at the controller (at AssignmentListCtrl, like console.log('Assigment'), at LoginController console.log('Login'));

    Test case:
    click TWICE on a link to AssigmentList

    Without logged in, expected:
    -> console output: 'Login'
    ACTUAL:
    FIRST CLICK:
    -> get request to the partial (partials/assignments.html)
    -> console output: 'Login'
    SECOND CLICK:
    -> console output: 'Assigment'
    -> console output: 'Login'
    THIRD CLICK:
    -> console output: 'Assigment'
    -> console output: 'Login'
    ...

    Any idea for a workaround?

    ReplyDelete
    Replies
    1. You might try debugging what's going wrong in $routeChangeStart hook.

      Delete
  5. Could you try the same thing in your implementation?
    Because this block is the same at me:

    .run ($rootScope, $location, User) ->
    $rootScope.$on '$routeChangeStart', (event, next, current) ->
    if not User.isAuthenticated() and \
    next.templateUrl isnt '/partials/login.html'
    $location.path("/login")

    If you put a "console.log('routeChangeStart');" before authentication check, "console.log('AssignmentListCtrl');" as the first line off AssignmentListCtrl, "console.log('LoginCtrl');" as the first line off LoginCtrl, you will see the following:
    - click on a link, that links to Assigment page(auth needed):
    Console output:
    >routeChangeStart <- routchangeStart event, auth needed
    >get("/partials/assignments.html"); <- loads the template
    >LoginCtrl <- redirects because authentication need

    - click on a link second time, that links to Assigment page(auth needed):
    Console output:
    routeChangeStart <- routchangeStart event, auth needed
    AssignmentListCtrl <- runs the Assigment controller body
    (HERE IS THE PROBLEM, the JS code check that route is behind authentication and redirects login (but authenticated ctrl loads before this)
    routeChangeStart <- auth needed redirected to Login
    AssignmentListCtrl <- runs the Login controller body

    I think, that the hook on routchange event doesn't stop the pageload process (getting template, runs controller body), by calling location.path.

    Could you check this flow (clicking twice on assigment link, and log to console the same like i did)?

    ReplyDelete
    Replies
    1. Krisztián, I don't have a working NodeJS+AngularJS setup right now. But I get your point and it appears to be right. Unfortunately, I don't know any possible solutions. Until I find time to fix it, if you figure out any turn arounds, please inform us as well.

      Delete
  6. Hi Volkan,

    Many thanks for sharing this article. It was really useful for me.

    I found few issues with syncing user session in client side. Because if long waited server session timeouts and in client side the user is still logged in. And the other issue is if I refresh the page all authorization will vanish. However I used cookies to resolve that. I'd like to know your opinion on this as I pretty new to angular JS.

    Thanks Again.
    Cheers!

    ReplyDelete
    Replies
    1. AFAIC, utilizing cookies for that purpose is a good approach. Further, let's assume that the cookies look ok, client thinks that the session is still valid, while due to timeout it was expired on the server side. In this case, since the very first request to the server will fail due to session timeout, client will get acknowledged of the session expire and consequently invalidate the client session as well, that is, remove the session cookies. I am not a security expert in the field, but IMHO, this all looks good.

      Delete
  7. Hi,
    It is very informative and very helpful on my research regarding seo techniques. Thanks for sharing this post.


    Apostille Authentication

    ReplyDelete
  8. Hi,
    Great post! Thanks you so much for the share. It is indeed a helpful one. I am looking forward of reading more article with the similar topic as this one. Good luck and More Power.
    Authentication Services

    ReplyDelete
  9. Hi!
    Great post like this must be highly recommended. It is so nice to read such wonderful blog. Thanks
    Authentication Certificate

    ReplyDelete