Let's say I send someone by email a link to a section of my web which requires the user to be logged in. For ex:
https://www.web.com/serverroute/angularapp#/clientrouteone
When the user that has not logged in clicks on the link, the client site only knows about "web.com/serverroute/" and redirects to login form.
Now on successful login how to go around it if I want redirecting to the original full url requested?
Save the prior url to $rootScope and redirect there after login
Or better with an interceptor in your main 'app.config':
$httpProvider.interceptors.push(['$q', '$location', '$rootScope', '$window', function ($q, $location, $rootScope, $window) {
return {
'request': function () {
'do your magic'
var magic = $window.history.back(); //just an example dont think this will work for you
return magic;
}
}
}]):
your login process must handle it. somewhere in your filter chain, where you redirect user to login page, you need to keep full url, so that it looks like "https://my.cool.site/login#original/url". then, on login form you pick it as $location.path() and redirect accordingly.
Related
A similar-ish question here (but not applicable).
In my SPA I'm using PassportJS to handle authentication. I have many routes which require the user to be logged in. I route like this:
// ... other routing stuff
when('/insights', {
templateUrl: 'partials/insights',
controller: 'insightsCtrl',
resolve: {
loggedin: checkLoggedin
}
})
var checkLoggedin = function($q, $timeout, $http, $location, $rootScope){
var deferred = $q.defer();
$http.get('/loggedin').success(function(user){
if (user !== '0') {
$rootScope.user = user;
deferred.resolve();
}
else {
deferred.reject();
$location.url('/login');
}
});
return deferred.promise;
};
My route to check if logged in:
app.get('/loggedin', function(req, res) {
res.send(req.isAuthenticated() ? req.user : '0');
});
If they're not logged in, they're redirected to /login. Once that is successfully done though, I'd like to go back to the original page they tried to request.
Is there a built-in way to store the route the user is trying to access so that I can redirect to it once they're logged in? I can do this with a cookie I suppose, but it feels a little clunky to do so...
There is no built in approach besides using the custom callback that PassportJS provides, but if you are writing this for a SPA, you could leverage $cacheFactory and create a service/factory that stores the last visit page of that user.
Limitations for $cacheFactory are the same as local storage which are that they are domain specific, so if you change domains, your local storage will not be the same cross-domain.
You can use a custom callback as explained in passportjs documentation.
With custom callback, you can send the current query string to your server, and get a redirect to same query string after successful login.
Another possibility is to use localStorage to save current state and take it after login.
You have many modules to use localStorage with angular like angular-local-storage.
Some of my AngularJS routes are to pages which require the user to be authenticated with my API. In those cases, I'd like the user to be redirected to the login page so they can authenticate. For example, if a guest accesses /account/settings, they should be redirected to the login form.
From brainstorming I came up with listening for the $locationChangeStart event and if it's a location which requires authentication then redirect the user to the login form. I can do that simple enough in my applications run() event:
.run(['$rootScope', function($rootScope) {
$rootScope.$on('$locationChangeStart', function(event) {
// Decide if this location required an authenticated user and redirect appropriately
});
}]);
The next step is keeping a list of all my applications routes that require authentication, so I tried adding them as parameters to my $routeProvider:
$routeProvider.when('/account/settings', {templateUrl: '/partials/account/settings.html', controller: 'AccountSettingCtrl', requiresAuthentication: true});
But I don't see any way to get the requiresAuthentication key from within the $locationChangeStart event.
Am I overthinking this? I tried to find a way for Angular to do this natively but couldn't find anything.
What I did is implement an angularjs interceptor which handles http request errors. Basically, what I do is when I get the 401 (unauthorized) from my backend, I save the current url and redirect the user to a login page. When the user does login successfully, I retrieve the saved url to set the path with $location.
app.config(function ($routeProvider, $locationProvider, $httpProvider) {
/* Global interceptor for 401 - not authorized */
var interceptor = ['$location', '$q', 'authorizationService', function ($location, $q, authorizationService) {
function success(response) {
return response;
}
function error(response) {
if (response.status === 401) {
authorizationService.saveUrl($location.path());
$location.path('/logon');
return $q.reject(response);
}
else {
return $q.reject(response);
}
}
return function (promise) {
return promise.then(success, error);
};
} ];
});
In the logon controller (on successful logon) I set the location like this:
$location.path(authorizationService.getUrl());
I use angular-http-auth in my project. If you have a backend is easier to delegate on it to handle the authentication, showing a login form automatically in each 401 response, than duplicate the authentication mapping both on client and server.
I am moving all http calls in my controllers to services, using $q to promise... Everything seems to work till I refresh the page then the content disappears.
Setting: In my login service, I have a function which does an $http.post, it takes username and password. Then the response data is put in the promise.
Problem: Though the service works, It is trying resend the http call when the page is refreshed. The call then fails because the are no login details.
Question: How do I keep/store the original data so when the page is refreshed (and user is still signed in) and when I use the service in other controllers it does not do the http call it just returns the data?
Service:
var app = angular.module('myApp', ['ngRoute']);
app.factory('loginService', function($http, $q) {
var deferResults = $q.defer();
return {
appContent: function(login_username, login_password) {
$http.post('/login',{username: login_username, password: login_password}).success(function(data) {
deferResults.resolve(myData);
});
return deferResults.promise;
}
});
Controllers:
function loginPageCtrl($scope, loginService) {
$scope.login = function (login_username, login_password, login_rememberMe) {
loginService.appContent(login_username, login_username).then(function (data) {
$scope.pageOneContent = data;
});
};
};
function pageTwoCtrl($scope, loginService) {
// Use the same data when page is refreshed
// without having to pass-in login details
loginService.appContent().then(function (data) {
$scope.pageTwoContent = data;
});
};
Of course that happens, your app loses all its state when the page is refreshed. You need to persist that username and password somewhere, like a cookie, and then retrieve that then make your second $http call, even better just keep a token in a cookie and use that for authentication. Use angular's $cookieStore to persist the login details:
var userData={username:login_username,password:login_password};
$cookieStore.put('user_data', userData);
and then when the controller loads, check if the cookie exists:
var userData = $cookieStore.get('user_data');
return userData
Check the source in this answer to see this in action.
To recap, every time the controller loads check the cookie, if its undefined then redirect the user to a login page, if it's not then extract the toke/username/password and make your $http call.
I'm working with a restful API that when authenticating a user successfully, the request returns a token. The token is then added as a header on every request like this:
Authorization: Bearer <token>
I struggled to find a good way to do authentication without a lot of code bloat.
I came up with a good solution using HTML5 sessionStorage. Here's a simple example:
// Main module declaration
var myapp = angular.module('myapp', []);
// Set some actions to be performed when running the app
myapp.run(['$location', '$rootScope',
function($location, $rootScope) {
// Register listener to watch route changes.We use this to make
// sure a user is logged in when they try to retrieve data
$rootScope.$on("$routeChangeStart", function(event, next, current) {
// If there is no token, that means the user is not logged in
if (sessionStorage.getItem("token") === null) {
// Redirect to login page
window.location.href = "login.html";
}
});
}]);
// A controller for the login page
myapp.controller('LoginController', ['$scope', '$http',
function($scope, $http) {
// If a user has check the "remember me" box previously and the email/pass
// is in localStorage, set the email/password
// Login method when the form is submitted
$scope.login = function() {
// Authenticate the user - send a restful post request to the server
// and if the user is authenticated successfully, a token is returned
$http.post('http://example.com/login', $scope.user)
.success(function(response) {
// Set a sessionStorage item we're calling "token"
sessionStorage.setItem("token", response.token);
// Redirect to wherever you want to
window.location = 'index.html';
});
};
}]);
I'm trying to sort out a login system using cookies so that the user's login will persist after leaving the app. I am able to set the cookie correctly, but I am unclear as to how I can use the stored cookie to limit the users access to the login screen if they are already logged in.
I think the best way to do this would be within the routes. This is what my file currently looks like:
var routes = angular.module('we365', ['rcForm', 'ngCookie', 'ngCookies']);
routes.config(function ($routeProvider) {
$routeProvider
.when('/login', {
templateUrl: 'views/login.html',
controller: 'loginCtrl'
})
.when('/', {// get digest view
templateUrl: 'views/getDigest.html',
controller: 'GetDigestCtrl'
})
.when('/artifact/:artifact_id', {// single artifact view
templateUrl: 'views/artifact.html',
controller: 'artifactCtrl'
})
.otherwise({
redirectTo: '/'
});
});
Also, i'd like to hide the 'login' button from the parent view so that the user isn't able to click on it. This is what the view looks like now:
<div class="container">
<div class="page-header col col-lg-12">
<h1>Welcome!</h1>
Login
Load Digest Data
</div>
</div>
There are many ways, I have two that are my favorite:
1) Check on route change
angular.module('MyApp', [])
.run(function($rootScope, myLoginService) {
$rootScope.$on('$routeChangeStart', function () {
if (!myLoginService.isUserLoggedIn()) {
$location.path('/login');
}
});
You can replace the isUserLogged for a mapper service that receives where the user wants to go; if the user has the proper privileges (either stored within a cookie or local storage in the form of a token), then let the route succeed. Otherwise, show an error, or route him to wherever you want. In my case, the myLoginService checks for a localStorage.
2) Any data request to a server has a token included to headers; failed requests (401) are intercepted and stored, while the user is redirected
This one is more for CRUD apps and not necessarily for routing, but the concept is simple: a user A can perform N actions as long as he/she has the privileges to do so; if he tries to perform an action (or M actions) that he's not allowed, then the request is intercepted and queued in order to ask him to authenticate with a user that CAN do those actions
.factory('securityInterceptor', ['$injector', 'securityRetryQueue', function($injector, queue) {
return function(promise) {
// Intercept failed requests
return promise.then(null, function(originalResponse) {
if(originalResponse.status === 401) {
// The request bounced because it was not authorized - add a new request to the retry queue
promise = queue.pushRetryFn('unauthorized-server', function retryRequest() {
// We must use $injector to get the $http service to prevent circular dependency
return $injector.get('$http')(originalResponse.config);
});
}
return promise;
});
};
}]);
Again, this is for more "data like" requests and not necessarily for routing. This was stolen from the AngularJS sample app. You should check it for more examples.