Hash removed from default routing in AngularJS - angularjs

I'm writing an angular app where I need to accept an URL with a fragment hash (OAuth 2.0). Making it short it looks like this.
http://example.com/#access_token=918f4d6e90b25b13ef66a
When this URI is loaded, the routing process does not recognize the path and and it correctly redirects the app to the default route. The problem is that in thi way I loose the hash and I can't parse it anymore.
What I want to do is to parse the hash before it gets removed.
Do you have any idea on a possible solution?
Thanks.
P.S. In this app I can't use HTML5 mode. With it enabled it works just fine.

You just need to define an extra route to "catch" and save the access-token, before redirecting to another route.
E.g.:
$routeProvider
...
.when('/access_token=:accessToken', {
template: '',
controller: function ($location, $routeParams, AuthService) {
AuthService.login( // `AuthService` will save the token
$routeParams.accessToken); // The token is available in `$routeParams
// under the key `accessToken`
$location.path('/'); // Redirect to the default route
$location.replace(); // For better UX, remove this route from history
}
})
See, also, this short demo.
Or navigate directly to this URL for a live demo:
http://fiddle.jshell.net/ExpertSystem/N8CgC/show#access_token=12345.

Related

UI-Router state doesn't work when I'm redirected to my app from Adyen (payment system)

I'm trying to get my app to work with the Adyen payment system. At a certain point I redirect the user to Adyen to make a payment. When the user is done, Adyen redirects to the provided return url. Adyen also adds some query parameters to the url, that are useful to use on the return url page.
This is an example of a url that Adyen redirects to after payment:
http://my.domain/#/payment/result?authResult=AUTHORISED&merchantReference=000220-16402-1459625180&merchantSig=BjeKwBOp70hcsNRD8GUat3fHn4qXxX4Bb2Nxg9RRa04%3D&paymentMethod=mc&pspReference=8614596253051402&shopperLocale=en_GB&skinCode=wMrD9Eei
Now, I want to use UI-Router to get me to the proper page, and load the right controller. Depending on the value in the query parameter authResult, I want to go to a different state. I have created a state for /payment/result?authResult:
$stateProvider.state('payment.result', {
url: '/payment/result?authResult',
templateUrl: IM_APP_DIR + '/views/payment.result.html',
controller: function($state, $stateParams) {
switch ($stateParams.authResult) {
case 'AUTHORISED':
$state.go('payment.authorised');
break;
case 'CANCELLED':
$state.go('payment.cancelled');
break;
etcetera...
}
}
});
Example for the authorised state:
.state('payment.authorised', {
url: '/payment/authorised',
templateUrl: IM_APP_DIR + '/views/payment.authorised.html,
controller: 'PaymentCtrl'
})
Now when I test this and the Adyen test environment redirects met to the url mentioned above, the controller for the /payment/result?authResult state is never entered. (I placed a breakpoint at the line where the switch is defined, but Chrome never gets there.) Instead I get a lot of errors from angular in my console. They mainly say I am getting into an infinite digest loop (https://docs.angularjs.org/error/$rootScope/infdig?p0=10&p1=%5B%5D). I guess that has nothing to do with this problem, since this is also what happened when Adyen redirected to the return url and I had not yet defined any state for the redirect path.
Any ideas what may be wrong with the states I defined, taking the redirect url into account?
Thanks a lot in advance for any help!
Ok, I got it to work eventually. The problem was that I had the parent > child setup in the routing config all wrong. I had also omitted a ui-view directive element in the parent route that the child view could be rendered to.

ui routing without urlRouterProvider.otherwise

Routing doesn't occur if I don't include $urlRouterProvider.otherwise('/'); in the config.
my go app engine setting with gorilla is
r := mux.NewRouter()
r.HandleFunc(/admin, handleAdmin)
and angularjs config is;
angular.module('admin')
.config(function($stateProvider, $urlRouterProvider) {
$stateProvider.state('home', {
url: '/',
templateUrl: '/admin/index.html',
controller: 'AdminController as admin'
});
//$urlRouterProvider.otherwise('/');
})
when I don't call $urlRouterProvider.othwerwise line, and I open http://localhost:8080/admin I expect to see admin/index.html, but I don't. I see it only if I navigate to http://localhost:8080/admin#/ manually.
But if I add $urlRouterProvider.othwerwise option and go to http://localhost:8080/admin it redirects automatically to http://localhost:8080/admin#/
I don't think this is usual way to do it because I may want "otherwise" to route to a custom 404 page. What point do I miss?
By default, Angular adds the hashPrefix in front of urls. So when you navigate to http://localhost:8080/admin , You don't see index.html since you have not yet visited the url as defined in the angular's ui-router. You will have to navigate to http://localhost:8080/admin/#/ to actuall be in the / state of your application.
It is the same reason that your app doesn't work without the .otherwise(), since then it automatically redirects you to the / state later.
For a possible fix:
Inside your .config function:
// This is in your app module config.
$locationProvider.html5Mode(true);
And in your index.html:
// This is in your index.html head.
<base href="/" />
The problem is not with not having a declared otherwise.
The problem lays on your route. You're specifying the url to be '/', that means the state home is accessible only through http://localhost:8080/admin/ and NOT through http://localhost:8080/admin
What the otherwise does is. When you access the url http://localhost:8080/admin the router try to find a state that matches the url, but don't find it. So it redirects to http://localhost:8080/admin/ which matches with your home state.

Angular route async authorization

Angular doesn't provide any authorization/access permission on routing (I'm talking default Angular route 1.x and not beta 2.0 or UI route). But I do have to implement it.
The problem I'm having is that I have a service that calls server to provide this info and returns a promise. This data however is only obtained once and then cached on the client, but it still needs to be obtained once.
I would now like to handle $routeChangeStart event that checks whether next route defines a particular property authorize: someRole. This handler should then get that data using my previously mentioned service and act accordingly to returned data.
Any ideas beside adding resolves to all my routes? Can I do this centrally somehow? Once for all routes that apply?
Final solution
With the help of accepted answer I was able to implement a rather simple and centralized solution that does async authorization. Click here to see it in action or check its inner working code.
The most simple way is to deal with current route's resolve dependencies, and $routeChangeStart is a good place to manage this. Here's an example.
app.run(function ($rootScope, $location) {
var unrestricted = ['', '/login'];
$rootScope.$on('$routeChangeStart', function (e, to) {
if (unrestricted.indexOf(to.originalPath) >= 0)
return;
to.resolve = to.resolve || {};
// can be overridden by route definition
to.resolve.auth = to.resolve.auth || 'authService';
});
$rootScope.$on('$routeChangeError', function (e, to, from, reason) {
if (reason.noAuth) {
// 'to' path and params should be passed to login as well
$location.path('/login');
}
});
});
Another option would be adding default method to $routeProvider and patching $routeProvider.when to extend route definition from default object.
ui-router have a lot of events that you can easy manipulate. I always use it.
State Change Events have everything you need. Something like this will be implement in the AngularJS 2.x.
But if you are looking the solution for native Angular 1.x.y router this solution will not help you. Sorry
If you can use ui-router, you could do this:
.state('root', {
url: '',
abstract: true,
templateUrl: 'some-template.html',
resolve: {
user: ['Auth', function (Auth) {
return Auth.resolveUser();
}]
}
})
Auth.resolveUser() is just a backend call to load the current user. It returns a promise so the route will wait for that to load before changing.
The route is abstract so other controllers must inherit from it in order to work and you have the added benefit that all child controllers have access to the current user via the resolve.
Now you catch the $stateChangeStart event in app.run():
$rootScope.$on('$stateChangeStart', function (event, next) {
if (!Auth.signedIn()) { //this operates on the already loaded user
//user not authenticated
// all controllers need authentication unless otherwise specified
if (!next.data || !next.data.anonymous) {
event.preventDefault();
$state.go('account.login');
}
}else{
//authenticated
// next.data.roles - this is a custom property on a route.
if(!Auth.hasRequiredRole(next.data.roles)){
event.preventDefault();
$state.go('account.my'); //or whatever
}
}
});
Then a route that requires a role can look like this :
.state('root.dashboard', {
//it's a child of the *root* route
url: '/dashboard',
data: {
roles: ['manager', 'admin']
}
...
});
Hope it makes sense.
I've approached this issue many times, I've also developed a module (github).
My module (built on top of ui.router) is based on $stateChangeStart (ui.router event) but the concept is the same with the default ng.route module, it's just a different implementation way.
In conclusion I think that handling routing changing events is not the good way to perform an authentication checking:
For example, when we need to obtain the acl via ajax the events can't help us.
A good way, I think, could be to automatically append a resolve to each "protected" state...
Unfortunately ui.Router doesn't provides an API to intercept the state creation so I started my module rework with a little workaround on top of $stateProvider.state method.
Definitively, I'm looking for different opinions in order to find the correct way to implement a Authentication Service in AngularJS.
if are there anyone that is interested in this research... please, open an issue on my github and the discuss

how can i change the base url on condition in angularJS

I am caught up in a situation here.
I have a switch in my login page and i want to redirect my service for authorization accordingly.
I want to load my services dynamically after checking some conditions, when i launch my application for the first time. But in angular as we have ng-app and we need to inject all the modules in it at the start. My base url is set before the app is launched. Is it possible to change my base url on condition?
On launching the application the base url gets assigned before the launch of first page which is login page. I have a switch in my login page which if true, i need to set the different base url. But since the base url is set and the control doesnt come again to this module, i am not able to change it conditionally.
This is in my service.js
angular.module('sampleService', ['ngResource'])
.factory('sample', function ($resource, $rootScope) {
$rootScope.serviceUrl = "http://......";
...
});
This is in my app.js
var app = angular.module("sampleApp", ["sampleService"]);
This is how I am using $routeProvider
app.config(["$routeProvider", function ($routeProvider, $locationProvider) {
...
}]);
I want to change my service url conditionally as:
if(myflag)
$rootScope.serviceUrl = "url1";
else
$rootScope.serviceUrl = "url2";
I hope this will give some idea of what i want to do and what i am doing.
Thanks in advance.
I suppose you're using ngRoute in your application, right?
Maybe you should look into uiRouter where you control state. And what you're describing could fall into this category.
https://github.com/angular-ui/ui-router
Also read the differences between the two in this Stackoverflow content.
Yes uiRouter is best solution when it comes to dynamic page loading
Here is a simple PDF on how to use ui-sref in your code ,
Simple Example

Problems with AngularJS $location.path

I am having fun and games with a troublesome AngularJS route, so lets see if I can explain this as well as I can.
APP.JS:
app = angular.module("index", []);
app.config(['$routeProvider',
function($routeProvider) {
$routeProvider.
when('/booking/damage/:regnumber', {
templateUrl: 'damage',
controller: 'DamageCtrl'
}).
otherwise({
redirectTo: '/'
});
}
]);
app.controller('IndexCtrl', function($scope, $location) {
$scope.regnumber = '';
$scope.progress = function() {
if ($scope.regnumber !== '') {
$location.path('/booking#/damage/' + $scope.regnumber);
} else {
$location.path('/booking#/damage');
}
};
});
My initial page has a path of
http://localhost/windscreens/glass/index#/index
and within this page is a form that via ng-submit="progress() calls the $scope.progress function within my IndexCtrl controller. There is a field in the form of ng-model="regnumber".
So when filling in the input field with lets say "ABC" and clicking on the button, I'd expect the path to become:
http://localhost/windscreens/glass/booking#/damage/ABC
But it becomes
http://localhost/windscreens/glass/index#/booking%23/damage/ABC
Thing is I am still really becoming used to the Angular routing system and haven't quite got it yet. Any advice on this will be appreciated!
Why am I seeing what I am seeing? What have I got wrong here?
The Angular $routeProvider can only change the part of the URL after the hash (#), so when calling $location.path(), you just use a plain URL fragment like you defined in the route for DamageCtrl.
Some explanation
I'm going to simplify a bit here, but hopefully it will help you understand what's going on.
You're making a SPA (single-page app), so the URL you enter in your browser to get into your app doesn't change while you navigate between routes; by default $routingProvider appends #/whatever/route to that base URL. In your case it looks like you have your entry point (ng-app) in a file called /windscreens/glass/index, so all routes will get appended to that.
Because you don't have an /index route defined that we can see, I'm not sure how http://localhost/windscreens/glass/index#/index is working for you. It should send you to http://localhost/windscreens/glass/index#/ because your otherwise route is just /.
Back to the question
If I understand your question correctly, I would make the index file (where ng-app lives) /windscreens/glass/index.html, then you can just enter http://localhost/windscreens/glass/ to get into the app, because the webserver will serve the contents of index.html by default.
At that point, your index page URL would become http://localhost/windscreens/glass/#/, and your /booking/damage/ routes would be http://localhost/windscreens/glass/#/booking/damage/ABC etc. The code to navigate to them would then be
$location.path('/booking/damage');
Angular routing changes the route on the page; it doesn't take you to a new directory or page.
So if index.html contains your Angular app and you have routes for "booking", "reservation", "cancellations", etc. You'll end up with urls that look like:
/glass/index.html#/booking
/glass/index.html#/reservation
/glass/index.html#/cancellations
The route merely appends itself to the index.html.
So, in a sense, your routes are working correctly. The %23 that you see being added is the url encoded # sign.
If you have a second Angular app that is found at /glass/booking and you're trying to forward the user to it, you can use $window.location.hash and $window.location.pathname. For example,
$window.location.hash = "/damage/ABC";
$window.location.pathname = "/windscreens/glass/booking";
should take you to:
/windscreens/glass/booking#/damage/ABC

Resources