I am using Grails 3.1.4 together with AngularJS.
Certain sections on the page are for logged in users only and I want to restrict access to the according html templates, so only they can access them.
I have set up the Spring Security so that access to those templates is restricted.
The problem I encounter is, that it appears that all the html templates are loaded when the page is loaded the first time. At that moment the user is not logged in, thus the server returns a 401 error.
Once the users is logged in and tries to navigate to the restricted page, angular checks the template cache, realizes nothing is there and returns a 404 error.
When I look at the index.html in the developer tools, I can see, that the asset pipeline includes a script tag for each template, so all the templates are loaded at the initial page load.
Is it somehow possible, to have the templates loaded and cached dynamically when they are needed for the first time?
You might allow HTML templates to be accessible to all. For restricting the content, you might need to implement Spring Security token based Authentication and check accordingly what content should be visible to whom.
Reference : http://alvarosanchez.github.io/grails-spring-security-rest/2.0.0.M2/docs/index.html
Related
I am building a complex web page, that has many users/groups/permissions where user can see all/some/none of the object of specific type according to the ACL (using Symfony2 and Symfony2 ACL).
What am I dealing with now is that I don't want to show every UI element in the frontend and just throw 403 error if user does something that he has not permission for. Hidding elements that the user has no permission to see would be better for UX.
It is like if I show a product info page for the user, I don't want to show the EDIT/DELETE buttons, if user does not have permission for doing that on this product or all products (class scope permission).
With Twig and server side rendered templates it would be easy as adding bunch of permission checks in template
{% if is_granted('EDIT', product) %}
<button>Edit product</buttom>
{% endif %}
But how to handle this in clientside with AngularJs?
What I was thinking is the following:
Create a controller method that serves Twig templates rendered server side for AngularJs. This takes a parameter of id identifying the object, which the user must have permissions on in order to see the EDIT/DELETE buttons rendered in the template (Twig and is_granted() handles this serverside)
User ask to view a specific product /product/1. The router templateUrl would be /templates/product/view.html?object_id=1, where object_id identify the object, that should be used while rendering templates serverside to grant or deny rendering ui elements.
The product JSON is then fetched and put in the template, which has already been rendered serverside, and has some Angular {{ }} waiting for product data to be placed in.
Are there any similar cases solved using other serverside technologies that you are familiar with and can be taken as an example to lead me on my way to success?
In our project client side Angular code is enough.
Back end wont ever send any data to front end user have no permission to view/access. (403)
Front end present login page to user with witch user can authenticate. Front end then send provided credentials to Back end, which is responsible for actual checks.
Back end will provide Front end with user role/access right/privileges. If those are more complex dedicated API endpoints will be provided. (Fe: Can user access this? Be: No. Fe: that? Be: Yes, but not delete. etc.)
Front end will use if's on data returned from Back end to conditionally render UI parts.
Do note that I use back end and front end, as described technique is framework agnostic and will work with pretty much any.
For Angular we created CurrentUser service that had is('role_name') that would return true if user had assigned role, false otherwise. ng-if or plain old if(), then where used to conditionally render html tags/data.
I'm asking this because a couple of times now, I've tried to play around with the $locationProvider.html5Mode(true) command along with <base href="/"> and ran into a lot of errors calling the scripts/styles/images for my project. I guess there must be something I am doing wrong, but is there a certain folder structure you should follow so you don't run into these errors? Or is there a specific way that the base href works that I'm not quite understanding?
Recently, I thought I'd try it on a very, very small app. It's effectively a static website, but I want to take advantage of Angular's routing to make sure all of the pages can load instantly. So my structure would be something like this:
my-project
css
images
js
angular
app.js
app.routes.js
mainCtrl.js
views
home.html
about.html
contact.html
index.html
So I know that this folder structure isn't great, but I'll only be using Angular in this project for routing, nothing more, so it fits my needs.
I put into the head <base href="/">, put in body ng-app and ng-controller, and inside the body put a <div ng-view> somewhere too.
I added in the $locationProvider.html5Mode(true) and tried the app out. All of my scripts are then being loaded as http://localhost:8888/script.js which is incorrect. The project is located in a folder so that index.html is located in http://localhost:8888/my-project/index.html. So, it should be loading the scripts from http://localhost:8888/my-project/js/angular/app.js for example.
Is there something that I'm not understanding about the base href? Eventually I may host this app somewhere online, so I want the URLs to scripts etc to all be relevant to the file really. Anyone have any ideas?
Alright, so above the base href tag I would have my CSS styles which would be linked as css/style.css and at the bottom of my body tag I would have my scripts loaded as js/init.js or js/angular/app.js for example. This would try to load it as if the js folder is located directly at localhost:8888/js.
The Angular framework is a Single Page Application (SPA) that is able to run in a browser by essentially tricking the browser into running code snippets rather than make server calls, by making use of the "hash" (#) page anchor. Normally, a URL with a # would jump to a specific anchor point in the page; in the case of Angular or other similar SPA frameworks, the # is redirected to a code segment instead.
Ideally, you would like to not have to reference this # in your page URLs. This is where Html5Mode comes into play. Html5Mode is able to hide the #, by using the HTML5 Push State (aka history API).
When Html5Mode is enabled, the normal links on the page are silently replaced by Angular with event listeners. When these events are triggered, the current page is pushed into the browser history, and the new page is loaded. This gives the illusion that you are navigating to a new page, and even allows for the back button to operate.
This is all fine when you are dealing with links which are clicked from within the running application, but relying on event listeners can't work if you navigate to the page from an external source, where Angular isn't loaded into memory yet. To deal with this, you must be loading your pages from a web server which supports URL rewrites. When the server receives a request for a URL that there isn't a physical page for, it rewrites the URL to load the base HTML page, where Angular can be loaded and take over.
When Angular receives a request for a route which has been rewritten in this manner, it must first determine what the intended route was. This is where the Base HTML Tag comes into play. Angular uses the Base reference to help it to determine which part of the URL is on the server, and which part is a client route. Essentially, where the # in the URL would be if Html5Mode was not enabled.
Unfortunately, Base is an HTML Tag that is used by the browser for more than just Angular. The browser also uses this tag to determine the correct location to load scripts and resources using relative paths from, regardless of the path in the location bar. In general, this isn't a problem if all of the scripts and resources are relative to the location of the Index.html file. When Base is omitted, the browser will load scripts from the apparent base path determined by the current URI. However, once you provide it, the browser will use whatever value you have supplied.
In general, unless you are hosting angular on a sub-page of your site and you want your users to expect something specific in the URL string, you should always control the base on your server, and use Base="/" on the client side.
I have a single page web app which has close to 10 different sections like
discussions
profile
video
etc , each of them have their states in the router as , it has its own controller and template and the urls are like
1. http://myapp/#/discussions
2. http://myapp/#/profile
3. http://myapp/#/video
when the angular app loads all the templates and js files are downloaded and at IIS only one request is made ie:
http://myapp/
but the things after '#' don't get passed to server. The UI router replaces templates at the client side but I want to track how many users visit the particular sections of my web app.
I cant do that from IIS logs as no resources are requested for individual sections in short i am expecting a log entry in IIS as below when a user visits discussion section
http://myapp/discussions
please let me know if I am correct in this approach or should i follow some other method.
A single page app, by definition, only makes an initial request to the server (IIS) to retrieve HTML and javascript. Subsequent interaction with your app is all handled by the javascript you loaded initially.
You won't be able to rely on your web server for tracking this. Instead, you should find something that can fire events from the javascript side, such as Google Analytics.
Is it possible to get ng-include directive to work with a CDN?
In this case:
<div ng-include='http://cdn.somewebsite.com/templates/home.tpl.html'></div>
The cdn sub-domain of somewebsite.com is mapped through DNS/CName to a content delivery network.
However when loading the main site somewebsite.com, it does not render the template. I guess internally it is treating this as a cross-domain call.
Is there any work arounds to get Angular templates be hosted on third party CDN's and work with the local domain?
Yes, you are right - the problem is with cross-domain call.
Official docs say:
By default, the template URL is restricted to the same domain and protocol as the application document. This is done by calling $sce.getTrustedResourceUrl on it. To load templates from other domains or protocols you may either whitelist them or wrap them as trusted values. Refer to Angular's Strict Contextual Escaping.
and
Please note: The browser's Same Origin Policy and Cross-Origin Resource Sharing (CORS) policy apply in addition to this and may further restrict whether the template is successfully loaded. This means that without the right CORS policy, loading templates from a different domain won't work on all browsers.
Example:
angular.module('myApp', []).config(function($sceDelegateProvider) {
$sceDelegateProvider.resourceUrlWhitelist([
// Allow same origin resource loads.
'self',
// Allow loading from outer templates domain.
'http://cdn.somewebsite.com/templates/**'
]);
});
Helpful links:
ngInclude
$sce
So your problem may be solved by appropriate angular settings, but not for all browsers.
Perhaps you can alter this code a bit. I have my client's intranet in a datatbase and that's connected to a remote API server. I use an angular app to pull the HTML, but it is part of a JSON object, so obviously that's pretty specicific to my own needs.
I happen to have full control over the CORS attributes of my server, so I can do this. If you try it google or some other site... you're stuck with an iframe. (I have not idea what that's allowed by JS is so strict!)
So, here's what I do to get my remote HTML data.
1:
In my controller I add this:
<div ng-bind-html="content"></div>
then in the code, I add this
$http.get(url)
.then(function (data) {
$scope.content = $sce.trustAsHtml(data.data.PageData);
});
That's it. Just don't forget that the site in the URL has to be allowing you to get the data via your remote system.
NOW: Just for fun, I used to use IFRAMEs to bring data from other sites before CORS was even invented. It was a big hack. Before AJAX, I'd make a tiny form on one page with all the form values empty. On another page, I'd have an iframe for it and just fill those input boxes with javascript and post them back with javascript, keeping the main page without a reload.
If you need more control of your data, you could simply have a hidden iframe, rip the HTML you want from it, put it in a variable and drop it whereever you want on your page.
There's always some half-a$$ed way to get things done. :)
Is there a blog, project, gists or seed out there using angularjs and requirejs that provides authentication not based on routes? I'm working on building a site that will show the user the same rendered view, but different data fed from the server based upon their authentication. I have session handling already written into it, but I need angular to check the server for the session on the initial render for changing login buttons to logout and getting only the data the user wants to see that they've selected.
I attempted to use the angular run method to initially grab the session from the server, but when using requirejs, my app module doesn't exist at the time of calling the run method.
From my understanding, please correct me if I'm wrong, I should be creating an injector or using the $rootScope to carry the user information to routed controller and show the user what they is related to them. If so, then I need to have a service that is initially fired during the project instantiation to retrieve the user's session data. I'd prefer to not use the servers template rendering to put the users session data into javascript if possible.