UI-Router does not register history before $onInit - angularjs

So I have a login scheme that only needs user login behind specific pages. Currently, I have the page checking if the user exists via the $onInit function, then firing the login (which redirects to a login page via Code Flow in OAuth 2). Then when I've logged in, the page redirects back to the previous page, instead of the page I fired the login even from.
I think this is due to ui-router not registering history with the browser before it redirects, thus the $state object in the OAuth 2 parameters, when decoded shows me the path: "redirectUrl":"http://localhost:3000/reservations" when it should be "redirectUrl":"http://localhost:3000/reservations/purchase".
So my question is two fold.
Is $onInit an appropriate place to be doing this, or should I have some global routechange listener or use resolve? If I use either of those two methods, I think I will still have the route problem.
If I keep using $onInit, is there some way to make sure the url is registered correctly so that it will redirect to the correct location post-login?
$onInit function:
function init() {
var token = Meteor.call('getAccessToken');
if (!token) {
Meteor.loginWithOidc();
}
}

Related

Angular js routing and event flow best practices

I am trying to determine what is the best approach to adopt when a user is already logged in and type /login in the browser URL.
Should I prevent the event to be fired by using $routeChangeStart or $locationChangeStart (which one to chose)
Should I instead redirect to /home ?
Where should I implement the event capture ? In the controller or the app.js
I think you have somewhere a service that tells you if the user is logged in or not. My approach is to use a "resolve" clause/guard in the route/state you want to protect. In this case (login page) you could say something like: are you already logged in? Redirect to /home. Are you not logged in? I leave you load the page and log in. The choice to redirect to /home or elsewhere it depends on your application. You could even redirect to the /my-profile page if you have it.

backbone router how to hide parameters from url

I am using backbone router to redirect to my pages, I have for example this url
http://localhost:56508/#/RegistryMain/44/1234
The code
RegistryMain: function (id, cnt) {
var self = this;
this.load();
require([
'js/views/Questionaire/RegistryMain'
], function (RegistryMainView) {
self.shell();
if (typeof app.RegistryMainView !== 'undefined') {
app.RegistryMainView.destroy();
}
app.RegistryMainView = new RegistryMainView({ Id: id, cnt: cnt });
$("#main-nav li.active").removeClass("active");
$("#admin").addClass("active");
});
},
For security reasons, I need to hide /44/1234 from the url so the user can't reuse them or change them to /45/1234 for example and access forbidden pages.
How can i do this in backbone?
And if i should use the POST approach, how to use it in backbone?
mu is too short is right in saying that you're trying to handle security in the wrong place. You can't trust client-side code, at all.
You should check the rights of the user from the server each time the frontend app calls an API endpoint to avoid your users navigating to pages they don't have the rights to see, or to avoid them messing with the data by posting invalid stuff to an endpoint.
Your API should return a 401 unauthorized HTTP response code to notify your frontend app of the situation.
And for smooth page transition, redirect the user to an error page if an API call fails.
That being said
To avoid showing a url, you can directly call loadUrl:
// current url: http://localhost:56508/#/RegistryMain
Backbone.history.loadUrl("RegistryMain/44/1234");
// current url: http://localhost:56508/#/RegistryMain
This will trigger the route callback without changing the url, so the user never sees the params.
Now that I use .loadUrl in my project, all pages have the same URL, but when I refresh a page, it redirects to the login page (the first
page in my project). How to make it refresh the current page?
That's a drawback of not using the url to trigger routes, you now can't know the state of the app as a single page app lives in the current page only and refreshing starts a new instance completely.
You should really rethink if not using the url is the right choice. If it still is, you could save the current url in the localStorage and use it on the app startup to redirect to the right page.

In AngularJS, what if you are routing to the same page (same URL and hash)?

In an AngularJS Single Page Application, if you are in the main page and now you route to the second page, and use the "service" (as a global), and use myService.cityName to pull data using AJAX (by using $resource), that's fine.
But what if on your page header, you also have an input text box, and you can type in a city name, and when the user press Enter, you are routing to the same page (the second page). So I think browser didn't do anything because it is the exact same URL. How would you solve this issue or what is the AngularJS's way of handling this?
(Update: it is not to refresh or reload a page... it is about, say, you get weather data for a city, and you are on the forecast subpage, with a path of #/forecast. Now at the top search box on the page, you enter a different city and click "Submit" to go to the same path #/forecast then nothing will happen)
Update2: essentially, in my controller code:
$scope.submit = function() {
$location.path("/dataPage");
}
and my form is <form ng-submit="submit()">.
so if I am already on the dataPage, then now browser wants to go to dataPage to show new data, but since the path and hash is exactly the same, the browser just will not do anything. (I suppose due to no hashchange event happened, so nobody will notify AngularJS to do anything)
Your question is unclear, if you want a page refresh like F5, one solution is at "Solution for refresh page". But if you just want to reload things, you could follow "Solution for reload page".
Solution for refresh page:
To update a page, all that you need to update the model data of controller and the page will be updated accordingly. Ideally, you should never refresh an entire page in AngualrJS . If you need to do so, you are not using AngularJS correctly .
The $location service does not provide access to refreshing the entire page. If you need to refresh the entire page, you need to use the $window.location object.
$window.location.href = "YOURPAGEPATH";
Solution for reload page:
just want to reload things under ng-view, re-initialized the current route controller, you could do like below.
$route.reload("YOURPATH");
$route.reload() will reinitialise the controllers but not the
services.
If you want to reset the whole state of your application you can use:
$window.location.reload();
This is a standard DOM method which you can access injecting the $window service
Also see: $route documentation
According to $route documentation:
Causes $route service to reload the current route even if $location
hasn't changed. As a result of that, ngView creates new scope,
reinstantiates the controller.
Well you can just save data from request on your global service. And in controller you can bind that data, so if you press submit from header that function should just refresh your data in service.

handling 4xx errors, plus login while using uiRouter

Well, I come from an ngRoute background, and I just started to learn uiRouter.
I used to write two different angular app one for login (login.html), and one for the main app (index.html). In he back-end, I just check if the user is authenticated then redirect them to index.html, else to login.html.
Now I want to encapsulate login into the main app. I have a lot of states requiring user to be logged in. In a lot of them some parts like header and footer are fixed. I'm looking for a concrete solution to handle it with these considerations:
I don't want to use resolve property for all of the states which need authentication.
I don't want my routes to be like /some-prefix-for-authed-paths/... for states that require authentication.
If somebody navigates to a route which requires authentication, I want to redirect them to login page and after login I want to redirect them back into where ever they were.
for 4xx errors, I want to display a custom error page(s) (probably route-dependent). For example if someone navigates to /users/3, and there is no user 3, (which will be determined by the resolve property of the state which will be rejected in case of not-existing user) I want to show them error page, but I DON'T want to change route to something like /error or something, because it actually doesn't make sense at all! Plus, I don't want a state push in the browser history.
You could try to implement an httpInterceptor that catches all non 2xx requests and redirect to some sort of error/login page.
Also to check for authentication you could use uiRouter onStateChange event by adding an access level to your states
.state('someState', {
url: '/someUrl',
isPrivate: true
})
$rootScope.$on('$stateChangeStart',
function(event, toState, toParams, fromState, fromParams){
event.preventDefault();
// Check if state is private
if(toState.isPrivate) {
// Do something
}
});
Note! This is just somewhat pseudo code

preventing access to certain routes based on some condition - angularjs

I am writing a small angular js app with different views. Some views are available to all users but few views are available to only logged in user. I have a small login form on top of my main page [note that there is no separate login screen].
Everything was working fine till here,till then I noticed that even though all the views are not available on my screen initially and will get loaded only once the user logs in. But if the user knows the url of the restricted views , he can bypass the login process.
I have found this stackoverflow question as something very similar to my problem Redirecting to a certain route based on condition. Please see the accepted answer.
But the issue here is that I don't have a separate login screen , so I can't use this code snippet given in the accepted answer
if ( next.templateUrl == "partials/login.html" ) {
// already going to #login, no redirect needed
} else {
// not going to #login, we should redirect now
$location.path( "/login" );
}
How can I achieve the same solution with an integrated login form since I don't have a separate url for my login screen?
I am using ui-router, and that provides a resolve attribute for every route. Whenever a route is accessed, the resolve part is executed first. It returns a promise, and the page does not get loaded until it is resolved. Here you can write some server side calls to validate user, and if invalid, just redirect to any valid page. You can write a resolve to every view and validate the user at every step. I used node/express sessions for storing the logged-in user data.

Resources