I'm using the Polymer Starter Kit and polymerfire to create a firebase app with two routes: 'staff' and 'donate'. The donate route is public and the staff route is private. I'd like to protect the staff route such that only logged in users who have an email ending with mycompany.com can access it. Unauthenticated users will be redirected to /donate.
My first attempt was to wrap links to and declarations of those routes in dom-if templates. This works to prevent users from seeing the route, but if a user is logged in, they will not be able to navigate to that route from the address bar - they'll have to click on a link to the route in the app first. This is confusing: the address bar may still say 'staff' even though the 'donate' page is shown.
<iron-selector selected="[[page]]" attr-for-selected="name" class="drawer-list" role="navigation">
<template is="dom-if" if="[[user]]" restamp="true">
<a name="staff" href="/staff">Staff</a>
</template>
<a name="donate" href="/donate">Donate</a>
</iron-selector>
<iron-pages
selected="[[page]]"
attr-for-selected="name"
fallback-selection="donate"
role="main">
<template is="dom-if" if="[[user]]" restamp="true">
<ksybf-staff name="staff"></ksybf-staff>
</template>
<ksybf-donate name="donate" route="[[subroute]]"></ksybf-donate>
</iron-pages>
I've also tried imperatively preventing route loading:
_routePageChanged: function(page) {
if (this.user) {
this.page = page || 'donate';
} else {
this.page = 'donate';
}
},
This produces similar results: initial browsing to /staff loads the 'donate' route, even though the address bar shows //app/staff.
How can I protect routes in a way which just works from the user's perspective?
Add an id to your <iron-pages>.
Add a iron-select listener to it.
Inside the listener function, add the logic to redirect in case the user is not logged in.
It would look like this:
For the <iron-pages>:
<iron-pages
id="selector"
selected="[[page]]"
attr-for-selected="name"
fallback-selection="donate"
role="main">
<ksybf-staff name="staff"></ksybf-staff>
<ksybf-donate name="donate" route="[[subroute]]"></ksybf-donate>
</iron-pages>
Add the listener:
listeners: {
'selector.iron-select': 'pageChanged'
}
And the function:
pageChanged: function(e){
var page = this.$.selector.selected;
switch (page){
case 'staff':
if (userIsNotLoggedIn){
//Ideally, I would send a toast here saying that the user should be logged in
window.location.href = '/donate'
}
}
}
How I approach this is to utilise the "selected-attribute" of iron-pages.
<iron-pages selected-attribute="active">
This will set the active attribute in your staff and donate pages.
Use an observer on the active attribute in your staff component and if active === true then do your check. If they aren't authenticated/authorized then just kick them back to where they belong.
Related
I'm very used to Ionic, and I recently started developping web apps with Angular2. I have an issue with the Router concept.
Let's say I want to make an app, with a login page, which redirects you to a dashboard with 3 tabs.
I Have a LoginPage component, which checks the login details and redirects to the app. How can I simply switch component in Angular 2 ?
I defined my route
const appRoutes: Routes = [
{ path: 'login', component: LoginPageComponent }
];
And on my app.component page, I juste have a button
<h1>
{{title}}
<!--<a routerLink="/login">Heroes</a>-->
<button (click)="login()">Change</button>
</h1>
trigerring this function :
login() {
this.router.navigate(['/login']);
}
When I click this button, my URL is changing : http://localhost:4200/login
But my view is still my view with the button, there is no "change" like in Ionic with a NavController.setRoot(NewComponent)
I don't want to display my content in a <router-outlet>, I'd like to have distincts pages with navigation between them. Is this possible in Angular2 ?
Thank in advance for your help
Goal: Be able to link directly to profile pages or refresh pages.
Problem: If you visit this profile page (https://pimp_stack.meteor.com/pevprofile/PqZrKZYTfy7EHfQei) you see the page for a few seconds, then you're rerouted to the home page.
How to make stay on the profile page?
The routing code:
Router.route('/', function () {
// render the Home template with a custom data context
this.render('Home');
});
// Login / Logout
Router.route('/login');
Router.route('/logout', function () {
Meteor.logout();
this.render('Home');
});
// given a url like "/pevprofile/<id>
Router.route('/pevprofile/:_id', function () {
this.render('/pevprofile');
});
The template code:
<template name="pevprofile">
<div class="container-fluid">
<h2>Profile</h2>
{{> PevDetails}}
</div>
</template>
<template name="PevDetails">
PEV Details
</template>
You might want to re-check this. Its working fine on my end. I'm seeing the profile page no redirection after several seconds.
I have the following code on my Meteor app.
main.js (partial):
Template.login.events({
'click .login-button': function( e ) {
var serviceName = e.currentTarget.id.replace( 'login-buttons-', '' );
Accounts._loginButtonsSession.resetMessages();
var loginWithService = Meteor["loginWith" + (serviceName === 'meteor-developer' ? 'MeteorDeveloperAccount' : capitalize(serviceName))];
var options = {}; // use default scope unless specified
if (Accounts.ui._options.requestPermissions[serviceName])
options.requestPermissions = Accounts.ui._options.requestPermissions[serviceName];
if (Accounts.ui._options.requestOfflineToken[serviceName])
options.requestOfflineToken = Accounts.ui._options.requestOfflineToken[serviceName];
if (Accounts.ui._options.forceApprovalPrompt[serviceName])
options.forceApprovalPrompt = Accounts.ui._options.forceApprovalPrompt[serviceName];
loginWithService(options, function (err) {
console.log('user logged in');
});
}
})
And index.html (partial):
<div class="service-login-buttons">
<div class="login-text-and-button">
<div class="login-button single-login-button" id="login-buttons-twitter">
<div class="login-image" id="login-buttons-image-twitter"></div>
<span class="text-besides-image sign-in-text-twitter">Sign in to save your Pomodoros</span>
</div>
</div>
</div>
On the localhost it works just fine (twitter login).
I changed the twitter app settings to point to the correct *.meteor.com address, but the button doesn't do anything (no errors either).
Is there perhaps some configuration I need to redo?
The answer is simple: I had to configure the Twitter API on the deployed version as well.
Since I was using custom login buttons, I simply added
{{> loginButtons }}
To get the configuration dialog (where the API keys are added), then removed that part after configuration was done.
I'm using angular-ui's UI-Router in my project and on state change I want to check if the user has access to the page. If not I popup a login modal.
$rootScope.$on('$stateChangeStart', function(evt, toState) {
if (!authentication.authorize(toState.data.access)) {
evt.preventDefault();
$state.go('login', {}, {notify: false});
}
});
The login state, using a modal from UI-Bootstrap:
angular.module('components.security').config(function($stateProvider) {
$stateProvider.state('login', {
onEnter: function($state, $modal) {
$modal.open({
templateUrl: "/components/security/login.html",
controller: 'LoginCtrl as controller'
});
}
});
});
It's working perfectly fine on the first page load: The ui-view isn't rendered, the login modal pops up, and when the user logs in I call $urlRouter.sync(); and the view is loaded.
However, if you navigate from page to page this is what should happen:
User is on /pageA, clicks a link to /pageB (which he doesn't have access to)
Url changes to /pageB
The ui-view is not loaded, instead the login modal pops up
After login is complete the modal closes, ui-view loads
Instead what happens is this:
User is on /pageA, clicks a link to /pageB (which he doesn't have access to)
Url stays at /pageA
Login modal pops up
After login is complete the modal closes, but you're still on page A.
So what I'd really like is that the url actually changes to /pageB but the ui-view doesn't load until you call sync.
Have you considered saving toState and angular.copy(toParams) in your authentication $stateChangeStart listener, then triggering $state.go after the user has logged in?
The URL is not set to /pageB when the user transitions there because when you preventDefault, the transition is prevented (and the url is reset to pageA):
if ($rootScope.$broadcast('$stateChangeStart', to.self, toParams, from.self, fromParams).defaultPrevented) {
syncUrl();
return TransitionPrevented;
}
I have an application that have four modules in the front end, I'm trying to use as much as possible AngularJs in the front end I'm using an empty website asp.net project to host all the files and the REST serviceStack, my project have kind of the following structure:
~/ (web.config, global.asax and all the out of the box structure for an asp.net website)
- App <- AngularJs
- Users <- js controllers and views (static html files)
- Companies
- BackEnd
- Public
Index.html
IndexCtrl.js
App.js
- Content
- Js
I use angularjs service calls and the backend I'm using REST with servicestack.
the question is how can I restrict the access only to authenticated users to those static html files? let's say the ones that are inside inside Companies, Backend and users for example
Hi After doing some research this is the solution that worked for me:
Install razor markdown from nuget
Change the file structure to match the default behavior RM [Razor Markdown] to /views
Modify the web config following the approach described in this service stack example
Change all the static htmls files to .cshtml files, this by default creates the same route without the extension like /views/{Pagename} without the extension, I'm just using this approach to get the authorization logic simpler to implement (at least for me)
Update the service method with an authorize attribute you can find out more in this page
to illustrate a lit of bit more this is my route definition in so far:
'use strict';
angular.module('myApp', ['myApp.directives', 'myApp.services']).config(
['$routeProvider', function($routeProvider) {
$routeProvider.when('/Dashboard', {
controller: 'dashboardCtrl',
templateUrl: 'Views/dashboard'
}).when('/Payments', {
controller: 'paymentsCtrl',
templateUrl: 'Views/payments'
}).
when('/Login', {
controller: 'loginCtrl',
templateUrl: 'Views/login'
});
}]
);
Notice that the references are pointed now to the razor paths.
this is a small menu I've done in angular
<div class="container">
<div class="navbar" ng-controller="indexCtrl">
<div class="navbar-inner">
<a class="brand" href="#/">header menu</a>
<ul class="nav">
<li ng-class="{active: routeIs('/Dashboard')}">Dashboard</li>
<li ng-class="{active: routeIs('/Login')}">Login</li>
<li ng-class="{active: routeIs('/Payments')}">payments</li>
</ul>
</div>
</div>
<ng-view></ng-view>
</div>
let's say that the payments page is restricted, so every time I click on a the page I get a 401 unauthorized message.
Service host:
public override void Configure(Container container)
{
Plugins.Add(new AuthFeature(() => new AuthUserSession(), new IAuthProvider[] {
new FacebookAuthProvider(appSettings),
new TwitterAuthProvider(appSettings),
new BasicAuthProvider(appSettings),
new GoogleOpenIdOAuthProvider(appSettings),
new CredentialsAuthProvider()
})); //I'm going to support social auth as well.
Plugins.Add(new RegistrationFeature());
Routes.Add<UserRequest>("/Api/User/{Id}");
Routes.Add<LoginRequest>("/Api/User/login","POST");
Routes.Add<PaymentRequest>("/views/Payments");
}
I hope that helps
Create a CatchAllHander method to check for restricted routes and, for those static files that require authentication, return the ForbiddenFileHander if not authenticated, otherwise return null. Given an isAuthenticated method and restrictedDirs is defined somewhere - maybe your app or web config file, it can be as simple as:
appHost.CatchAllHandlers.Add((httpMethod, pathInfo, filePath) => {
if ( restrictedDirs.ContainsKey(pathInfo) && !isAuthenticated())
return new ForbiddenHttpHandler();
return null;
});
Why not use Forms Authentication? Simply add a few < location > tags to your web.config to allow/disallow different sections, you can even do it based on roles.