I have built a simple application in Angular consuming a simple API I created myself using Laravel. The application is hosted here. The API is hosted here. Now I can log in to the application at which point the API returns a simple auth_token which is sent as the URL parameter in every subsequent request that is sent to the server.
There is only one user in the system:
Email: admin#admin.com
Password: admin12345
You can log into the application using these credentials at which point the application will set a cookie using the $cookieStore service and will use the token in this cookie for every subsequent request. After using the application, a user can log out from the application, where a DELETE request is sent to the server and on the success method, the cookie is deleted from the browser.
Unfortunately there is some issue with the code I suppose. The DELETE request is working as expected and it deletes the auth_token on the server and returns 200 OK. But the success method is not called. I am not sure what I am doing wrong. It might be just a syntax problem.
app.js
function AppCtrl ($scope, $cookieStore, $location, Auth) {
$scope.setActive = function (type) {
$scope.destinationsActive = '';
$scope.flightsActive = '';
$scope.reservationsActive = '';
$scope[type + 'Active'] = 'active';
};
$scope.authenticate = function (credentials) {
Auth.save(credentials, function(data){
$cookieStore.put('auth_token', data.auth_token);
$scope.isLoggedIn = true;
$location.path('destinations');
$scope.message = null;
}, function(data){
$scope.message = "Email/Password combination incorrect!";
});
};
$scope.logout = function () {
//var auth_token = $cookieStore.get('auth_token');
Auth.delete({
'auth_token': $cookieStore.get('auth_token')
}, function(data){
$scope.isLoggedIn = false;
$cookieStore.remove('auth_token');
});
};
if($cookieStore.get('auth_token')){
$scope.isLoggedIn = true;
}else{
$scope.isLoggedIn = false;
}
}
The logout function is called when the log out button is pressed. What am I doing wrong here?
Note: The application is not working on Chrome for some reason (Use Firefox). If you can shed some light on that, it would be very helpful.
Both the repositories are public if you wish to have a look:
AngulAir Application: http://gitlab.learningtechasia.com:8901/rohan0793/angulair.git
AngulAirAPI: http://gitlab.learningtechasia.com:8901/rohan0793/angulairapi.git
Here is your solution
$scope.logout = function () {
//var auth_token = $cookieStore.get('auth_token');
Auth.delete(
{'auth_token': $cookieStore.get('auth_token')}, // parameters
{},//postData, which you don't need for this
function(data){
$scope.isLoggedIn = false;
$cookieStore.remove('auth_token');
},
// error callback
function (httpResponse) {
// do what you want for error handling here
}
);
};
Note:-> (Below points solved the problem)
Only the 2nd option(postdata) in $resource.delete API was missing. We should give it as a blank {} if it is not required for API.
And delete method should return 204 Status Code in order to execute success callback.
Related
I'm dealing with tokens, and with every HTTP request, the token will be added to the header so the server can tell if user is logged in or not.
Because of that, I can't redirect to the specific URL and check for tokens because the token wont be added to the header.
Is it possible to load in a new HTML page from an http request? Every time the server responds, I get the code of the HTML page. I think angular doesn't reload the new incoming page.
Edit: Here is some code
Code that adds to every http request
// ===================================================
// application configuration to integrate token into requests
// ===================================================
.factory('AuthInterceptor', function($q, $location, AuthToken) {
var interceptorFactory = {};
// this will happen on all HTTP requests
interceptorFactory.request = function(config) {
// grab the token
var token = AuthToken.getToken();
// if the token exists, add it to the header as x-access-token
if (token) {
config.headers['x-access-token'] = token;
}
return config;
};
// happens on response errors
interceptorFactory.responseError = function(response) {
// if our server returns a 403 forbidden response
if (response.status == 403) {
AuthToken.setToken();
$location.path('/login');
}
// return the errors from the server as a promise
return $q.reject(response);
};
return interceptorFactory;
});
I'm using ui-routing. I have a front-end and back-end of the site. So when a user logs in from the front end, the front-end.html goes away, and back-end.html gets loaded. But angular just reads the back-end.html code.
// function to handle login form
vm.doLogin = function() {
vm.processing = true;
//clear the error
vm.error = '';
Auth.login(vm.loginData.email, vm.loginData.password)
.success(function (data) {
vm.processing = false;
// if a user successfully logs in, redirect to main application
if (data.success)
return $http.get('/account');
//Here is where a user logs in and i redirect them to the backend of the site. But the response is the HTML code of the page. I want that page to load.
else
vm.error = data.message;
});
};
You are heading for the wrong direction.
You should read this:
http://blog.thesparktree.com/post/75952317665/angularjs-interceptors-globally-handle-401-and
If the user is on a page for a long time and the session ends, if they proceed to make an AJAX call after the session is already expired.. instead of receiving the JSON object, it instead receives the HTML of the login page.
Ideally I'm trying to make it so that it will redirect to a log in page.
Is there any way i can detect this?
I already have an ActionFilterAttribute that works for non-AJAX calls like so:
public class VerifySessionAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
var userId = filterContext.HttpContext.Session["UserId"];
var userName = filterContext.HttpContext.Session["UserName"];
if (userId == null || userName == null)
{
filterContext.Result = new RedirectResult(string.Format("/Account/Login"));
return;
}
base.OnActionExecuting(filterContext);
}
}
But that doesn't get hit for the scenario above during AJAX calls.
I've also tried an Interceptor.. something like this:
app.factory('httpAuthInterceptor', function ($q) {
return {
'responseError': function (response) {
// NOTE: detect error because of unauthenticated user
if ([401, 403].indexOf(response.status) >= 0) {
// redirecting to login page
//$state.go('home');
$window.location.href = '/Account/Login';
return response;
} else {
return $q.reject(rejection);
}
}
};
})
app.config(function ($httpProvider) {
$httpProvider.interceptors.push('httpAuthInterceptor');
});
But in the same scenario it doesn't seem to hit there as well during the expired session / AJAX call
Is there anything I can do to detect this? When the session is expired I just want to redirect to the login page.. Thanks for any help
EDIT: here's how I make my calls
app.factory('HeadlinesFactory', ['$http', function ($http) {
var HeadlinesFactory = {};
HeadlinesFactory.getShowsForClient = function (clientId) {
return $http({
method: 'POST',
url: '/Show/GetShowsForClient',
data: { clientId: JSON.stringify(clientId) }
});
};
//etc
EDIT2: how all my controllers look like. Except my Account Controller where I put the VerifySession in front of everything except the Login page to prevent loop redirects:
[Authorize]
[CustomFilters.VerifySession]
public class ShowController : Controller
{ ... }
Ajax requests will not process redirect requests for security reasons. In addition, since you are returning a redirect result, a 401/403 status code is not thrown but rather a 302 is returned.
What you could do is expand your filter to conditionalize logic based on whether or not the request is an ajax request. In addition, based on your comments, it seems like creating a new Authorize attribute instead would be the right way to go since that way you can simply replace the default Authorize attribute with your own logic.
public class VerifySessionAttribute : AuthorizeAttribute
{
protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
if (!filterContext.HttpContext.User.Identity.IsAuthenticated)
{
if (filterContext.HttpContext.Request.IsAjaxRequest())
{
filterContext.HttpContext.Response.StatusCode = 401;
filterContext.HttpContext.Response.SuppressFormsAuthenticationRedirect =
true;
}
else
{
filterContext.Result = new RedirectResult(string.Format("/Account/Login"));
return;
}
}
}
}
This would allow your Angular interceptor to pick up the request and handle it appropriately.
Since IsAjaxRequest looks explicitly for the "X-Requested-With": "XMLHttpRequest" and AngularJS no longer provides that header with Ajax requests, you can add a configuration to the $httpProvider to always include the header.
app.config(['$httpProvider', function ($httpProvider) {
$httpProvider.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';
}]);
Please find below the angularjs factory method to call http request:
var HttpService = angular.module("HttpService",[]);
HttpService.factory("HttpServiceFactory",['$http', '$q', '$location', '$rootScope' ,function($http, $q, $location, $rootScope){
return {
getData: function(url, headers, bOnErrorRedirect, bShowInPageError, params){
var headerParam = {'Accept':'application/json'};
if(headers !== undefined || headers !== null){
headerParam = $.extend(headerParam, headers);
}
var updatedParams = {'TimeStamp':new Date().getTime()};
updatedParams = $.extend(params, updatedParams);
var deferred = $q.defer();
$http.get(url,{
headers: headerParam,
params : updatedParams
}).success(function(successResponse){
if(successResponse){
var responseJSON = angular.fromJson(successResponse);
if(responseJSON && responseJSON.messages && responseJSON.messages.length){
//Process Error
}else{
deferred.resolve(successResponse);
}
}else{
deferred.resolve(successResponse);
}
}).error(function(errorResponse , status){
//Process Error
console.error("status here:: "+status);
});
return deferred.promise;
}
}
}]);
And I am calling this method in controller with all required dependencies as below:
HttpServiceFactory.getData(args.sURL,null,false,true,args.oQueryParams).then(function(response){
scope.bDataLoading = false;
// process data
})
.catch(function(oResponse) {
scope.bDataLoading = false;
scope.bDisplayError = true;
// process error
});
Here everything works fine. But the issue is when I've multiple http calls on a page, the UI freezes and does not allow to interact till the request has been processed.
For example, on a page I am displaying 2 angular-ui-grid based on user's selected criteria by input box and calendar control. In such case, the UI freezes until both grids have been displayed or error message has been displayed.
During http service call, user can not do anything but simply wait to finish the request.
How do I resolve the issue of UI freezing ? Is it a true async behavior ? If not, what am I missing to achieve correct async behavior ?
I believe this has to do with the way JS Closures work, but I am not totally sure. I am using an AngularJS service to manage the life-cycle of a model that is used within my application. The service uses a combination of fetch() and save() to run GET and POST requests get and update the model from an API. After I fetch() the object, I attempt to place the result into an object sitting in the service where it can be fetched later on. My problem, is that after a successful save(), I take the result and place it into the same object to essentially "update" the object that is on the client with the correct object that is on the server (hence the result of the POST is just an echo of the payload if all is successful).
The problem is that my object is not persisting, and all subsequent calls to save() contain a "stale" object that is not completely updated.
Here is my service:
app.factory('MailboxSubscription', function (API, $q, $stateParams, $rootScope) {
var Subscription = null; //THIS IS MODEL THAT I TRY TO UPDATE CONSTANTLY
var isBusy = false;
var service = {};
var deferred;
var defaultFailure = function(res){
}
service.fetch = function (success, force, failure) {
if(!failure){ failure = defaultFailure;}
if(isBusy){
deferred.promise.then(success, failure);
return deferred.promise;
}else{
deferred = $q.defer();
}
if(Subscription && !force){ // ONCE THE MODEL HAS BEEN FETCHED ONCE, IT STAYS IN MEMORY AND ALL SUBSEQUENT CALLS WILL SKIP THE API CALL AND JUST RETURN THIS OBJECT
deferred.resolve(Subscription);
}else{
//Make the API call to get the data
//Make the API call to get the data
if(typeof(success) === 'function'){
var ServiceId = $stateParams.serviceId;
}else{
var ServiceId = success;
}
isBusy = true;
API.Backups.O365.Exchange.get({id : ServiceId || $stateParams.serviceId}, function(res){
isBusy = false;
if(res.success){
Subscription = res.result; // ON A FIRST-TIME FETCH, THIS API CALL IS USED TO GET THE MODEL
deferred.resolve(Subscription);
}else{
deferred.reject(res);
}
}, function(res){
isBusy = false;
deferred.reject(res);
});
}
deferred.promise.then(success, failure);
return deferred.promise;
}
service.save = function(success, failure){
if(!failure){ failure = function(){};}
if(!success){ success = function(){};}
var deferred = $q.defer();
API.Backups.O365.Exchange.update({id :$rootScope.ServiceId || $stateParams.serviceId}, Subscription, function(res){
if(res.success){
Subscription = res.result; // AFTER AN UPDATE IS MADE AND THE OBJECT IS SAVED, I TRY TO SET THE RESULT TO Subscription.
deferred.resolve(res);
}else{
deferred.reject(res);
}
}, function(res){
deferred.reject(res);
});
deferred.promise.then(success, failure);
return deferred.promise;
}
service.get = function(){
return Subscription;
}
return service;
});
So the problem appears to stem from trying to use Subscription as a centralized resource for storing the model, but the model is not updating correctly.
If you are looking to have that Subscription model updated throughout the service, I'd suggested when you call MailboxSubscription.fetch() and MailboxSubscription.save()in your controller, you use MailboxSubscription.get() in the .then() method of your calls.
// get initial value of Subscription model
$scope.Subscription = MailboxSubscription.get();
// let's fetch
MailboxSubscription.fetch().then(
// callback
function() {
// let's get the updated model
$scope.Subscription = MailboxSubscription.get();
},
// errback
function() {
// handle error
}
);
// let's save
MailboxSubscription.save().then(
// callback
function() {
// let's get the updated model
$scope.Subscription = MailboxSubscription.get();
},
// errback
function() {
// handle error
}
);
Also, I've created a working jsfiddle simplifying your use case. It works fine. Maybe there is something that can be gleamed from that (I am using $timeout to spoof your API calls).
I'm implementing a server with WCF and trying to reach its rest services using a client developed with Angular.
Here is the service :
public interface IConnexion
{
[OperationContract]
[WebGet(UriTemplate = "Connexion/{login}/{mdp}", ResponseFormat = WebMessageFormat.Json)]
Utilisateur Connexion(string login, string mdp);
[OperationContract]
[WebInvoke(UriTemplate = "Deconnexion/{id_user}", RequestFormat = WebMessageFormat.Json)]
void Deconnexion(string id_user);
}
And my function trying to reach the service :
app.service('serverConnexionServices', function(userService, serverConfigService) {
this.Connexion = function(login, pass)
{
var uri = this.getServerUri() + "Connexion/"+login+"/"+CryptoJS.MD5(pass);
var promises = $http.get(uri)
.success(function(data, status, headers, config) {
// this callback will be called asynchronously
// when the response is available
return data;
}).
error(function(data, status, headers, config) {
// called asynchronously if an error occurs
// or server returns response with an error status.
return "";
});
return promises;
};
});
my form's controller:
app.directive('loginContent', function(userService, serverConnexionServices){
return{
restrict : 'E',
templateUrl : "includes/home/login-content.html",
controllerAs: "loginController",
controller: function($scope, userService, serverConnexionServices) {
var IsSubmitEnabled = true;
this.errors = "";
this.validateLogin = function(user) { // Calling the service
this.IsSubmitEnabled = false; // To avoid more than one click while checking on the server
var retour = serverConnexionServices.TryConnection(user).then(function(promise) {
alert('promise:'+retour);
if(promise.length > 0)
{ // There is an error so we show a message to the user
this.promise= retour;
}
this.IsSubmitEnabled = true;
user = {};
});
};
}
}
});
But when I try to open my client the alert box appears before the breakpoint is raised in Visual Studio (I put the breakpoint on the first line of my "Connexion" function. So it means the server has not answered yet to the request but the $http.get() has already sent its answer. So the result object doesn't contain what the server is sending. According to Angular's documentation, the response will be sent asynchronously. But How can I force my request to wait the answer ?
My controller now gets the answer from the server but it doesn't update the view, and if I try to call manually $scope.$apply() I get the error "$digest already in progress". How can I force my view to refresh itself ? (I noticed that if I put this.errors = "aaa"; once the "then" is closed, my view is refreshed and whos "aaa", I've also checked that errors.length > 0 with an alert message)
My view :
<div ng-show="loginController.errors.length > 0" class="alert alert-danger" role="alert" style="display: inline-block;"><span class="glyphicon glyphicon-exclamation-sign" aria-hidden="true"></span>{{loginController.errors}}</div>
EDIT: I can now reach my breakpoint in the implementation of Connexion in Visual Studio but still the answer from the server is not what is expected
EDIT2 : I've added my controller which needs the refresh.
EDIT3 : Ok, I updated my code, I needed to put a "then" function on my controller's call and my controller now get the answer. However my view doesn't update itself on errors. It should set the div visibility to true but the async call seems to block it. I've tried to use apply() and digest() without success. Could you help me on this ?