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
Related
I'm learning the basics of Node.js + MongoDB and some other related tools, but I'm stuck in a simple login screen I made, the goal is, once the right credentials are given, to redirect to index.html which only has a text "welcome!". I've taken a look at different places (official API, tutorials, this page, etc.) but can't see my error yet.
This is my server:
var http = require('http');
var hostname = 'localhost';
var port = 3000;
var mongojs = require("mongojs");
var express = require("express");
const HOME_URL = "/";
const LOGIN_URL = "/login";
const LOGIN_ACTION_URL = "/doLogin";
const INDEX_URL = "/index";
var app = express();
var db = mongojs("gus:1234#127.0.0.1:27017/awesomeapp");
app.set("views", __dirname + "/public/views");
app.engine('html', require('ejs').renderFile);
app.get(HOME_URL, function(request, response) {
response.redirect(LOGIN_URL);
});
app.get(LOGIN_URL, function(request, response) {
response.render("login.html");
});
app.get(INDEX_URL, function(request, response) {
response.render("index.html");
});
app.get(LOGIN_ACTION_URL, function(request, response) {
db.users.find({
user: request.query.user,
pass: request.query.pass
}, function(err, doc) {
if(err) {
console.log(JSON.stringify(err));
response.end("An error ocurred");
return;
}
if(doc.length == 0) {
console.log("invalid_credentials");
response.end("invalid_credentials");
return;
}
console.log("user found: " + JSON.stringify(doc));
// This is my problem:
response.redirect(INDEX_URL);
});
});
app.listen(port);
and this is done in the login view:
$.ajax({
url: "/doLogin",
type: "GET",
data: {
user: $("#user").val().trim(),
pass: $("#pass").val()
}
}).done(function(data, textStatus, jqXHR) {
if(data == "invalid_credentials") {
$("#alert-wrong-credentials").show(100);
} else if(data == "ok") {
$("#alert-wrong-credentials").hide();
}
}).fail(function(jqXHR, textStatus, errorThrown) {
console.log(errorThrown);
});
I can successfully return the error string "invalid_credentials" when trying non-existing credentials, and I can see the user data when I enter the right ones:
user found: [{"_id":"58af514cb63980d2e8a51fed","user":"gus","pass":"123"}]
but I'm unable to redirect to the index.html page.
You should handle redirect on the client side once the login is complete. Redirect would work if you're actually visiting the LOGIN_ACTION_URL.
if(data == "invalid_credentials") {
$("#alert-wrong-credentials").show(100);
} else if(data == "ok") {
// Redirect
window.location.href = "index_url";
}
Also, there is a similar question here Node.js Page Redirect on AJAX login? With a function call after redirect? .
Ajax calls don't affect the current browser page. You send a request to a server. You get a response. If you want the current browser page to change, then you have to write Javascript that looks at the ajax response and then changes the current browser page by setting:
window.location = someURL;
So, doing a res.redirect() in response to an ajax call will just return a 302 status to the ajax call. That's it. If you want the ajax call to use that as an indicator to change the current browser page, then you have to write code to look for the 302 response and grab the right header from the response and then change window.location. There is no automated way to do that from an Ajax call.
When the browser is requesting a web page for a URL and it gets a 302 when loading a web page, then it will follow the redirect at that point. But, not for Ajax calls. An ajax call just gets you the response, whatever it is and it's up to your code that processes the response to actually do something with it.
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 have auth interceptor and JWT authentication.
When token expires im getting 401 and i need to send request to refresh token.
After the token is refreshed i want to resend the user request but it is not working.
For example, to go to my account tab, user need to have valid token,he click on my account and get 401 and then the refresh token is sent and the valid token is saved in the local storage.
Now what i want that after this the interceptor auto make the action of the user, in this case to go to my account but my way doesnt working, how can i do this?
The interceptor:
'responseError': function(rejection) {
var $state = $injector.get('$state');
var deffer = $q.defer();
if (rejection.status === 401) {
var $http = $injector.get('$http');
var AuthService = $injector.get('AuthService');
var user = store.get('user');
var cred = AuthService.refreshTokenValue(user.secretId, user.secretClient, user.refreshToken);
AuthService.getRefreshToken(cred).then(function(res) {
UserService.oAuth.accessToken = res.data.access_token;
// here i want to resend the request
return deffer.resolve(rejection);
});
}
return $q.reject(rejection);
}
you should first create a promise object to return then retry request using $http(rejection.config),
resolve or reject promise according to your authorization process.
Sample Code
var $state = $injector.get('$state');
var retryRequest = function($http, config, deferred) {
function successCallback(response) {
deferred.resolve(response);
}
function errorCallback(response) {
deferred.reject(response);
}
$http(config).then(successCallback, errorCallback);
}
if (rejection.status === 401) {
var deferred = $q.defer(); //moved deferred to here
var $http = $injector.get('$http');
var AuthService = $injector.get('AuthService');
var user = store.get('user');
var cred = AuthService.refreshTokenValue(user.secretId, user.secretClient, user.refreshToken);
AuthService.getRefreshToken(cred).then(function(res) {
UserService.oAuth.accessToken = res.data.access_token;
retryRequest($http, rejection.config, deferred);
// here i want to resend the request
return deffer.resolve(rejection);
});
return deferred.promise;
}
return $q.reject(rejection);
but you should better use an httpBuffer to queue requests and retry them all once authorization succeeds
Edit: you can use something like this https://github.com/witoldsz/angular-http-auth/blob/master/src/http-auth-interceptor.js
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.