Keeping track of user sessions in Ember.js apps
Edit: This post describes what is probably a bad idea. If you're looking for a modern user session management pattern in Ember.js, look at the code of Discoure and this post about managing controller communication and dependencies in Ember.js
Warning: Unfinished design pattern ahead! Help polishing it is welcome.
If your app requires users to log in for certain views or actions, you need a good, universal (to the scope of your app) way of keeping track of user sessions. Polluting the global namespace by putting session data there is probably very doable, but ugly and a waste of the framework you've so lovingly built your app upon.
This is a job for Ember.StateManager! It provides the base for a finite–state machine, and that's really what we are doing here. This way we have a well–defined way of transitioning to and from a logged in and logged out state, as well as detecting the initial state so that user sessions are persisted between site visits.
In addition to the StateManager, we will have a Controller that contains the data about the logged-in user. In many cases you will want to just use an instance of your user model, if you have one.
The userSessionStateManager
First, we declare our StateManager, which we'll call userSessionStateManager. The name is long but the readability is good.
App.userSessionStateManager = Em.StateManager.createWithMixins({
Notice the use of createWithMixins() instead of just the usual create(). This is because Em.Object.create() no longer supports functions that call their parent functions. I get the feeling that the purpose of this change was to enforce good convention.
Next our initialization function. Here we delegate the session detection to the controller and set the initial state.
init: function() { this._super(); signedIn = App.userSessionController.isLoggedIn(); if (signedIn) { this.initialState = 'loggedin'; } else { this.initialState = 'loggedout'; } },
We define our loggedin state here, again using createWithMixins().
loggedin: Em.State.createWithMixins({ enter: function(sm) { this._super(sm); App.userSessionController.loggedInUser.set('logged_in', true); }, exit: function(sm) { this._super(sm); } }), }); // userSessionStateManager ends here
The loggedout state is pretty much the same.
loggedout: Em.State.createWithMixins({ enter: function(sm) { this._super(sm); App.userSessionController.loggedInUser.set('logged_in', false); }, exit: function(sm) { this._super(sm); } }),
These example functions are very bare, but provide a basic and standardized way of executing setup and teardown code on login and logout.
The userSessionController
The controller provides details of the actual log in and log out process– That is, the code that results in a cookie being stored in the user's browser.
App.userSessionController = Em.Controller.create({
This initialization does not use createWithMixins(), because we never call any parent functions. Semantics are very important in Ember!
loggedInUser: App.User.createRecord({ id: null, username: null, logged_in: false }),
loggedInUser is a reference to an empty App.User model. You do not need to fill out every property of your model; Ember-data is not bothered by incomplete records, you might say to a fault. In fact, we have added a property here that does not exist in our User model: logged_in. Again, our persistence layer does not find this a reason to worry.
isLoggedIn: function(callback) { // log-in state checking action here, that probably $.post()s to an API, // which returns the username and id of the logged-in user (if any) // pretend with me that you got the data into an object called user if (logged_in) { App.userSessionStateManager.goToState('loggedin'); this.loggedInUser.set('id', user.id); this.loggedInUser.set('username', user.username); return true; } App.userSessionStateManager.goToState('loggedout'); return false; },
The details of actually checking the logged in status here are omitted because that is completely up to the back-end that you're using. In my work at Renderfarm.fi developing the front-end web application, this involved pinging an 'loggedIn' view in the authentication API in our Django back-end.
logIn: function(username, password) { // post to your log-in API, which again returns the username and id of the user upon success // in this pseudocode the data is in 'user' if (login_success) { App.userSessionStateManager.goToState('loggedin'); this.loggedInUser.set('id', user.id); this.loggedInUser.set('username', user.username); } },
This function just pings whatever API you are using with username and password values. At Renderfarm.fi, we encrypt both values with the CSRF token that Django gives us before sending the data to the server, to prevent eavesdropping.
logOut: function() { // post to the log-out portion of your API App.userSessionStateManager.goToState('loggedout'); this.loggedInUser.set('id', ''); this.loggedInUser.set('username', ''); }, }); // userSessionController ends here
This is the simplest function of them all, which just posts to the API. I suspect with most frameworks, no logic is required here, just a post. Be sure to protect that part of your API from CSRF attacks.
The userSessionController should probably be put into an outlet or something, but I decided to publish this early so that I could show it to someone.