I think I've missed something but I have problem with implementing for example users activation process with use of links sent to users' e-mails.
I have page for signing up. After filling form request is sent to backend where some logic is done and also mail is sent to user's mailbox. In this mail there is activation link.
And here my problem starts - I want user to click that link and be moved to my page but I want to pass this token directly to backend to check its validity, activate account and at the end redirect user to login page.
How to implement that correctly?
That's my current routing configuration for AngularJS app:
$routeProvider.when('/', {
templateUrl: 'views/main.html',
controller: 'appController'
}).when('/login', {
templateUrl: 'views/login.html',
controller: 'userController'
}).when('/signup', {
templateUrl: 'views/signup.html',
controller: 'userController'
}).when('/activate/:activationToken', {
templateUrl: 'views/activate.html',
controller: 'userController'
}).otherwise({
redirectTo: '/'
});
That's my current backend routing configuration for node.js:
router.post('/users/login', userHelper.shouldNotBeLoggedIn, authentication.login);
router.post('/users/signup', userHelper.shouldNotBeLoggedIn, authentication.signup);
router.get('/users/logout', userHelper.shouldBeLoggedIn, authentication.logout);
router.get('/users/activate/:token', userHelper.shouldNotBeLoggedIn, authentication.activate);
Here is how I return data from backend to frontend:
if (err) {
logger.error(util.inspect(err));
res.status(403).json({message: err.code});
} else {
res.status(200).json({message: 'accountActivated'});
}
One way is to use a pure back-end URL + view that handles the account activation if the token is correct, then 302 redirect to a regular URL where the Angular app lives. If the token is incorrect, redirect to a URL that displays an error message.
Update:
In userController, when the URL matches the URL sent in the activation email (this may already exist at /activate/:activationToken), sent the token to your back-end like this:
// Make sure to inject $http and $routeParams in your controller.
$http.post('/users/activate/' + $routeParams.activationToken, {}).success(function(response) {
console.log('Yay');
}).error(function(response) {
console.error(response.data.message)
});
Related
Using AngularJS and Spring Security, when the authentication fails there is a redirect to the login page with error=true. I need to display this error on login page (AngularJS). I don't know how to do this.
Below is the declaration of authentication-failure-url in ssecurity-context.xml:
<security:form-login login-page="/login"
login-processing-url="/authenticate"
authentication-failure-url="/login?error=true"
username-parameter="username" password-parameter="password"/>
Could some please tell me how to retrieve the error and show on login page?
JS
angular.module('app.services')
.service('AuthenticateService',['$http','$location',function($http,$location)
{
return
{
login : function(uname, pword)
{
var data = "username="+uname+"&password="+pword;
return $http.post("authenticate",data,
{
headers: { "Content-Type": "application/x-www-form-urlencoded" } })
.then(function (response)
{
console.log(response.status);
},function(error)
{
console.log(error.status);
});
}
}
}]);
When your login attempt fails, you're redirecting to /login/error/true
Then, you should have on your angularJS a route to that URL with the partial HTML that you want to display, alongside with an angularJS controller which will handle or do the appropriate actions upon that URL.
For example, let's say that your route file will look this:
var myApp = angular.module("myApp", [ "ngRoute" ]);
myApp.config(function ($routeProvider) {
$routeProvider
.when("/login/error/:errValue",
{controller: "LoginController", templateUrl: "/partials/login.html"})
});
Now all you have to do is tho check on LoginController whether :errValue is true or false. and act upon that value.
with wrong credentials, I tried checking the value of errValue in the controller, but its says undefined
Below are the routes declared
$stateProvider
.state("/login",{ //this for login page
url:'/login',
templateUrl:'./resources/js/baseapp/ContactLogin/views/contactLogin.html',
controller:'contactLoginCtrl'
})
.state("/home",{
url:'/home', // this is for home page
templateUrl:'./resources/js/baseapp/ContactHome/views/ContactHome.html',
controller:'contactHomeCtrl'
})
.state("/login:error/:errValue",{ // this is for login error page
url:'/home',
templateUrl:'./resources/js/baseapp/ContactLogin/views/contactLogin.html',
controller:'contactLoginCtrl'
})
$urlRouterProvider.otherwise("/login");
So I needed to change my URL so that google analytics could track it. Google analytics wouldn't accept it with the "/#/" (hash) in the link. That said, I used Angular's locationProvider and revised my app routing with:
(function() {
'use strict';
angular
.module('mbapp')
.config(routerConfig);
/** #ngInject */
function routerConfig($stateProvider, $urlRouterProvider, $locationProvider) {
$stateProvider
.state('home', {
url: '/',
templateUrl: 'app/main/main.html',
controller: 'MainController',
controllerAs: 'main'
});
$stateProvider
.state('steps', {
url: '/steps',
templateUrl: 'app/steps/steps.html',
controller: 'StepsController',
controllerAs: 'steps'
});
// use the HTML5 History API
$locationProvider.html5Mode(true);
$urlRouterProvider.otherwise('/');
}
})();
My URL is fine and changes it to http://website.com/steps rather than http://website.com/#/steps. However, now, if a user refreshes (f5) the link it then throw a 404 error and not sure why. Additionally, it seems that somehow this gets injected as the URL when the refresh is called "http://website/steps#/steps".
Any ideas on this?
Thanks much.
The problem is probably on the server side. You have to configure your server so it responds to every request with your html file. For example in express:
var app = require('express')();
app.configure(function(){
// static - all our js, css, images, etc go into the assets path
app.use('/assets', express.static('/assets'));
app.get('/api/users/:id', function(req, res){
// return data for user....
});
// This route deals enables HTML5Mode by forwarding missing files to the index.html
app.all('/*', function(req, res) {
res.sendfile('index.html');
});
});
When you reload the page, the request goes to the server side of your application, and it tries to resolve the url but it probably can't, because those routes only exists on the client side of your application.
Also it is a good idea to prefix every server side route with an /api route prefix, so you can easily distinguish between client side and server side routes.
In a single page application using angular routing, how can I redirect a page after an api call. In my case, I want to redirect the user to the profile page after they have called the login api. So this is what I thought would work but it isn't.
On the client, main.js. I have the angular routing set up
app.config(function($routeProvider){ $routeProvider
//the home page display
.when('/', {
templateUrl: 'main.html',
controller: 'mainController'
})
.when('/login', {
templateUrl: 'login.html',
controller: 'loginController'
})
.when('/signUp', {
templateUrl: 'signUp.html',
controller: 'signUpController'
})
.when('/profile', {
templateUrl: 'profile.html',
//controller: 'mainController'
}); });
and from my controller I call the /login post api
app.controller('authController', function($scope, $http, $rootScope, $location){
$scope.user = {username: '', password: ''};
$scope.error_message = '';
$scope.login = function(){
$http.post('/login', $scope.user).success(function(data){
if(data.state == 'success'){
//set username authenticated property to true after successful log in
//I am only pasting some of my code here, more logic before controller
$rootScope.authenticated = true;
$rootScope.current_user = "james";
$location.path('/profile');
}
else{
$scope.error_message = data.message;
}
});
};
});
and here is my login api
router.post('/login', passport.authenticate('local-login', {
successRedirect : '/success', // redirect to the secure profile section
failureRedirect : '/failure', // redirect back to the signup page if there is an error
failureFlash : true // allow flash messages
}));
and when it succeeds, it calls success which sends back the data which should trigger the callback in $http.post and redirect the page through $location.path('/profile');. However, the callback isn't called and my page displays the user information from res.send
//sends successful login state back to angular
router.get('/success', function(req, res){
res.send({state: 'success', user: req.user ? req.user : null});
});
Am I on the right track? I am just following microsoft's tutorial https://www.microsoftvirtualacademy.com/en-us/training-courses/mean-stack-jump-start-8442 but their completed page on github doesn't even work so it doesn't help me debug this problem of mine.
Using successRedirect and failureRedirect in passport will redirect the client to the specified pages, which will prevent your client-side angularJS routing from taking place. The reason you're seeing the user info after logging in is because your client is being redirected to the /success page, rather than actually responding to the original request. The client then fetches the success page with a GET request, and the new GET request is then responded to with the user info.
I would suggest leaving the node.js redirects out when using AngularJS, since you probably want to handle redirection on the client side:
router.post('/login', passport.authenticate('local-login'), function(req, res){
res.send(req.user);
});
The inline function will never execute if the user is not authenticated. Instead, passport will respond directly with a 401 error status, with a body of "Unauthorized". Therefore the success state is not required. On the client side, you should use the .error() function to deal with 401 errors, rather than checking your state variable:
$http.post('/login', $scope.user).success(function(user){
$rootScope.authenticated = true;
$rootScope.current_user = "james";
$location.path('/profile');
})
.error(function(err){
$scope.error_message = err;
});
If you want to pass back a more specific reason as to why the request was unauthorized (which is not always a good idea), you can use flash messages, and issue another GET request from angular to get a more detailed authorization failure message.
You seem to have a slight impedance mismatch on what the front-end and back-end want to do here. Your AngularJS code expects to make a POST to the API endpoint and get back a 200 (success) along with some JSON data which tells it about the success or failure of the login attempt.
The back-end, thinks it's going to receive a POST and then redirect the caller to a new location. At least that's the way I'm reading it. It's not simply sending back some data with an HTTP response code of 200 or an error code.
I think you want to tweak the back-end code to simply return data to the front-end to get the result you expect.
So far I haven't seen success in making Ajax calls to API redirecting to a page. We have a similar situation where API call may result in redirecting to a error page. We wanted to handle that in the server rather than asking UI (Angular) to do it. But it's just frustrating to see none of the methods of redirect like res.redirect are working.
Our scenario is Angular makes a API call through Ajax and API running on Node.js should redirect to a html page.
I'm implementing some simple client-side authentication logic in Angular.js. The pages involved are:
/account#/login (public)
/account (require login)
/account#/settings (require login)
When a user is not logged in and try to visit either /account or /account/#/settings, the app is supposed to redirect to the login page.
I have the following routes configured using ui-router:
$stateProvider
.state('overview', {
url: '/',
restricted: true
})
.state('settings', {
url: '/settings',
restricted: true
})
.state('login', {
url: '/login',
restricted: false
})
and upon URL change, I check if the upcoming page is a restricted page and whether the current user is not logged in. If so redirect to login.
app.run(function($rootScope, $location, $state, auth) {
$rootScope.$on('$stateChangeStart', function(event, next) {
if (next.restricted && !auth.isLoggedIn()) {
event.preventDefault();
$state.go('login');
}
});
});
auth is just a service that checks the login status and returns either true (logged in) or false (not logged in).
Here's my question:
Even though this (kind of) works, I see a page flickering issue when trying to visit a restricted page while not logged in. The page flashes the contents of the restricted page quickly before redirecting me to the login page.
I did a little bit researching online and some people have mentioned the potential solution could be using resolve when defining my states, since the page won't load unless it resolves successfully. However, when I try to add
resolve: {
load: function(auth) {
return auth.isLoggedIn();
}
}
It didn't work. What am I missing? Is using resolve the way to go?
The way you are currently doing it will check if the user is logged in or not and set load to true or false. Also controller gets instantiated before load is resolved which is why you see the flickering. You need to achieve two things here:
Make sure that load is resolved before the controller is instantiated.
If user is not logged in, redirect the user to the login page.
For the first part we need to use a promise as it will be resolved and converted to value before controller is instantiated. This is what the documentation says:
If any of these dependencies are promises, they will be resolved and
converted to a value before the controller is instantiated and the
$stateChangeSuccess event is fired.
Following code can do that for us:
var isLoggedin = ['auth', '$q',
function(auth, $q) {
var deferred = $q.defer();
//assuming auth.isLoggedIn returns a promise
var loginPromise = auth.isLoggedIn();
loginPromise.then(
function(response) {
deferred.resolve(response);
},
function(error) {
deferred.reject('Not logged in');
});
return deferred.promise;
}
];
And states will use isLoggedin:
$stateProvider
.state('overview', {
url: '/',
resolve: {
loggedin: isLoggedin
}
})
.state('settings', {
url: '/settings',
resolve: {
loggedin: isLoggedin
}
})
.state('login', {
url: '/login'
})
For the second problem, that is redirecting the user to login page, you can listen to $stateChangeError event which is fired in case the state is not resolved, and use $state.go to redirect the user.
I'm starting to experiment with ExpressJS + AngularJS and I've run into a stressful situation.
My goal is to have one login page and one dashboard page, and using Passport + MongoDB I'll authenticate a user in and, if credentials are correct, redirect him to the dashboard page.
I've started with the angular-express-seed and modified it so I ended up with the previously mentioned views:
$routeProvider.
when('/login', {
templateUrl: 'partials/login',
controller: 'LoginCtrl'
}).
when('/dashboard', {
templateUrl: 'partials/dashboard',
controller: 'DashboardCtrl'
}).
otherwise({
redirectTo: '/login'
});
I'm serving each partial view with:
app.get('/partials/:name', routes.partials);
exports.partials = function (req, res) {
var name = req.params.name;
res.render('partials/' + name);
};
The login system is working correctly except for the part that I can access the dashboard directly (without loging in at all).
So I guess I need to figure out if the user if authenticated, before granting him access to the dashboard (or any other area for that matter).
I found this that does the trick:
function ensureAuthenticated(req, res, next) {
if (req.isAuthenticated()) { return next(); }
res.redirect('/login');
}
and I would call it on every GET to a partial
app.get('/partials/:name', ensureAuthenticated, routes.partials);
The big problem here is that the login view is also a partial, and I don't want ensureAuthenticated to run when that's the view being requested.
Currently calling ensureAuthenticated on every partial request crashes any browser, no errors whatsoever.
What I've tried so far (and did not work):
function ensureAuthenticated(req, res, next) {
if ( req.route.params.name !== 'login' && req.isAuthenticated() ) { return next(); }
res.redirect('/login');
}
Also tried calling the login view with its own app.GET and not using ensureAuthenticated but somehow Angular flips off and does not even load.
Any ideas on how to resolve this ?
Thanks in advance.
It's unclear to me if the first (unmodified) version of ensureAuthenticated crashes your browsers, or the last version (in which you check for /partials/login), although no matter what, it shouldn't happen (if by 'crashing' you mean 'stalls', that's a tell-tale sign of Express not sending back a response)
But as an alternative, try this:
app.get('/partials/login', routes.partials);
app.get('/partials/:name', ensureAuthenticated, routes.partials);
Your routes.partials does have to check explicitly if it's called for the login route, because in that case the name parameter won't exist in req.params.
Just to make sure: do you have a server-side handler for /login as well? Otherwise the res.redirect('/login') isn't going to work.
I don't think that server side redirecting on XHR request is a good practice. XHR request should return 401 if the user is not authorized and the client side (Angular) should take the action as displaying notification message and redirecting user to the login partial. Look at this project for handling such requests. There are demo and blog post.