I use my custom authenticator whch is the same as device authenticator almost.
When I refresh the page, local storage cleared and user logged out.
Ember version : 2.5.1
Ember Simple Auth version : 1.1.0
Why?
Thanks in advance
InternalSession.restore() should be called once in initializing. But I didn't see anything.
I created a instance-initializer and calling that restore function, then everything is ok.
Or in session.init it could be done
@jmimi: you should never call InternalSession.restore yourself - that's private API that might change any time.
@marcoow Thanks for response. I don't call InternalSession.restore() directly.
I have done this in my session service which inherited from ESA session service :
init(){
this._super(...arguments);
this.get('session').restore();
}
Yep, that's what you should not do ;)
@marcoow So what should I do to solve my problem?
Can you post the code of your custom authenticator?
It's so much similar to devise authenticator.
import Ember from 'ember';
import Base from 'ember-simple-auth/authenticators/base';
import config from 'my-app/config/environment';
const {RSVP: {Promise}, isEmpty, run, get, $} = Ember;
export default Base.extend({
serverTokenEndpoint: Ember.computed(function () {
return `${config.Backend.baseUrl}/${config.Backend.defaultNamespace}/accounts/login`;
}),
serverTokenEndpointLogout: Ember.computed(function () {
return `${config.Backend.baseUrl}/${config.Backend.defaultNamespace}/accounts/logout`;
}),
restore(data) {
const tokenAttribute = get(data, 'token');
const identificationAttribute = get(data, 'username');
if (!isEmpty(tokenAttribute) && !isEmpty(identificationAttribute)) {
return Promise.resolve(data);
} else {
return Promise.reject();
}
},
authenticate(identification, password) {
return new Promise((resolve, reject) => {
const data = {};
data['password'] = password;
data['username'] = identification;
return this.makeRequest(data).then(
(response) => run(null, resolve, response),
(xhr) => run(null, reject, xhr.responseJSON || xhr.responseText)
);
});
},
invalidate() {
return Promise.resolve();
},
makeRequest(data, options){
const serverTokenEndpoint = this.get('serverTokenEndpoint');
const requestOptions = $.extend({}, {
url: serverTokenEndpoint,
type: 'POST',
dataType: 'json',
data,
beforeSend(xhr, settings) {
xhr.setRequestHeader('Accept', settings.accepts.json);
xhr.setRequestHeader('src', 'web');
}
}, options || {});
return $.ajax(requestOptions);
}
});
Check that the restore method actually returns a resolving promise after reload - that's the reason for this kind of bug in 99% of the cases.
When I reload the page, it seems restore isn't called
Do you have an initializer or instance initializer in your app called ember-simple-auth.js? If so you need to remove that.
No i don't.
What's the content of the session store before the reload?
authenticated: {username: 'blah', token: 'blahblah', type: 'blah'},
isAuthenticated: true
after reload, autenticated key gone and isAuthentictaed comes false
Please copy/paste the actual store contents from localStorage or the cookie, depending on which one you are using.
From lcoalStorage :
ember_simple_auth:session: {"locale":"en","authenticated":{"authenticator":"authenticator:myapp","username":"[email protected]","token":"DrAdUHXSJRmqGxGmwtwr5BKDfalsrXA7fC1XJsD19EbsOFOs85NJrRAwOXYi9lJN","type":"password"}}
after reload :
ember_simple_auth:session: {"locale":"en","authenticated":{}}
Assuming you defined the above authenticator in app/authenticators/myapp.js everything looks good and the restore method should be called actually.
Would need access to the code to debug further. You can also place a breakpoint on this line and see why it never calls the authenticator's restore method.
Oh, I have an instance initializer to set locale
import config from 'myapp/config/environment';
export function initialize(appInstance) {
const i18n = appInstance.lookup('service:i18n');
const session = appInstance.lookup('service:session');
const locale = session.get('data.locale') || config.i18n.defaultLocale;
i18n.set('locale', locale);
if (!session.get('data.locale')) {
session.set('data.locale', locale);
}
}
export default {
name: 'i18n',
after: ['ember-simple-auth', 'ember-i18n'],
initialize
};
This causes setting a key-value in session store and run before setupSessionRestoration then becuase InternalSession isn't setup so authenticator in InternalSession is null and causes storage to be cleared.
Now what should I do?
I set my instance initializer to be run after ESA. Where is my mistake?!
Hm, this actually looks like a bug.
Aha so I do my first solution for this temporarily.
Thanks a lot @marcoow
No, you should never call restore manually.
Instead set the locale in the application route's beforeModel method. That only runs once the session has been restored successfully.
Aha yes. You're right.
I'll do this.
any news about this bug?
how we can fix it temporary?
@akhedrane: for now you cannot save anything in the session in an (instance) initializer. You can in the application route's beforeModel method though which should work for most of the use cases you'd want to use an initializer for (in many cases it's actually the better choice anyway).
i have initializer to inject i18n service only. no data saved in the session.
but the problem still exist.
Doesn't sound like you're experiencing this bug then. You'll need to provide more info, code etc. so that your problem can be reproduced.
I am having this issue as well. I have found a pattern that causes it. A logged out user makes an unauthorized call the to the API, then logs in. The next refresh will log you out. I can't see anything in my code, or anything that jumps out that would cause this. My local storage looks fine before and after logging in.
Before logging in:
{"authenticated":{}}
After logging in:
{"authenticated":{"authenticator":"authenticator:oauth2-gateway","access_token":"XXXXX","token_type":"bearer","expires_in":600,"refresh_token":"XXXXXX","created_at":1477929759,"expires_at":1477930359010}}
@anthonycollini: can you provide the source for your authenticator?
I have 2 authenticators:
app/authenticators/oauth2-password-grant.js
import config from '../config/environment';
import OAuth2PasswordGrant from 'ember-simple-auth/authenticators/oauth2-password-grant';
export default OAuth2PasswordGrant.extend({
clientId: config.oauth2ClientId,
serverTokenEndpoint: config.apiURL + '/oauth/token',
serverTokenRevocationEndpoint: config.apiURL + '/oauth/revoke',
});
app/authenticators/oauth2-gateway.js
import config from '../config/environment';
import Ember from 'ember';
import OAuth2PasswordGrant from 'ember-simple-auth/authenticators/oauth2-password-grant';
export default OAuth2PasswordGrant.extend({
torii: Ember.inject.service(),
clientId: config.oauth2ClientId,
serverTokenEndpoint: config.apiURL + '/oauth/token',
serverTokenRevocationEndpoint: config.apiURL + '/oauth/revoke',
authenticate(torii_provider) {
return new Ember.RSVP.Promise((resolve, reject) => {
this.get('torii').open(torii_provider).then(data => {
return this.makeRequest(config.apiURL + '/oauth/gateway_authorize', data);
}).then(response => {
// Copied from OAuth2PasswordGrant#authenticate
Ember.run(() => {
const expiresAt = this._absolutizeExpirationTime(response['expires_in']);
this._scheduleAccessTokenRefresh(response['expires_in'], expiresAt, response['refresh_token']);
if (!Ember.isEmpty(expiresAt)) {
response = Ember.assign(response, { 'expires_at': expiresAt });
}
resolve(response);
});
}).catch(error => {
reject(error);
});
});
}
});
@anthonycollini: do you have any custom initializers that access the session as described by @jmimi above? If not please create a new issue for your problem as it's most likely unrelated then.
@marcoow is this 'bug' only present when setting a value on the session or accessing it before the beforeModel hook has fired? I'm having the same problem, but for me it's hard te reproduce, seems like a timing issue (it was working before for a long time, same version 1.1.0).
@jcbvm: would be good if you could share some of the code so we can have a closer look at what might be happening.
@marcoow below some code, stripped some sensitive data
// custom authenticator 'main'
import Ember from 'ember';
import Base from 'ember-simple-auth/authenticators/base';
export default Base.extend({
store: Ember.inject.service(),
userService: Ember.inject.service(),
authenticate(credentials) {
return this.get('userService').authenticate(credentials.username, credentials.password).then(data => {
return this.didAuthenticate(data);
});
},
restore() {
return this.get('userService').getAuthentication().then(data => {
return this.didAuthenticate(data);
});
},
invalidate() {
return this.get('userService').clearAuthentication();
},
didAuthenticate(data) {
// stripped some code here, it basicly pushes some data in store and returns session data
return Ember.RSVP.resolve();
}
});
// login route
actions: {
authenticate() {
let credentials = this.controller.getProperties('username','password');
this.session.authenticate('authenticator:main', credentials);
}
}
md5-aea45e7f707db7f3ee09151a01dbcc8b
```javascript
// session-service initializer
export default {
initialize(application) {
application.inject('model', 'session', 'service:session');
application.inject('route', 'session', 'service:session');
application.inject('controller', 'session', 'service:session');
application.inject('component', 'session', 'service:session');
application.inject('template', 'session', 'service:session');
}
};
@marcoow Just a little note, I'm encountering this a lot of times with auto refresh when developing, same behaviour, restore gets never been called.
@jcbvm: what's the code of the userService? Which session store are you using?
@marcoow userService just makes an ajax call to my backend, nothing special there. I'm using the localStorage Session Store. Before I was using a custom session store (which did not save anything on the client). Didn't have the described behaviour with that store.
@jcbvm: I don't see anything in your code that's wrong. Would be helpful if you could setup a demo app that illustrates the problem you have in your app.
@marcoow I'm currently trying to narrow down the issue. It seems like I only have this issue when livereloading is on in development mode. Not been able to reproduce this by F5-ing via the browser.
Ok so we have just hit this issue and I have tracked down the problem, our code was actually throwing an Exception in our authenticator's restore method (details of which are not too important)
The problem is that the error is being swallowed and not ever output to the user. The problem is that if there is an error thrown at this point https://github.com/simplabs/ember-simple-auth/blob/master/addon/internal-session.js#L67 it will not be caught by this error handler: https://github.com/simplabs/ember-simple-auth/blob/master/addon/internal-session.js#L85 , instead that error handler will only catch issues with the this._callStoreAsync('restore') call on this line https://github.com/simplabs/ember-simple-auth/blob/master/addon/internal-session.js#L62
If the error handler is valid for cases of exceptions thrown in the user provided restore method then I would recommend changing the internal-session restore method to look more like this:
restore() {
this._busy = true;
const reject = () => RSVP.Promise.reject();
return this._callStoreAsync('restore').then((restoredContent) => {
let { authenticator: authenticatorFactory } = restoredContent.authenticated || {};
if (authenticatorFactory) {
delete restoredContent.authenticated.authenticator;
const authenticator = this._lookupAuthenticator(authenticatorFactory);
return authenticator.restore(restoredContent.authenticated).then((content) => {
this.set('content', restoredContent);
this._busy = false;
return this._setup(authenticatorFactory, content);
}, (err) => {
debug(`The authenticator "${authenticatorFactory}" rejected to restore the session - invalidating鈥);
if (err) {
debug(err);
}
this._busy = false;
return this._clearWithContent(restoredContent).then(reject, reject);
});
} else {
delete (restoredContent || {}).authenticated;
this._busy = false;
return this._clearWithContent(restoredContent).then(reject, reject);
}
}).then(null, () => { // <- this is the subtle difference
this._busy = false;
return this._clear().then(reject, reject);
});
},
The difference is that this will now catch all issues with user provided restore methods
@jcbvm related to my comment above, it could be that you are getting an error along the lines of Cannot call method 'authenticate' of undefined and the error is being swallowed.
From the code you provided I wonder have you got a service called 'user'? if so your inject might need to look more like this:
userService: Ember.inject.service('user'),
but that's just a stab in the dark 馃槀
I'm hitting a similar problem, albeit on the third attempt at reloading the app when authenticated:
Authenticator.restore() called, user is authenticatedAuthenticator.restore() called, user is authenticatedAuthenticator.restore() NOT called, user is kicked out of the app...I don't have any initialisers, but I do override the session service. In the override I do persist some session state in session.init() - not sure if this is similar to using an initialiser...
Also, it seems that if I move the code for setting the values in the store from the session.init() method to beforeModel() in the application route, the first refresh causes the session to invalidate - i.e it kicks the user out after 1 refresh instead of 3
Further update... after successfully logging in, if I temporarily disable the code in session.init() which writes to the store, then I don't trigger this bug...
Here's what my session.init() looks like:
export default SessionService.extend({
async init() {
this._super(...arguments)
// !! Comment out this line to not trigger refresh logout bug
await this._setDefaults()
},
/**
* Sets default `state` & `code_verifier`.
* Intended to be used on session service init so that all app components
* which generate authorize links use the same `code_verifier`
*/
async _setDefaults() {
// Ensure all invocations use the same state & code_verifier
let { code_verifier } = await this.session.store.restore()
const state = this._generateState()
if(!code_verifier) {
code_verifier = this._generateCodeVerifier()
}
await this.session.store.persist({ state, code_verifier })
},
@lougreenwood: you cannot write to the session store before the session has been fully created.
Sure, but shouldn't the session store have been created when the the session service is initialised - that appears to be a reasonable expectation?
In any case, I worked around this as much as I need to by using custom properties to hold default values. Seems this addresses my problem and maybe it'll help other people in future who come across a similar issue...
The session subscribes to update events on the session stores and will try to restore itself whenever the store changes. That will very likely lead to problem if that happens at the wrong time. In general, you should never access the store directly.
In general, you should never access the store directly.
Not sure I follow, do you mean that the following interaction with the store should be avoided?
let { code_verifier } = await this.session.store.restore()await this.session.store.persist({ state, code_verifier })yes, exactly
ok - I don't mean to be terse, but if it's not supposed to be used (or if the safe use cases for it are vague/minimal), why is it public?
Also, if you don't mind, could you describe how to safely store custom session state pls?
Seems that this long-living bug is likely a mis-understanding of how ESA should be used and the purpose of various public APIs?
You can store custom session data by setting it on the session:
this.session.set('data.authenticated.myData', 'maVal');
That is how it currently works but also the reason for the problems I mentioned above - basically all data that is written to the session is automatically persisted to the store which leads to a lot of hacky and complex code. This needs to be refactored into a cleaner solution but as that's a larger effort nobody has yet found the time to do it.
cleaning up old issues