Scenario:
We have an API that we are building an Angular single page app for, and we require multiple tenants. Each tenant has specific access credentials that allow them to interact with their database. But I have to allow for the tenant to take this SPA and host it on their own website if they choose to, which is why I have made it a very generic Angular SPA. We cannot expose the security credentials to the API in JavaScript, so it takes a server side component. I am using MVC routing to interpret which tenant the user is going to, then it acquires a session token and passes it to the JavaScript so the SPA will function using that token.
Normally, with this scenario if you are using Razor, you can just use bundling or the virtual directory (~) in your JavaScript src attribute. However, since it is pure HTML, it has no clue what ~ means.
Here is the routes I have for the tenant:
routes.MapRoute(
name: "Default",
url: "{tenantName}/{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
So a typical URL will look like:
http://localhost:51982/Tenant2/Home
With the JavaScript src referencing:
<script src="Scripts/angular.min.js"></script>
Of course, this causes the URL to look like:
http://localhost:51982/Tenant2/Scripts/angular.min.js
Which will result in a 404 error. Now, if I change it to:
<script src="/Scripts/angular.min.js"></script>
It will work because it goes to the root of the site. That is great, up until I have to deploy it to as a Web Application rather than a website like:
http://localhost:51982/WebApp1/Tenant2/Home
So ideally, I'm trying to find a way to force MVC to strip out the Tenant identifier of the URL if the reference goes to an actual file name. The only way I can find that it will work is that I have to just use /Scripts/xxxx.js and so the site will only work when set up as a Website and not a Web Application. I was hoping someone might have come across this unique scenario before.
Related
I want to make an angular app with routes and jwt auth, but I don't want normal users to see the HTML partials of admin views. What's the best way to do this with laravel and angular? People can just put "/partials/adminPage1.html" on the url and see the partial when they are not logged in. My API is secure but I don't want the html to be public.
I want it so this is public:
index.php, publicPartial1.html, publicPartial2.html, etc
and only logged in users can use these files:
admin.php, adminPartial1.html, adminPartial2.html
You can/need to approach this in a few ways:
when "someone" puts "/partials/adminPage1.html" you need to check in the sever side (by the service you are checking it's permissions/role) then display/redirect to the appropriate route with ReturnUrl in the query for after login redirect.
You can be more secured by downloading the routes from the server by requesting them first (per user/role/permission) from a dedicated service, but then you'll need to bootstrap your AngularJS, since routing needs to be loaded with AngularJS life cycle, so in that case you are getting the routes, building them in a provider while bootstrapping AngularJS after getting the routes from the designated service as I mentioned.
* I would suggest to simply implement option (1) which is straight forward and most commonly used. *
I am running a MEAN framework with express routing requests. I have two main routes to public/ and app.
With the APP being an API and public being a set of web pages which reads data from the API.
// Setting the app router and static folder
app.use(express.static(path.resolve('./public')));
I have two controllers in the public folder, home and header.
In the home controller I am using Angular JS to call the API and return the results.
The API allows for filtering through the use of query strings:
$http.get('http://search?sumBoth=1&customer=' + customer)
I would like to build up a route specific to this controller along the lines of
http://url/customers/CustomerName
Where CustomerName would be set as the customer variable
Question
a) Is this best done in Angular or Express?
b) Where and how do I implement such routing?
I hope this question is well received, please comment, if you need more information.
I understand that the response of $http.get('http://host/path?sumBoth=1&customer=' + customer) is a list of search results. In that case the path should be a collection path, it's not really a best practice to have search terms in the path. Subcollection paths are pretty standard (something like http://host/customers/search?params, but still the specific search terms should go in the query string)
If on the contrary you expect to retrieve just one result by some identificator (provided for instance the customer name is unique) that's a different story, you should really use http://host/customers/:identifier.
In any case you can use angular resources, both parts of your application need to be aware of the routing. In the front-end you define an additional verb that adds the filters (or just use the standard query one - see https://docs.angularjs.org/api/ngResource/service/$resource). In the back-end you need to route the call and parse the parameters. If it's a list, parse the query string and render your result array, if it's a single resource, parse the identifier, find the corresponding resource and render it back.
I try to build a multi-tenant SPA using Aurelia where the tenant is given as:
http://myapp.example.org/tenant1
http://myapp.example.org/tenant2
http://myapp.example.org/tenant3
How can I return the same index.html for all of these urls (while being able to extract the tenant in the SPA code for Oauth2 login)?
I have made similar AngularJs solutions but then I used a "trick" by implementing a Asp.net web api that accepted a {tenant} route. Is there a simple "all Javascript" Aurelia way of doing this?
The only way to "redirect" all those pages to index without changing the URL is by doing it in the server, with a URL rewrite. The thing you did in ASP.NET MVC was exactly that.
If you want to do that only with javascript, you'll need to redirect all those pages to index and pass the tenant as a parameter. For example:
location.href = location.host + "/?tenant=" + location.search;
The problem here is: by doing that, you'll really need all those tenant pages phisically, what I suppose it's not what you want.
There is another try: you can also make a default 404 page and then make that redirect from there, but you'll throw an 404 error to the client, what I don't think it's good at all.
So, if you're using IIS or any other server, you should just do a rewrite and everything is gonna be ok.
If you're using NodeJS or .NET you can see how to do it directly from the Aurelia's documentation.
http://aurelia.io/docs.html#configuring-push-state
I currently have a set-up based on the meanjs stack boilerplate where I can have users logged in this state of being 'logged-in' stays as I navigate the URLs of the site. This is due to holding the user object in a Service which becomes globally available.
However this only works if I navigate from my base root, i.e. from '/' and by navigation only within my app.
If I manually enter a URL such as '/page1' it loses the global user object, however if I go to my root homepage and navigate to '/page1' via the site. Then it's fine, it sees the global user object in the Service object.
So I guess this happens due to the full page refresh which loses the global value where is navigating via the site does not do a refresh so you keep all your variables.
Some things to note:
I have enabled HTML5Mode, using prefix of '!'.
I use UI-Router
I use a tag with '/'
I have a re-write rule on express that after loading all my routes, I have one last route that takes all '/*' to and sends back the root index.html file, as that is where the angularjs stuff is.
I'm just wondering what people generally do here? Do they revert the standard cookies and local storage solutions? I'm fairly new to angular so I am guessing there are libraries out there for this.
I just would like to know what the recommended way to deal with this or what the majority do, just so I am aligned in the right way and angular way I suppose.
Update:
If I manually navigate to another URL on my site via the address bar, I lose my user state, however if I manually go back to my root via the address bar, my user state is seen again, so it is not simply about loosing state on window refresh. So it seems it is related to code running on root URL.
I have an express re-write that manually entered URLs (due to HTML5 Location Mode) should return the index.html first as it contains the AngularJs files and then the UI-Route takes over and routes it properly.
So I would have expected that any code on the root would have executed anyway, so it should be similar to navigating via the site or typing in the address bar. I must be missing something about Angular that has this effect.
Update 2
Right so more investigation lead me to this:
<script type="text/javascript">
var user = {{ user | json | safe }};
</script>
Which is a server side code for index.html, I guess this is not run when refreshing the page to a new page via a manual URL.
Using the hash bang mode, it works, which is because with hash bang mode, even I type a URL in the browser, it does not cause a refresh, where as using HTML5 Mode, it does refresh. So right now the solution I can think of is using sessionStorage.
Unless there better alternatives?
Update 3:
It seems the best way to handle this when using HTML5Mode is that you just have to have a re-write on the express server and few other things.
I think you have it right, but you may want to look at all the routes that your app may need and just consider some basic structure (api, user, session, partials etc). It just seems like one of those issues where it's as complicated as you want to let it become.
As far as the best practice you can follow the angular-fullstack-generator or the meanio project.
What you are doing looks closest to the mean.io mostly because they also use the ui-router, although they seem to have kept the hashbang and it looks like of more of an SEO friendly with some independant SPA page(s) capability.
You can probably install it and find the code before I explained it here so -
npm install -g meanio
mean init name
cd [name] && npm install
The angular-fullstack looks like this which is a good example of a more typical routing:
// Server API Routes
app.route('/api/awesomeThings')
.get(api.awesomeThings);
app.route('/api/users')
.post(users.create)
.put(users.changePassword);
app.route('/api/users/me')
.get(users.me);
app.route('/api/users/:id')
.get(users.show);
app.route('/api/session')
.post(session.login)
.delete(session.logout);
// All undefined api routes should return a 404
app.route('/api/*')
.get(function(req, res) {
res.send(404);
});
// All other routes to use Angular routing in app/scripts/app.js
app.route('/partials/*')
.get(index.partials);
app.route('/*')
.get( middleware.setUserCookie, index.index);
The partials are then found with some regex for simplicity and delivered without rendering like:
var path = require('path');
exports.partials = function(req, res) {
var stripped = req.url.split('.')[0];
var requestedView = path.join('./', stripped);
res.render(requestedView, function(err, html) {
if(err) {
console.log("Error rendering partial '" + requestedView + "'\n", err);
res.status(404);
res.send(404);
} else {
res.send(html);
}
});
};
And the index is rendered:
exports.index = function(req, res) {
res.render('index');
};
In the end I did have quite a bit of trouble but managed to get it to work by doing few things that can be broken down in to steps, which apply to those who are using HTML5Mode.
1) After enabling HTML5Mode in Angular, set a re-write on your server so that it sends back your index.html that contains the Angular src js files. Note, this re-write should be at the end after your static files and normal server routes (e.g. after your REST API routes).
2) Make sure that angular routes are not the same as your server routes. So if you have a front-end state /user/account, then do not have a server route /user/account otherwise it will not get called, change your server-side route to something like /api/v1/server/route.
3) For all anchor tags in your front-end that are meant to trigger a direct call to the server without having to go through Angular state/route, make sure you add a 'target=_self'.
This is probably a simply routing issue but after a quick google I haven't clicked as to what I am doing wrong with the routing.
When using SignalR the routing MapConnection corrupts the default MVC route renderings.
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.IgnoreRoute("{*favicon}", new { favicon = #"(.*/)?favicon.ico(/.*)?" });
// default MVC
routes.MapRoute("Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index", id = UrlParameter.Optional } // Parameter defaults
);
// SignalR routing
routes.MapConnection<EventConnection>("echo", "echo/{*operation}");
In this order I get a 404 when SignalR js client connects to /echo/ url.
If I swap the default MVC and SignalR routing around, the SignalR js client connects to /echo/ url and SignalR functions correctly but the Routes are rewritten incorrectly when rendered to the view i.e.
/echo?action=Index&controller=Home
Am I missing something obvious? I am looking at ExclusionConstraints to exclude the echo path but this seems heavy handed, surely there is a simplier way?
Also, I have tried using Regex contraint like in the following question which doesn't work.
MVC2 Routing with WCF ServiceRoute: Html.ActionLink rendering incorrect links!
I ended up fixing this in the SignalR code using the below answer and submitting a pull request.
Html.ActionLink construct wrong link when a non-mvc route is added
https://github.com/SignalR/SignalR/pull/19