Is there any way to redirect the user to the previour URL after successful login? Right now it automatically redirects to the root URL. I have seen a few other issues like this and tried them all but nothing seems to work.
I am using the ApplicationRouteMixin. I am saving the previous route in the app router like this:
willTransition() {
this._super(...arguments);
this.set('session.previousRouteName', this.currentRouteName);
}
I am using the sessionAuthenticated hook in the application.js file.
sessionAuthenticated() {
this._super(...arguments);
let previousRoute = this.get('session.previousRouteName');
if (previousRoute) {
this.transitionTo(previousRoute);
}
}
Here is the form component that handles the authentication:
let credentials = this.getProperties('identification', 'password'),
authenticator = 'authenticator:jwt';
this.set('errorMessage', null);
this.set('isLoading', true);
this.get('session')
.authenticate(authenticator, credentials)
.then(async() => {
const tokenPayload = this.get('authManager').getTokenPayload();
if (tokenPayload) {
this.get('authManager').persistCurrentUser(
await this.get('store').findRecord('user', tokenPayload.identity)
);
}
})
.catch(reason => {
if (!(this.get('isDestroyed') || this.get('isDestroying'))) {
if (reason && reason.hasOwnProperty('status_code') && reason.status_code === 401) {
this.set('errorMessage', this.get('l10n').t('Your credentials were incorrect.'));
} else {
this.set('errorMessage', this.get('l10n').t('An unexpected error occurred.'));
}
this.set('isLoading', false);
} else {
console.warn(reason);
}
})
.finally(() => {
if (!(this.get('isDestroyed') || this.get('isDestroying'))) {
this.set('password', '');
}
});
Lastly here is the configuration:
ENV['ember-simple-auth-token'] = {
refreshAccessTokens : false,
serverTokenEndpoint : `${ENV.APP.apiHost}/auth/session`,
identificationField : 'email',
passwordField : 'password',
tokenPropertyName : 'access_token',
refreshTokenPropertyName : 'refresh_token',
authorizationPrefix : 'JWT ',
authorizationHeaderName : 'Authorization',
headers : {}
};
The issue is that the sessionAuthenticated hook runs but it runs after the user is redirected to the root url. Can someone point me to the issue ? Thanks.
If you're making use of the ApplicationRouteMixin, the transition to the previously visited route after a successful login will be handled for you automatically, without you having to do any manual setting of the currently visited route. Feel free to check out the API documentation of the ApplicationRouteMixin's sessionAuthenticated method:
[...] If there is a transition that was previously intercepted by the AuthenticatedRouteMixin's beforeModel method it will retry it [...].
You'd have to ensure that all of the routes that should only be accessed by a correctly logged-in user and which could be transitioned back to after a successful login also carry the AuthenticatedRouteMixin. A failed transition to any of these routes will then store the attempted transition onto the session service and this transition will be retrieved and re-attempted automatically once a subsequent login has been successful.
Does this already help? Feel free to point out any other open questions
Hi @jessica-jordan , thanks for the response but I want to redirect them to the last visited route regardless of whether that route requires the user to login or not.
For e.g. in my app I have a public page which anyone can see regardless of whether he/she is logged in or not. Now on the nav bar, I have a login button. The user clicks on the button and then logs into his/her profile. After successful login, the user should be redirected back to that public page.
I am just a beginner in Ember JS hence I may be wrong, but if I apply the AuthenticatedRouteMixin to that public page, that would mean that it would require the user to login in order to access that page which I don't want. Is my above statement correct ? Please correct me if I am wrong.
Ah I see and yes you're right - in fact already on the right track: I think it's correct that you're enhancing the sessionAuthenticated method of the ApplicationRouteMixin with your own custom transitioning behaviour. I think what you might want to do is to overwrite the hook instead of inheriting the already present functionality via this._super(...arguments);. E.g. you could set up your Application route as follows to keep track of the route you visited recently via the willTransition action:
// app/routes/application.js
import Route from '@ember/routing/route';
import ApplicationRouteMixin from 'ember-simple-auth/mixins/application-route-mixin';
export default Route.extend(ApplicationRouteMixin, {
actions: {
willTransition(transition) {
// transition.targetName returns the public / private route that was aimed to be visited last
this.set('session.previousRouteName', transition.targetName);
}
}
});
And then overwrite the default behaviour of the sessionAuthenticated hook as well:
// app/routes/application.js
import Route from '@ember/routing/route';
import ApplicationRouteMixin from 'ember-simple-auth/mixins/application-route-mixin';
export default Route.extend(ApplicationRouteMixin, {
sessionAuthenticated() {
this.transitionTo(this.get('session.previousRouteName'));
},
actions: {
willTransition(transition) {
this.set('session.previousRouteName', transition.targetName);
}
}
});
You can also take a look at the default implementation of the method to see what might be useful for your own custom transitioning flow.
Feel free to point out if there are any other open questions!
Sure, I was trying something similar and got partial success.
I added this in the main router.js file
willTransition() {
this.set('session.previousRouteName', this.get('currentRouteName'));
this._super(...arguments);
}
and added this in the application.js file
sessionAuthenticated() {
let previousRoute = this.get('session.previousRouteName');
if (previousRoute) {
console.log('transitioning to ' + previousRoute);
this.transitionTo(previousRoute);
}
}
i.e. I removed the call to the super class.
I got success in redirecting back to the unauthorized pages but wasn't able to redirect the user back to the authenticated pages.
I think it is down to the fact that I am using this.get('currentRouteName') instead of transition.targetName. I'll try your suggestions and get back to you. Thank you so much for the help so far. :smile:
I have to use the window object but I don't have any other options. I have tried everything that I could think of.
Tried using the transition.targetName but it doesn't work for routes which take params. For e.g. I can't redirect the user back to http://localhost:4200/e/fac99f4d/.
I tried using the transition.intent.url it works fine for most cases but for transition from parent to child routes, it is undefined hence can't use it in order to redirect to url such as http://localhost:4200/e/fac99f4d/coc although it works great for url such as http://localhost:4200/e/fac99f4d/.
if (transition.targetName !== 'login' && transition.intent.url) {
this.set('session.previousRouteName', transition.intent.url);
}
app/routes/application.js sessionAuthenticated() {
if (this.get('session.previousRouteName')) {
this.transitionTo(this.get('session.previousRouteName'));
} else {
this._super(...arguments);
}
},
actions: {
willTransition(transition) {
transition.then(() => {
if (window.location.pathname !== '/login') {
this.set('session.previousRouteName', window.location.pathname);
}
});
}
}
Not sure whether it is recommended or not but this is the only one that gets the job done.
Closing this one. Thanks a lot. @jessica-jordan
Shouldn't something like this be built into ember-simple-auth?
@srv-twry your solution works except for the case where landing directly on the route that you want to redirect back to after login. The other issue with it was it was breaking signup for me. I had to add /signup to the excluded paths. Another issue is it doesn't take into account query params.
afterModel() {
...
this.cacheCurrentPathName();
},
sessionAuthenticated() {
// Return to the previous route after login.
// See https://github.com/simplabs/ember-simple-auth/issues/1618
if (this.get('session.currentPathName')) {
this.transitionTo(this.get('session.currentPathName'));
} else {
this._super(...arguments);
}
},
cacheCurrentPathName(transition = Promise.resolve()) {
if (this.get('fastboot.isFastBoot')) return;
transition.then(() => {
const currentPathName = window.location.pathname;
if (currentPathName === '/login' || currentPathName === '/signup') return;
this.get('session').currentPathName = currentPathName;
});
},
actions: {
willTransition(transition) {
this.cacheCurrentPathName(transition);
},
},
@mhluska Hi, yeah I myself faced a lot of problems and in fact wasn't able to exactly get the solution which I wanted. This is the final implementation that works for most cases. It doesn't touch the window object. Still isn't 100% working but kind of okay.
import { inject as service } from '@ember/service';
import { merge, values, isEmpty } from 'lodash';
export default Route.extend(ApplicationRouteMixin, {
session: service(),
sessionAuthenticated() {
if (this.get('session.previousRouteName')) {
this.transitionTo(this.get('session.previousRouteName'));
} else {
this._super(...arguments);
}
},
/**
* Merge all params into one param.
*
* @param params
* @return {*}
* @private
*/
_mergeParams(params) {
return merge({}, ...values(params));
},
actions: {
willTransition(transition) {
transition.then(() => {
let params = this._mergeParams(transition.params);
let url;
// generate doesn't like empty params.
if (isEmpty(params)) {
url = transition.router.generate(transition.targetName);
} else {
url = transition.router.generate(transition.targetName, params);
}
// Do not save the url of the transition to login route.
if (!url.includes('login')) {
this.set('session.previousRouteName', url);
}
});
}
}
}
Hope it helps.
Thanks @srv-twry . Have you considered trying to use an observer on router.currentURL using the router service instead? I'm noticing that willTransition doesn't fire in all route change cases such as when I call this.transitionToRoute from somewhere. Also router.currentURL contains query params.
Here's an updated solution that seems to be working for me in all cases:
In routes/application.js:
import { on } from '@ember/object/evented';
import { observer } from '@ember/object';
import { inject as service } from '@ember/service';
...
router: service(),
cacheCurrentURL() {
const currentURL = this.get('router.currentURL');
const currentRouteName = this.get('router.currentRouteName');
if (currentRouteName === 'login' || currentRouteName === 'signup') return;
this.currentURL = currentURL;
},
currentURLChanged: on('init', observer('router.currentURL', function() {
this.cacheCurrentURL();
})),
sessionAuthenticated() {
// Return to the previous route after login.
// See https://github.com/simplabs/ember-simple-auth/issues/1618
if (this.currentURL) {
this.transitionTo(this.currentURL);
} else {
this._super(...arguments);
}
},
...
Most helpful comment
Hi @jessica-jordan , thanks for the response but I want to redirect them to the last visited route regardless of whether that route requires the user to login or not.
For e.g. in my app I have a public page which anyone can see regardless of whether he/she is logged in or not. Now on the nav bar, I have a login button. The user clicks on the button and then logs into his/her profile. After successful login, the user should be redirected back to that public page.
I am just a beginner in Ember JS hence I may be wrong, but if I apply the
AuthenticatedRouteMixinto that public page, that would mean that it would require the user to login in order to access that page which I don't want. Is my above statement correct ? Please correct me if I am wrong.