karlfreeman / angular-devise

An outdated example Rails app that uses a Angular.js UI for user signup, authentication & password retrieval
http://angular-devise.herokuapp.com
109 stars 19 forks source link

session doesn't get updated automatically #1

Closed giosakti closed 11 years ago

giosakti commented 11 years ago

Hi, I'm currently exploring the possibility of integrating devise as an authentication backend with angular as its frontend, and i found your code, which has put me into the right path :)

however I found some problems with your "Session" service here:

angular.module('angularDevise.services').service('Session', function($cookieStore, UserSession, UserRegistration) {

  this.currentUser = $cookieStore.get('_angular_devise_user');
  this.signedIn = !!$cookieStore.get('_angular_devise_user');
  this.signedOut = !this.signedIn;
  this.userSession = new UserSession( { email:"foo@bar.com", password:"example", remember_me:true } );
  this.userRegistration = new UserRegistration( { email:"foo-" + Math.floor((Math.random()*10000)+1) + "@bar.com", password:"example", password_confirmation:"example" } );

});

during my experiment, I don't think that those three variable (currentUser, signedIn and signedOut) will get automatically updated after this code at the session controller:

      .success(function(data, status, headers, config) {
        $cookieStore.put('_angular_devise_user', data);
      });

Therefore to make the login process seamless, I have to do some $broadcast and $on to catch the changes and change the variable manually so that the view will update accordingly.

Is this an expected behavior or am I missing something here ?

karlfreeman commented 11 years ago

@giosakti Your bang on. This repo is still a work in progress. The missing piece of the puzzle is the session integration. I'm still working on a solution but for the most part I'm a little unsure what the best route to go down is as there are quite a few ways to do it.

I'll keep you posted, you should if your looking in to Angular with Rails make sure to be aware of how it compresses ( I've updated the repo with the required changes )

giosakti commented 11 years ago

Ah okay then, I want to share some of my gists regarding authentication after taking your codes further:

First, now my session service looks like this:

https://gist.github.com/giosakti/5251023

Notice that I now use sessionStorage, not cookie. It's an interesting concept in HTML5 that you may want to check: http://www.w3schools.com/html/html5_webstorage.asp

However it only lives within a tab or window in a browser, so if you open a new tab the session data won't last, but it's perfect for my needs now.

I also create an interceptor for handling login and logout:

https://gist.github.com/giosakti/5251029

Now I can just do something like this in my session controller:

app.controller "SessionsController", ($scope, Session) ->
  $scope.session = Session.userSession

  $scope.create = ->
    if Session.signedOut
      $scope.session.$save().then ((response) ->
        $scope.$emit('event:loginConfirmed', response.data)
      ), (error) ->

  $scope.destroy = ->
    $scope.session.$destroy()
    $scope.$emit('event:logoutConfirmed')

I still found some glitches regarding this implementation, such as handling session timeout, etc. I want to try researching further on this matter.

karlfreeman commented 11 years ago

@giosakti That session service / interceptor of yours look really great! Initially I looked into sessionStorage but never as much as your gists. Whilst doing so I started to think that storing session information in the sessionStorage alone isn't quite right. Hear me out.

We ideally want two pieces of information available to Angular, the current_user and their session.

Storing both these sets of information in the sessionStorage means that ( the user is never 'remembered' correctly):

Storing both these sets of information in the localStorage ( the user is never 'forgotten' correctly ):

I think a combination of the two might be the best possible solution. We want the session to to be persistant across contexts / tabs and when each context / tab requests the current_user the validity of the session can be determined server side.

Storing in a mix of both sessionStorage and localStorage:

Thoughts?

karlfreeman commented 11 years ago

Also I'd preface this all with a I'm not sure there is a 'definitive' way of handling sessions in JS alone. Eg the 'session' in some instances is in fact an oauth2 bearer_token or a temporary api_key or [insert other way of individually identifying a user].

Would love to hear how others are attempting this, so will try and reach out.

giosakti commented 11 years ago

@karlfreeman that was some interesting illustrations you provide there.

It's true that I still haven't figured out the best way in handling session timeout. So regarding the usage of both session storage and local storage, I taken this from wiki:

"Session storage is per-page-per-window and is limited to the lifetime of the window. Session storage is intended to allow separate instances of the same web application to run in different windows without interfering with each other, a use case that's not well supported by cookies."

So by that definition sessionStorage has their own use-case, which is to provide multiple session within the same browser. However there is also other case, such as ours where we want the application to remember user even though they have closed the tab/window, thus I now believe that sessionStorage (maybe) doesn't have the capability to support this.

Now taking the above into account, I agree with you that maybe the combination of both is perhaps the right way to go. This combination can provide user with the ability to login with different cred simultaneously and the browser will still remember even after they have closed the tab/window. But we may need to make some adjustment on the server-side first before this can work out (for example the session expiring system, handling login with the same account simultaneously, etc)

(Unfortunately when I check google implementation of multiple login using firebug, the sessionStorage and localStorage was still empty, meaning that they still use cookies for this purpose. Perhaps because they have the obligation to maximize compability)

jesalg commented 11 years ago

What do you guys think of the approach taken in this repo: https://github.com/colindensem/demo-rails-angularjs - It's using token_auth wrapping. Do you see any downsides to it?

giosakti commented 11 years ago

Hi @jesalg, token_auth_wrapping is a sound concept, I know about it from the same reference that your demo link also refer to (http://nils-blum-oeste.net/).

Actually, I have used it on my application but haven't got the chance to write about it on my blog. These are my gists that relates to token_auth_wrapping:

the token wrapper: https://gist.github.com/giosakti/5537888

factory that uses the wrapper: https://gist.github.com/giosakti/5537889

jesalg commented 11 years ago

Thanks @giosakti I'll check those out!

karlfreeman commented 11 years ago

Still working on an internal implementation too add to this repo. Sorry for not being responsive :(

jesalg commented 11 years ago

@karlfreeman Great job so far, this demo has been very helpful.

I was thinking of a simpler strategy for my own use where I would just ping a server route such as /current_user which will either send me the logged-in user's info or 401. An interceptor in angular would handle that 401 by broadcasting an event and/or sending the user to to a login screen. In addition to that, I could also call getCurrentUser() every time the application starts to make sure I have the current state of the user. That would solve most of the issues discussed above such as current_user expiring or being stale when stored in localStorage or sessionStorage

That would also greatly simplify things, only downside being that there would be a call to the back-end on every route change. Although if most of your angular controllers are hitting the server for data this would be enforced anyway with before_filter :authenticate_user!

Now the only other thing I need to figure out is how to take them back to the angular controller they came from. There might be a way to store that in the localStorage somehow via the interceptor right before they are redirected to the login controller.

karlfreeman commented 11 years ago

@jesalg Your bang on the same trail I've headed down.

Personally I wouldn't use an interceptor for all 401's as against the api I'm building sometimes 401 is due to lack of permission, not authorization.

How I've handled the route change checking /current_user is to wrap them in a resolve which checks against the state of the current_user. The redirect back path is set within the resolve. When I'm abit more free I'll be updating this with the missing peice of the puzzle. Thanks :)

jesalg commented 11 years ago

@karlfreeman That's interesting. I'll explore that option. Thanks!

giosakti commented 11 years ago

The alternative system looks interesting guys!

However design-practice-wise, is it a good practice to make additional request for authentication every time the route changes? So basically, when I click on my app that points to another location, it will (minimum) make two request; one for the resource and the other one for the authentication information. And technically speaking, you also have to send the resource that user wants to access to the '/current_user' right?

Pardon me if my understanding is wrong, I do think that it will simplify things but I'm seeing potential security problem there. Because i believe that authentication, authorization and accessing resource should be made in one request.

jesalg commented 11 years ago

@giosakti It won't necessarily be making two requests every time. Assuming we are still taking about using this with Rails and devise, the resource in question will most likely be protected with before_filter :authenticate_user! in the Rails controller. So your interceptor should catch that in case of an unauthenticated user, have the user authenticate (possibly storing the auth response in global scope for UI purposes), and redirect them back to the controller they came from.

Only scenario I see where you'd have to make an explicit request to /current_user is when you have a route with static content in angular which you want to protect (but that is very unlikely) and of course at the beginning of the application if needed.