I am new to Angular and one of the things that I am trying to wrap my head around is the route authorization. I am coming from the .NET/IIS world where route authorization is as simple as decorating your API or MVC controller with the [Authorize] attribute.
I have read several posts and documents on how Angular handles routing. My concern is that the authorization is happening on the client. What prevents the user from firing up the dev tools, breakpoint the script execution, and changing the variables in the authorization service that control whether or not the user is authorized to access this route?
As I mentioned, I am new to Angular, so maybe I have misunderstood how the routing works. If this is the case, please correct me.
So, my question is: How can one achieve the same level of security with Angular routing as you would if using server-side routing authorization?
Thank you.
It is evident that pure client side solution cannot exist. Thus, only Angular routing cannot be used in cases when a certain route have to be securely restricted. I am thinking that routing has to be handled on both ends.
I am using Node so here is what I did:
//first evaluate the restricted route
app.get('/admin/*', function(req, res) {
//authorization
var authenticated = call_to_auth_service();
if (!authenticated) {
res.status(403);
res.end();
}
else {
//just remove the front /
var url = req.url.replace('/admin/', 'admin/');
res.render(url);
}
});
//open access - everything else goes back to the index page and there the angular routing takes over
app.get('*', function(req, res){
res.render('index');
});
This works, but I am not sure if this is the best approach. What do you think? Is this the proper way of handling the routing?
Thank you.
Related
I'm trying to make angular's routeProvider to manage all the routes of my app. I define my routes accordingly in the app.js file of angular (client side):
(function(){
var app = angular.module("Crowdsourcing", ["ngRoute"]);
app.config(function($routeProvider){
$routeProvider
.when("/single/:street/:buildingNumber/:apartmentNumber", {
templateUrl: "/views/single.html'",
controller: "ListingCtrl"
})
.otherwise({redirectTo:"/"});
});
}());
However, when writing a URL (e.g. /single) on the browser that request is tried to be handled by my backend express/node api. Thus, it is not found because the backend routes are for example /api/foo. How can I make sure Angular's routeProvider always manages my requests?
And more important what is the basic flow of the application? When Client writing a URL how does the application knows whether the url is handled by express js or by routeProvider ?
Generally you distinguish URLs which are handled by the angular app from the express URLs by using a # sign between hostname and page URL.
For example:
If you want to request an URL from the backend you would call:
http://host.com/api/foo/
Instead if you want to request an URL from the angular app you would call: http://host.com/#/single
So if you want to call the angular app URL you have add the # sign.
This is a very important fact you need to keep in mind when working with single page applications.
Update:
Have a look at this seed project: https://github.com/btford/angular-express-seed
It uses the a similar technology stack as your project. This should make it more clear for you.
I have faced the same issue few days ago. The problem is, even when you make a server call for data it redirects it to client side routing and searches in $routeProvider.
You need to make sure you add something like below code in your server.js.
It means when ever there is a call starting with /api then it will redirect it to the routes in server side instead of client side routing.
var api = express.Router();
require('./app/routes')(api);
app.use('/api', api);
In your factory when we make a call as below this will go to server side routes.js rather than client side.
$resource('/api/adduser')
I have read several other answers on this topic, but I do not fully understand how this works. I have an angular front-end. I am trying to use $routeProvider to load partials into my page for a single page application. I am getting 404 for the $routeProvider requests and I think there must be a way to set this up without declaring a route for every partial on the server. There will be many more partials soon even though I only have one declared in my config right now. My routeProvider code looks like this:
myApp.config(['$routeProvider',
function ($routeProvider) {
$routeProvider.
when('/register', {
templateUrl: 'partials/register',
controller: 'registerCtrl'
})
}])
but going to localhost:3000/register gives a 404 error. I looked at this response on SO as well as some others, but I have not been able to put all the pieces together.
Express 4, NodeJS, AngularJS routing
How is this solution from the above post
app.use('/*', function(req, res){
res.sendfile(__dirname + '/public/index.html');
});
able to find the partials and return them to the front end (btw this snippet did not work for me).
My app isn't much different than creating a new express app with express generator. I have these require and use statements related to routing:
var routes = require('./routes/index');
var users = require('./routes/users');
app.use('/', routes);
app.use('/users', users);
and this route to serve my home page in routes/index:
router.get('/', function(req, res, next) {
res.render('home');
});
When you use front-end routing you don't have to declare a route for every partial on the server. The simplest form of the solution is to let the user hit any route with a wildcard match on the back, and no matter what route they're viewing you just serve up the same front end application template. So in fact you for the most part do away with backend routing altogether. Once your JS kicks in and reads the current route out of the navbar, it will resolve the appropriate view.
Check out one of the tutorials for how to configure your express backend to handle this:
http://fdietz.github.io/recipes-with-angular-js/backend-integration-with-node-express/implementing-client-side-routing.html
There's some nuance and previously there was controversy around this structure because of things like SEO, but a lot of that has been cleared away by Google now being able to parse JS and resolve the view in SPAs (http://googlewebmastercentral.blogspot.com/2014/05/understanding-web-pages-better.html)
Of course, you will actually have some routes on the backend in order to accept api requests, but ideally that's it unless you choose to get fancy and go isomorphic, prerendering the js on the backend and serving a completed view (though that's better suited in React for now, at least until Angular 2.0)
I have an angular site hosted in S3, and I have a 404 route set up (I use hash), if someone for example does
mysite/#/gibberish
goes to
mysite/#/404
and on the s3 bucket we have a redirect rule in place for
mysite/gibberish
goes to
mysite/404.html
all is well
Now I just want to add an extra logic on top that if someone types in
mysite/customerid
which is a 404 to somehow redirect this to an angular controller so I can send this request to right page.
So somehow in my redirect in S3 rule add a reg exp for some incoming request and rather than serve 404.html send it i.e. mysite/#/handlethis
Is this possible ?
Depending on the router of your choice, you could do something like the following (which is what we've done (well, not precisely this, but close)):
ui-router
app.config(function ($urlRouterProvider) {
var regx = /\/#\//; // match against /#/
$urlRouterProvider.otherwise(function ($state, $location) {
if (!regx.test($location.path()) { // if no match
$state.go('customHandlingState', /** params **/, /** configuration **/ });
// Transition to your custom handler state, with optional params/config.
}
});
});
You could pair this up with custom stateChange[Start|Stop|Error|Success] handlers in the run block of your app to customise it to your liking.
I would supply an example of how to do this with ngRoute, but I gave up on ngRoute two years ago and haven't looked back since. As such I have no suggestion to give, nor was I able to find a solution to the problem you present.
I would strongly suggest you scrap the S3 portion of this recipe as it will make your life a lot easier when it comes to client side routing (speaking from personal experience here, it's my opinion on the matter - not fact) and handle your 404's/500's on the client with custom state handlers.
If need be you could hook into some logging service and store some data whenever a client/person ends up in an erroneous state.
I suppose my 'counter question' is; What do you gain from using S3 redirect rules? So as to get a better understanding for the needs and goals here.
Some reference material to go along:
ui-router#$state.go
ui-router#$urlRouterProvider.otherwise
I would suggest using routeParams
https://docs.angularjs.org/api/ngRoute/service/$routeParams
the route would look like this:
/mysite/:cid
then access the id with the controller:
$routeParams.cid
I hope this could help
You can manually configure your server to always serve your index.html(your main html file which includes reference to angular script) on all incoming http requests. Client routing will be handled by Angular
I apologize this question turned out a bit long, but I have worked on this for some time and really needed to explain all the story.
Background: App based on MEAN stack, trying to authorize Facebook logins using Passport.js.
Following Passport.js guide I implemented something similar to:
// HTML
Add a Facebook login
// send to facebook to do the authentication
app.get('/connect/facebook',isLoggedIn, passport.authorize('facebook',
{ scope : 'email' })
);
// handle the callback after facebook has authorized the user
app.get('/connect/facebook/callback',
passport.authorize('facebook', {
successRedirect : '/profile',
failureRedirect : '/profile'
}));
Notice the target=_self in the html in order to skip Angular routing.
Clearly, authorization works fine. However, redirection does not work, as the routing is handled by Angular. After authorization I never land on /profile (but on the default Angular route).
Therefore, I tried with a custom callback as suggested by Passport.js here, with the hope of passing json data to Angular, and let Angular do the routing. I ended up doing something like:
// In the controller
$http.get("/connect/facebook").success(function(data){
// here I wait for json data from the server and do the routing
});
// I call this route from Angular
app.get('/connect/facebook',isLoggedIn,passport.authorize('facebook',
{ scope : 'email' })
);
// But Facebook lands here!
app.get('/connect/facebook/callback',function(req, res, next) {
passport.authorize('facebook', function(err, user, info) {
res.json({something:smtg});
...
Clearly custom callbacks work for local-login, as Passport.js explains. But here do you see the problem? I call /connect/facebook from Angular, but I should receive some json from /connect/facebook/callback.
I am about to give up Passport, but before this, do you see any solution which would allow landing on /profile after FB authorization, perhaps with a custom message? Many thanks for reading through.
EDIT:
The same question had been reported as an issue on the Passport-Facebook GitHub account. Some additional attempts have been posted there, but not quite the fix yet.
This is a bit more in depth than can be described in one answer, but I'll try to start pointing you in the right direction.
Essentially, Angular.js routes are not really HTML routes at all, but an internal route structure that happens to use the URL for use of the end user. Remember that Angular.js is a client script, and that a full page reload is not desired, as this will reload the entire script. Therefore, /# is used to trick the browser into jumping to a specific bit of code within the already loaded script. (as opposed to the traditional anchor location in the HTML document). Unfortunately (or fortunately), HTML 5 mode allows you to hide the /# part of the url, so instead of seeing http://somesite.com/#/someroute you just see http://somesite.com/someroute. Rest assured, however, that the /# is still there. Angular.js uses the HTML5 pushState (AKA HistoryAPI) to perform the magic replacement.
Given this, if you have called a server route, you are outside the Angular.js script, and any call to load the angular script again will start from the very beginning. You can't actually call your Angular.js route from the server without a full reload. Therefore, you are really doing a double route redirect here. Your server should be calling it's default route for angular, appending /#/someroute to the call. The angular.js page will load, parse off the /#, and redirect to the correct angular route. Keep in mind, however, that if there was any dependency on already loaded objects, those are no longer in memory. Therefore, any route accessed this way should operate as if it is an entry point to your application.
Effectively, you should try using successRedirect : '#/profile', keeping in mind that the profile route in angular should be treated as an app entry point.
Hopefully this gets you started.
If #Claies's way is not working, is it possible you have not get rid of the #= fragment from the facebook callback.
Have a read of this post
I'm working on e2e tests for a web app and I would like to log in a user and persist their session.
Specifically the following scenario:
The log in form is posted with valid credentials.
Angular routes the user to a landing page.
I call browser.get( /* the current url */ )
I expect the current URL to be the same, instead of my user getting kicked back to the log in screen.
We're using HTTP header based auth and I don't know how to configure this scenario for testing purposes.
Is it as simple as activating cookies somewhere? Or maybe supporting the auth headers via a config?
I managed to solve this be simply adding browser.sleep(1000) in before calling browser.get( /* the current url */ ).
Basically Protractor was hammering on the router to fast and my app was kicking me out before auth creds were set. Or, perhaps, might be the HTML5 routing takes a little time to process (our deep links are hashed, but then angular converts the hash to HTML5 routes).
You can use browser.waitForAngular(); too.
You can use promises like this
goAfterLogin: function(){
browser.get('http://www.example.com').then(function(){
return this; //or other return
}).then(function(){
return this;
});
}
Its up to you how will you use it (you as well can create promis with waitForAngular() )