Trying to find solution for redirection to the login page after Ajax-call if a user is not authenticated longer.
I used a method described here
How to manage a redirect request after a jQuery Ajax call
private static void RedirectionToLogin(ActionExecutingContext filterContext)
{
string redirectOnSuccess = filterContext.HttpContext.Request.Url.AbsolutePath;
string redirectUrl = string.Format("?ReturnUrl={0}", redirectOnSuccess);
string loginUrl = FormsAuthentication.LoginUrl + redirectUrl;
filterContext.HttpContext.Response.AddHeader("REQUIRES_AUTH", "1");
filterContext.HttpContext.Response.StatusCode = 401;
filterContext.HttpContext.Response.Redirect(loginUrl, true);
}
JavaScript is
$(window).ajaxComplete(function (event, request, settings) {
alert(request.getResponseHeader('REQUIRES_AUTH'));
alert(request.status);
if (request.getResponseHeader('REQUIRES_AUTH') === 1) {
window.location = App.basePath + "Login/LogOn";
{
});
If user was logged out request.getResponseHeader('REQUIRES_AUTH')) is always NULL
Why request.getResponseHeader('REQUIRES_AUTH')) is NULL???
I think the reason you are getting a null value for "REQUIRES_AUTH" is that your log in page
~/Login/Logon
does not require authentication. Which is correct, otherwise you would not be able to log in! Also your javascript has a curly bracket the wrong way around. (typo) should be:
$(window).ajaxComplete(function (event, request, settings) {
alert(request.getResponseHeader('REQUIRES_AUTH'));
alert(request.status);
if (request.getResponseHeader('REQUIRES_AUTH') === 1) {
window.location = App.basePath + "Login/LogOn";
}
});
I am also looking for a nice solution for this problem, but am still a bit stuck. Have you managed to solve this problem? if so, how did you manage it?
Related
I'm trying to add access control to my Angular app and running into some puzzling troubles...
On the front-end, the authentication function is being called repeatedly until it is stopped for being too large of a call stack. Each time, the $http request within the function is triggering the errorCallback function. The expected behavior is for the auth function to fire once every time ui-router's state changes, changing $rootScope values that indicate the browser's level of authentication.
First, the factory responsible for making the GET request:
.factory ('authRequest', function ($http) {
return {
authStatus : function() {
$http.get('/auth', {'withCredentials' : true}).then(function successCallback(response) {
console.log("Successful authorization check.");
return response.status;
}, function errorCallback(response) {
if (response.status) {
console.log("Failed to authenticate.");
return response.status;
}
console.log("Failed to receive a response.");
return 'errNoResponse';
});
}
}
})
Then, the ng-controller for processing the factory's response:
//Navbar controller, set to fire upon each state change and verify authorization.
.controller('navCtrl', function ($rootScope, $state, authRequest) {
$rootScope.$on('$stateChangeStart', function (event, toState) {
console.log('authRequest value: ' + [authRequest]);
if (authRequest.authStatus === 202) {
console.log('Auth check succeeded, assigned user privileges.');
$rootScope.loggedIn = true;
$rootScope.loggedInAdmin = false;
if (toState = 'users' || 'login') {
event.preventDefault();
}
} else if (authRequest.authStatus === 222) {
console.log('Auth check succeeded, assigned admin privileges.');
$rootScope.loggedIn = true;
$rootScope.loggedInAdmin = true;
if (toState = 'login') {
event.preventDefault();
}
} else {
console.log('Auth check failed.');
$rootScope.loggedIn = false;
$rootScope.loggedInAdmin = false;
event.preventDefault();
$state.go('login');
}
});
})
Meanwhile, on the back-end, I'm not seeing evidence of the /auth Express route being reached with any of the requests. I have a console log set to go off when /auth receives a GET request, but I'm not seeing any activity in the console. Every other Express route is being accessed without issue. The expected behavior is to receive the request, decode the request's JWT cookie header, then send a response code back according to what sort of user privileges are listed. Here's the Express route for /auth:
// GET /auth. Fired every time the app state changes. Verifies JWT authenticity and sends a response based on the user's privileges. 202 is an auth'd user, 222 is an auth'd admin. 401 is no token or error.
app.get('/auth', function (req, res, next) {
console.log('Authorization request received.')
var decoded = jwt.verify(req.cookie, [JWTAuthSecret]);
if (decoded) {
if (decoded.admin === true) {
return res.status(222).send(res.status);
console.log(decoded.sub + ' is an admin, responded with code 222');
} else {
return res.status(202).send(res.status);
console.log(decoded.sub + ' is not an admin, responded with code 202');
}
} else {
return res.status(401).send(res.status);
console.log('Decode failed, responded with code 401');
};
});
With the current setup, the app is hanging indefinitely. As mentioned earlier, a ton of auth requests are being produced upon each state change. Each one logs an "authRequest value: [object Object]" then "Auth check failed." Eventually I get the following error:
angular.js:13550RangeError: Maximum call stack size exceeded
at angular.js:10225
at n.$broadcast (angular.js:17554)
at Object.transitionTo (angular-ui-router.js:3273)
at Object.go (angular-ui-router.js:3108)
at app.js:275
at n.$broadcast (angular.js:17552)
at Object.transitionTo (angular-ui-router.js:3273)
at Object.go (angular-ui-router.js:3108)
at app.js:275
at n.$broadcast (angular.js:17552)
So there seems to be a problem with the frequency of the calls on the front end, as well as a problem with actually getting the data sent to the /auth route.
This is my first time working with Angular factories, so my instinct is to assume my factory implementation is wonky... I haven't figured out how it might be fixed on my own, though.
Thanks for reading, hope I can get some advice on what to change to make this work.
I see a couple issues. This might not solve everything, but will hopefully help narrow things down.
One is that authRequest.authStatus is a function but you're never calling it. In your controller, you need to call the function. That's part of the reason nothing's pinging the backend.
authRequest.authStatus().then(function(status) {
if (status === 202) {
//do stuff
} else if (status === 222) {
//do other stuff
}
});
Now, in your factory, you're not returning anything to the function, so make sure you do that.
.factory ('authRequest', function ($http) {
return {
authStatus : function() {
return $http.get('url').then(callbacks);
}
}
})
The accepted answer provided me with solutions to the front-end troubles I was facing. The GET request to /auth was successfully being sent to Express as intended, but was still responding with an error code despite a valid authentication cookie.
Checking the backend logs, I was receiving an error saying JsonWebTokenError: jwt must be provided when trying to decode the JWT. Turns out req.cookie isn't the correct syntax to check the request's cookie - req.cookies is.
After changing that, my cookie output went from being undefined to [object Object]. The error I was getting changed as well to TypeError: jwtString.split is not a function.
After double checking the way cookies are referenced in the Express docs, I realized I wasn't calling the JWT cookie by name. The updated code is:
app.get('/auth', function (req, res, next) {
console.log('Authorization request received.')
console.log ('Cookie Data: ' + req.cookies.CookieName);
var decoded = jwt.verify(req.cookies.CookieName, [JWTAuthSecret]);
if (decoded) {
if (decoded.admin === true) {
return res.status(222).send(res.status);
console.log(decoded.sub + ' is an admin, responded with code 222');
} else {
return res.status(202).send(res.status);
console.log(decoded.sub + ' is not an admin, responded with code 202');
}
} else {
return res.status(401).send(res.status);
console.log('Decode failed, responded with code 401');
};
});
With this change and the front-end changes, the app's authentication and authorization are working as intended.
I'm trying to get an OAuth access token from the GitLab CE endpoint for OAuth. I've got the code parameter fine in JS, but when I make an Ajax request to the access token page, it throws an error about the server not being CORS enabled, and the origin null not being allowed. Why is my origin null? I also looked at the gitlab documentation, it says you have to use a http client, and then it shows an example of RestClient. What is this? I've tried using it but it doesn't work. Please see below for my code:
if (getQueryVariable('code') === false) {
if (local !== false) {
window.location.replace('https://gitlab.com/oauth/authorize?client_id=####&redirect_uri=http://localhost:4000&response_type=code');
} else { //live
window.location.replace('https://gitlab.com/oauth/authorize?client_id=####&redirect_uri=http://roconnor.gitlab.io/repoViewer&response_type=code');
}
} else {
var oauthcode;
if (local !== false) {
oauthcode = $.getJSON('http://gitlab.com/oauth/token?client_id=####&client_secret=####&code=' + getQueryVariable('code') + '&grant_type=AUTHORIZATION_CODE&redirect_uri=http://localhost:4000');
var parameters = 'client_id=####&client_secret=####&code=' + getQueryVariable('code') + '&grant_type=AUTHORIZATION_CODE&redirect_uri=http://localhost:4000'
RestClient.post 'http://gitlab.com/oauth/token', parameters //this is the code given by the docs
} else {
oauthcode = $.getJSON('http://gitlab.com/oauth/token?client_id=####&client_secret=####&code=' + getQueryVariable('code') + '&grant_type=AUTHORIZATION_CODE&redirect_uri=http://roconnor.gitlab.io/repoViewer');
}
console.log(oauthcode); //the error gets thrown here
I am having what I hope is an easy to solve problem with the Soundcloud API using JavaScript:
unauthorized, the following code works fine:
var group = 'https://soundcloud.com/groups/chilled';
SC.initialize({
client_id: 'MY_CLIENT_ID',
redirect_uri: 'http://localhost:49957/tn/callback.html'
});
// Resolve works fine and gives number ID of group
SC.resolve(group + '?client_id=' + client_id).then(function (g) {
console.log('Group 1: ' + g.id);
});
after I authorise a user:
SC.connect().then(function () {
return SC.get('/me');
}).then(function (me) {
authUser = me.id
});
// Resolve no longer works and i get 401 unauthorised
SC.resolve(group + '?client_id=' + client_id).then(function (g) {
console.log('Group 1: ' + g.id);
});
can anyone help me to understand what I am doing wrong - I can't seem to find an example to follow anywhere. Driving me potty!
Many thanks in advance,
James
For anyone else out there facing the same issues, I have answered my own question:
Firstly, I was not properly logged in due to an error on Soundcloud's sample code in their callback.html used to return to the site after oAuth client side login flow. In Soundcloud's sample callback.html, the code:
<body onload="window.opener.setTimeout(window.opener.SC.connectCallback, 1)">
must be altered to:
<body onload="window.setTimeout(window.opener.SC.connectCallback, 1)">
This allows the popup to close properly and completes the login flow if the application settings are correctly configured to the same domain (localhost or production, but not a mix of the two).
Further to this callback, i have added the following code:
var params = getSearchParameters();
window.opener.oAuthToken = params.code;
function getSearchParameters() {
var prmstr = window.location.search.substr(1);
return prmstr != null && prmstr != "" ? transformToAssocArray(prmstr) : {};
}
function transformToAssocArray(prmstr) {
var params = {};
var prmarr = prmstr.split("&");
for (var i = 0; i < prmarr.length; i++) {
var tmparr = prmarr[i].split("=");
params[tmparr[0]] = tmparr[1];
}
return params;
}
In my subsequent pages, I can get any data as a sub-resource of the '/me/...' endpoint now, but anything I used to be able to interrogate via public access is still in accessible. Since I can now iterate through the logged in users groups, I no longer have to resolve the name of a group via the public resource '/resolve/', so my issue is not solved, but avoided in my current development.
Been searching for solutions for hours and getting close to no luck. I just don't see much documentation on the matter of Parse Config. At least with solutions because I tried everything I could find.
So basically I'm trying to set a default picture when someone saves an object with a status as "denied."
It started with me looking at this: Set default profile picture for parse signup
And here's what I got.
//Accepts/denies picture request
Parse.Cloud.afterSave("Requests", function(request) {
var toUserId = request.object.get("to").id;
var fromUserId = request.object.get("from").id;
var toUser = null;
var fromUser = null;
var status = request.object.get("status");
if (status === "accepted") {
.....
} else if (status === "denied") {
Parse.Config.get().then(function(config) {
request.object.set('photo', config.get("denied"));
}).then(function() {
console.log('Success: Denied photo set.');
}, function(error) {
console.log('error: denied photo not set');
});
} else if (status === "waiting") {
....
}
});
I get a success everytime, but I get nothing as the photo file. I'm stuck and not sure what else to do here. The status changes to denied correctly, but I don't get anything to show up as a file in the photo spot, stays as undefined..
I2015-08-24T01:54:09.837Z]v46 after_save triggered for Requests for user oE3FhNfyWW:
Input: {"object":{"createdAt":"2015-08-24T01:54:03.398Z","from":{"__type":"Pointer","className":"_User","objectId":"odv4R9OWso"},"objectId":"InB8Iods8U","status":"denied","to":{"__type":"Pointer","className":"_User","objectId":"oE3FhNfyWW"},"updatedAt":"2015-08-24T01:54:09.834Z"}}
Result: Success
I2015-08-24T01:54:09.973Z]Success: Denied photo set.
I notice the code doesn't say request.object.save(), which might explain why the object isn't changed when you check later on.
But saving seems strange, since this function runs after saving. That's either wasteful or infinitely-loopy. Since the goal is to modify request.object (the object just saved), then do this on beforeSave().
Remember to call response.success() or .error() at the end of beforeSave().
I want to intercept all route changes with Sammy to first check if there is a pending action. I have done this using the sammy.before API and I return false to cancel the route. This keeps the user on the 'page' but it still changes the hash in the browsers address bar and adds the route to the browsers' history. If I cancel the route, I dont want it in the address bar nor history, but instead I expect the address to stay the same.
Currently, to get around this I can either call window.history.back (yuk) to go back to the original spot in the history or sammy.redirect. Both of which are less than ideal.
Is there a way to make sammy truly cancel the route so it stays on the current route/page, leaves the address bar as is, and does not add to the history?
If not, is there another routing library that will do this?
sammy.before(/.*/, function () {
// Can cancel the route if this returns false
var response = routeMediator.canLeave();
if (!isRedirecting && !response.val) {
isRedirecting = true;
// Keep hash url the same in address bar
window.history.back();
//this.redirect('#/SpecificPreviousPage');
}
else {
isRedirecting = false;
}
return response.val;
});
In case someone else hits this, here is where I ended up. I decided to use the context.setLocation feature of sammy to handle resetting the route.
sammy.before(/.*/, function () {
// Can cancel the route if this returns false
var
context = this,
response = routeMediator.canLeave();
if (!isRedirecting && !response.val) {
isRedirecting = true;
toastr.warning(response.message); // toastr displays the message
// Keep hash url the same in address bar
context.app.setLocation(currentHash);
}
else {
isRedirecting = false;
currentHash = context.app.getLocation();
}
return response.val;
});
When using the code provided within the question and answer you have to notice that the route you cancelled will also be blocked for all future calls, routeMediator.canLeave will not be evaluated again. Calling a route twice and cancelling it depending on current state is not possible with this.
I could produce the same results as John Papa did when he used SammyJS on the SPA/Knockout course.
I used Crossroads JS as the router, which relies on Hasher JS to listen to URL changes "emitted" by the browser.
Code sample is:
hasher.changed.add(function(hash, oldHash) {
if (pageViewModel.isDirty()){
console.log('trying to leave from ' + oldHash + ' to ' + hash);
hasher.changed.active = false;
hasher.setHash(oldHash);
hasher.changed.active = true;
alert('cannot leave. dirty.');
}
else {
crossroads.parse(hash);
console.log('hash changed from ' + oldHash + ' to ' + hash);
}
});
After revisiting an older project and having a similar situation, I wanted to share another approach, just in case someone else is directed here.
What was needed was essentially a modern "auth guard" pattern for intercepting pages and redirecting based on credentials.
What worked well was using Sammy.around(callback) as defined here:
Sammy.js docs: Sammy.Application around(callback)
Then, simply do the following...
(function ($) {
var app = Sammy("body");
app.around(checkLoggedIn);
function canAccess(hash) {
/* access logic goes here */
return true;
}
// Authentication Guard
function authGuard(callback) {
var context = this;
var currentHash = app.getLocation();
if (!canAccess(currentHash)) {
// redirect
context.redirect("#/login");
}
else {
// execute the route path
callback();
}
};
})(jQuery);