This can be done, but will require extending ember-simple-auth
and ember-simple-auth-token
. It's not exactly recommended to use your approach, the access token plus refresh token is definitely the preferred method. It also scales better since your resource servers don't need the ability to authenticate, just to authorize and validate tokens. It also adds a bit of overhead to generate a new token for every single request. While I don't recommend this course, here's how it can be done:
As you've discovered, you cannot update the session directly, but if you dive into the source code you will discover that ember-simple-auth
uses an event to update it. However, this event will only work if you override/customize an authenticator
. Likely ember-simple-auth-token
's authenticators/jwt.js
. If you look at their authenticator, you can see an example of how to update the session.
So, I would recommend creating your own custom JWT token authenticator that extends ember-simple-auth-token
's. It will likely add a method to update the session
or perhaps better to handle the raw request headers that contain a new access token.
After that's complete, you have a choice. You can either override your own adapter
and call that function yourself from there, or perhaps the better choice would be to override ember-simple-auth
's data-adapter-mixin
mixin.
If you override ember-simple-auth
data-adapter-mixin
, this seems to be the best starting point: handleResponse
. If you override that function, you should have access to your raw API response from which you can call your update session function.
As you can see though, this is not a trivial change. It's definitely going against the grain of what these libraries were originally designed to do, but if you put in a good bit of work, it should be possible.
Updates for OP's comment
I actually implemented both of those requirements before: "1) to have session timeout after a pre configured idle time. 2) allow certain set of users to proxy in and switch the context of login user on the fly". It turns out that it's actually pretty easy to accomplish with a refresh token model.
To support #1, the refresh token expiration should be set to the length of the idle time before logout. e.g. if the refresh token is set to expire in 30 minutes and the access token expires in 5 minutes. The client will automatically fetch new access tokens (and optionally a refresh token) every ~5 minutes, based on leeway. However, if the user leaves the page for over 30 minutes, the refresh token will be expired and they will need to re-authenticate.
For #2, unfortunately, it does take a bit of overriding of ember-simple-auth-token
to work, but it's relatively easy and something I have already implemented. Basically you create create an custom authenticator that has an addition function to exchange and access token for a new one with updated context/state. See my implementation below:
ember-simple-auth-token
overridden authenticator:
/**
Provides a public method to call to exchange an access token for an
updated access token with updated data encoded in it. This method
essentially asks the backend to update an access token with different
values. This is, at the moment, is only used to switch the company root
attribute in the token (AKA switch companies for admins).
@param {string} token - The valid access token
@param {object} [headers] - An optional object which can add additional
headers to the request
@param {object} additionalData - An object which contains the data which should be updated on the token. It should look something like this:
```
{
company_root_id: '<UUID of a valid company root>'
}
```
@return {Promise} A promise which is the request. It will either resolve
with the updated session data or reject with the error.
*/
exchangeAccessToken(token, headers, additionalData) {
const data = this.makeRefreshData(token);
Ember.merge(data, additionalData);
return new Ember.RSVP.Promise((resolve, reject) => {
this.makeRequest(this.serverTokenRefreshEndpoint, data, headers).then(response => {
Ember.run(() => {
try {
const sessionData = this.handleAuthResponse(response);
this.trigger('sessionDataUpdated', sessionData);
resolve(sessionData);
} catch(error) {
reject(error);
}
});
}, (xhr, status, error) => {
Ember.Logger.warn(`Access token could not be refreshed - server responded with ${error}.`);
reject();
});
});
}
Action to trigger the token exchange:
switchCompany(companyId) {
let session = this.get('_session.session');
if(!this.get('isAdministrator')) {
throw new Error('Logged in user is not an administrator, they cannot switch companies.');
}
let token = session.get('authenticated.access_token');
let appInstance = Ember.getOwner(this);
let authorizerName = session.get('authenticator');
let authorizer = appInstance.lookup(authorizerName);
if(!authorizer) {
throw new Error(`Authorizer (${authorizerName}) does not exist`);
}
if(typeof authorizer.exchangeAccessToken !== 'function') {
throw new Error(`Authorizer (${authorizerName}) does not have an \`exchangeAccessToken\` method.`);
}
return authorizer.exchangeAccessToken(token, null, {
company_root_id: companyId
});
}
The backend would obviously need to be extended to accept additional parameters on the refresh token endpoint which allows the user to switch roles, if authorized.