A little while back, I share a post on my approach to setting a current user property on an Ember app using the Ember Simple Auth library.
That post received several helpful comments, including one from Marco Otte-Witte, the creator of the Ember Simple Auth library.
So, I thought I'd share his approach here, with a few tweaks of my own.
To get this working, we'll be creating a custom service that requests the current user from our API and sets it equal to an account
property. Then, we'll inject this service into our Application route.
Building The Custom Service
Create a file, services/session-account.js
.
import Ember from 'ember';
const { inject: { service }, RSVP } = Ember;
export default Ember.Service.extend({
session: service('session'),
store: service(),
loadCurrentUser() {
return new RSVP.Promise((resolve, reject) => {
const token =
this.get('session.data.authenticated.token');
if (!Ember.isEmpty(token)) {
return this.get('store').findRecord('user',
'me').then((user) => {
this.set('account', user);
resolve();
}, reject);
} else {
resolve();
}
});
}
});
Here, we are injecting the Session and Store services. Then, we define our loadCurrentUser
function to introspect on the Session service and extract the current user's authentication token from where it is stored in session.data.authenticated.token
.
If a token is present, we know that a current user exists and it interacting with out app. If that is the case, we want to send a request to our API for our current user.
We do so by sending a request to a special current-user-retrieval end-point in our API: http://localhost:3000/users/me
. This request is triggered by:
this.get('store').findRecord('user', 'me')
Let's take a look at that end-point in our API:
module Api
module V1
class UsersController < ApplicationController
def me
render json: current_user
end
end
end
end
And the current_user
method in our Application Controller:
class ApplicationController < ActionController::API
include ActionController::Serialization
include ActionController::HttpAuthentication::Token::ControllerMethods
private
def authenticate!
authenticate_token || render_unauthorized
end
def authenticate_token
authenticate_with_http_token do |token, options|
User.find_by(authentication_token: token)
end
end
def render_unauthorized
render json: {
errors: ['Bad credentials']
}, status: 401
end
def current_user
authenticate_token
end
end
Once the current_user
payload is sent back to Ember, we use a promise to set a property, account
, equal to that user.
Injecting the Service
Now, we need to inject the service into our Application Route.
import Ember from 'ember';
import ApplicationRouteMixin from 'ember-simple-auth/mixins/application-route-mixin';
const { service } = Ember.inject;
export default Ember.Route.extend(ApplicationRouteMixin, {
sessionAccount: service('session-account'),
beforeModel() {
return this._loadCurrentUser();
},
sessionAuthenticated() {
this._loadCurrentUser().then(()=>{
this.transitionTo('/');
}).catch(() => this.get('session').invalidate());
},
_loadCurrentUser() {
return this.get('sessionAccount').loadCurrentUser();
}
});
Let's break this down.
- We've defined a private function,
_loadCurrentUser
, that introspects on thesessionAcount
service we injected, and calls theloadCurrentUser
function we defined in that service (see above). - We use the
sessionAuthenticated
hook, which is invoked once the session is successfully authenticated (i.e. the authenticator sent a request to the API and received a payload with the newly signed-in user's email and token). This hook will call our_loadCurrentUser
private function and then transition to the root path. - We are also using the
beforeModel
hook (which runs before themodel
function of any of our routes is invoked) to load the current user when necessary.
Just one more thing!
Injecting session-account
into Application Controller
In order to use the sessionAccount
property in our application template, we need to inject our session-account
service into the Application Controller.
import Ember from 'ember';
export default Ember.Controller.extend({
sessionAccount: Ember.inject.service('session-account')
});
Using our Current User Property
Now, we can access the current user in our application template like this:
// templates/application.hbs
Hi, {{sessionAccount.account.name}}