I developed a project using ASP.NET MVC that uses session to keep track of users after login. Simply authorization! So I used the below code to use it as attribute in required controllers:
public class GppAuthorizeAttribute : System.Web.Mvc.AuthorizeAttribute
{
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
if (httpContext.Session["userId"] == null)
return false;
else
return true;
}
protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
filterContext.Result = new RedirectResult("~/Auth/Login");
}
}
Finally in controller, doing this:
[GppAuthorize]
// GET: Dashboard
public ActionResult Index()
{
return View();
}
So for above, the scenario works fine. Now I am trying to validate each request with a token for authentication (Checks if the request has valid token to work with server-side) and not sure how to do that in ASP.NET MVC 5 as most of the tutorials uses Web Api. I did few R & D and got this for a basic idea to start. Here is the link with an answer:
Authenticate MVC Controller Using Bearer Token and Redirect To The Controller
It looks promising, the questions are: After login,
How can I create the token and pass it in each http request specifically after user login?
Is there anything that I require to do with session or it should be independent of session
anyway?
If the example code with provided link works, how can I make it work for http request with
Ajax call? Say for below code sample:
$.ajax({
type: "POST",
url: "/Dashboard/GetProducts",
contentType: "application/json; charset=utf-8",
dataType: "json",
success: function (value) {
alert(value);
GetProjectDetails();
},
error: function (ex) {
alert('Failed to retrieve states.' + ex); //Check if authentication failed here
}
});
Will this help me to prevent unauthorized url to access data from the website or web project
that I am working with (Though I know, it'll but is there any way to override and make
unauthorized url calls)?
Related
I need to implement antiforgery validation to my MVC project. However, I am having problems with the server side.
I am using #Html.AntiForgeryToken() to create the token.
Then i get the token value and include it in Ajax request.
// Get token
var cookie = $('input[name=__RequestVerificationToken]').val();
// Ajax request
return $.ajax({
url: url,
type: 'post',
headers: {
'x-system-source': headerValue,
'x-verification-token': cookie //verification token
},
data: ko.toJSON(entity),
contentType: 'application/json',
dataType: 'json'
});
Code below comes from microsoft documentation, however, I am not sure how to implement it. Should I create a custom attribute with the method below and add the attribute to every http request? If so, what would be the best way? Is it possible to create attribute for a controller level? I would also like to avoid repetitive code as much as possible. In addition, is there a way to unit test aniforgery validation feature to make sure that it works as expected.
I am new to the web development, and I would appreciate if you could provide code samples or point me in the right direction. Thanks for your time.
void ValidateRequestHeader(HttpRequestMessage request)
{
string cookieToken = "";
string formToken = "";
IEnumerable<string> tokenHeaders;
if (request.Headers.TryGetValues("x-verification-token", out tokenHeaders))
{
string[] tokens = tokenHeaders.First().Split(':');
if (tokens.Length == 2)
{
cookieToken = tokens[0].Trim();
formToken = tokens[1].Trim();
}
}
AntiForgery.Validate(cookieToken, formToken);
}
I have developed an application in Asp.Net MVC with C# Programming Language and using code first Approach, The application is deployed and everything is working properly except a minor issue that i am facing.
When the session is expired it does not redirect the user to the main login page of the application, where i want that the application should redirect the user to the main login page while the session is expired.
How is it possible?
There are numerous examples for the functionality that you want to achieve, but I wanted to give you a basic scenario of how you would do a redirect to the login page when a session expires or the user logs out:
Assuming you have setup your session, when a user will logout, the controller will have a Logout method:
public ActionResult Logout()
{
Session.Abandon();
Session.Clear();
return RedirectToAction("LoginPage", "Login");
}
This would destroy the session variables and redirect the user to the Login page.
Now when a session expires, you can do something like this:
public ActionResult SessionCheck()
{
string message = string.Empty;
if (Session["UserName"] == null)
{
message = "Session expired. Please Login again";
}
return Json(message, JsonRequestBehavior.AllowGet);
}
You can check for this method throughout your program using AJAX or you could use SessionState.
I prefer to use AJAX so I am giving you a simple example:
function IsSessionActive()
{
var url ="/Login/SessionCheck";
var param = {};
param = JSON.stringify(param);
var result = getResultPost(url, param);
if (result != "")
{
alert("Session has expired, Please login again");
//Redirect Here
window.location="#Url.Action("LoginPage", "Login")";
return true;
}
}
function getResultPost(url, param) {
var result;
$.ajax({
url: url,
type: "POST",
async: false,
dataType: "json",
data: param,
contentType: "application/json; charset=utf-8",
success: function (data, textStatus) {
result = data;
},
error: function (e) {
result = "Error";
}
});
return result;
}
And finally call this in your View like:
$(document).ready(function () {
if (IsSessionActive()) return false;
})
This will check for the session on every page that you call this method and if the session is expired, it will alert the user and redirect to the Login page. You can customize your own styles like a modal or customized alert box to show the user that their session has expired. I hope this helps.
Suddenly, this error started happening. The web api method is this:
// POST api/Account/Logout
[Route("Logout")]
public IHttpActionResult Logout()
{
Authentication.SignOut(CookieAuthenticationDefaults.AuthenticationType);
return Ok();
}
That method is inside AccountController which has [Authorize] attribute.
When I just logged in and I see a page that also has [Authorize], I press a button that allows me to log out. Tha log out button calls the Web Api using Ajax.
Let me tell you also that this worked before.... suddenly it stopped working.
I have added this code in Startup.Auth.cs:
var config = new System.Web.Http.HttpConfiguration();
app.UseWebApi(config);
But it did not work either. I added that line just in case, because as I have old you, this worked before, without that line.
Any help on this, please?
Also please, don't suggest to remove [Authorize] attibute, as I have seen in other answeres here. Web Api's have to be called being authenticated.
EDIT: this is the call in Ajax:
self.logout = function () {
// Log out from the cookie based logon.
var token = sessionStorage.getItem(tokenKey);
var headers = {};
if (token) {
headers.Authorization = 'Bearer ' + token;
}
$.ajax({
type: 'POST',
url: '/api/Account/Logout',
headers: headers
}).done(function (data) {
// Successfully logged out. Delete the token.
self.user('');
sessionStorage.removeItem(tokenKey);
location.href = '/';
}).fail(showError);
}
We are trying to create following scenario:
Html page sends ajax request to WebApi asking wheter user has certain user role or not. WebApi checks from ADFS if user is logged in (if not, WebApi authenticates user). WebApi then reads user roles from ADFS and returns true/false to html page.
What we have so far:
Ajax sends Get-request to WebApi. In WebApi we have Authorize-tag, which correctly sends user to ADFS-authentication. However, after authentication ADFS returns html-page containing saml information to client instead of WebApi. So now we create another ajax request (post this time) which has received html-page as data. WebApi then parses this and returns either true/false based on user roles in SAML-response.
Questions:
Currently used mechanism seems clunky. Is this mechanism correct or is there a better way to do this?
If we use method above, is there a possibility that user edits received html-page and gives himself roles that he doesn't really have?
Even if above mechanism is correct, we are still having cors-error when WebApi redirects to authentication. Cors is enabled in WebApi's Startup.cs. How do we get rid of this?
Codes:
Ajax:
var uri = 'api/user';
$(document).ready(function () {
$.ajax({
url: uri,
type: "Get",
success: function (data) {
$.ajax({
url: uri,
type: "POST",
data: data,
success: function (value) {
if (value == true) {
$('#userData').text('You have correct role.');
}
else {
$('#userData').text('You don't have correct role.');
}
},
error: function (jqXHR, textStatus, err) {
window.location.href = "NotLoggedIn.html";
}
})
},
error: function (jqXHR, textStatus, err) {
window.location.href = "NotLoggedIn.html";
}
});
});
Startup.Auth.cs:
public void ConfigureAuth(IAppBuilder app)
{
app.SetDefaultSignInAsAuthenticationType(WsFederationAuthenticationDefaults.AuthenticationType);
app.UseWsFederationAuthentication(new WsFederationAuthenticationOptions
{
MetadataAddress = ConfigurationManager.AppSettings["ida:AdfsMetadataEndpoint"],
Wtrealm = ConfigurationManager.AppSettings["ida:Audience"],
});
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = WsFederationAuthenticationDefaults.AuthenticationType
});
}
UserController:
namespace SSOWebApiSample.Controllers{
[RoutePrefix("api/user")]
public class UserController : ApiController
{
[HttpGet]
[Route("")]
[Authorize]
public IHttpActionResult GetUser()
{
return Ok();
}
[HttpPost]
[Route("")]
public async Task<IHttpActionResult> PostUser()
{
bool isAdditionalInfoAllowedUser = false;
string result = await Request.Content.ReadAsStringAsync();
//Parse result here
return Ok(isAdditionalInfoAllowedUser);
}
}
}
AdalJS will clean this up nicely. Please refer to the following:
To Do application adapted from Azure AD to ADFS
Azure AD sample with CORS
Enabling Cross-Origin Requests in ASP.NET Web API 2
To make successful CORS Web API calls with ADFS authentication, I found needed to set instance, tenant, clientId, and endpoints members when calling adalAuthenticationService.init() in my Angular app's Config. See sample two for endpoints example, but replace GUID with URL.
With help of fellow friends I managed to find a solution for my problem from this topic: Reusable way to allow an account to be used by a single person at a time
I have a SingleLogin class which inherits from AuthorizeAttribute and implements a custom AuthorizeCore method for the purpose of re-usability of my single-login code:
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
bool isAuthorized = base.AuthorizeCore(httpContext);
if (isAuthorized)
{
int userId = (int)WebSecurity.CurrentUserId;
using (var db = new UsersContext())
{
if ((httpContext.Session.SessionID != db.getSessionId(userId))
|| db.getSessionId(userId) == null)
{
WebSecurity.Logout();
isAuthorized = false;
httpContext.Response.Redirect("/Home/Index");
}
}
}
return isAuthorized;
}
Everything works fine except my JsonResult action:
[HttpPost]
public JsonResult MessageSave(string message)
{
bool messageSaved = false;
int userId = (int)WebSecurity.CurrentUserId;
message = HttpUtility.HtmlEncode(message);
// Model method - adding chat log - db
db.addChatLog(message, userId);
messageSaved = true;
return Json(new { messageSaved = messageSaved });
}
This method is triggered by Ajax POST call which you can see in code example below. Just basic POST.
EDIT 3
Please check these images: http://imgur.com/a/Cjael .. Hm I guess POST does trigger, but have no idea why does my alert not work when I try to test it before $.ajax ... As you can see in response I do get Home/Index page but I am not redirected to home/index immediately(text stays inside of textBox and page just waits..), I have to push enter one more time to be redirected.. Very strange.
EDIT2
Seems like I can't even access my jQuery even after I get logged out. I put some alerts inside of my .js file.
I have a separate .js file which is then put in my View as <script src="~/Scripts/custom/homeChat.js"></script> . I pass the Razor values from View into my JS file via HTML5 data-.
My textBox element #txtMsg, triggers my jQuery event, therefore when I am logged out it probably doesn't recognize my textBox element anymore, and doesn't trigger my jQuery event?
Element that triggers .js in view is:
#Html.TextBox("txtMsg")
JS:
$("#txtMsg").keypress(function (e) {
//when enter
if (e.which == 13) {
alert("ALERT DOESNT TRIGGER");
$.ajax({
type: "POST",
url: url,
data: JSON.stringify({ message: input }),
contentType: "application/json; charset=utf-8",
dataType: "json",
success: function (data) {
if (data.messageSaved) {
$("#txtMsg").val("");
}
else {
window.location.href = urlhome;
}
}
});
}
}
});
So if you can't even come into your event, how can you even know something went wrong? I have this ˙HandleUnauthorizedRequest but you are required that you can get into your jQuery event(in my case .keypress in the js code above) for this to work if I understand right.
EDIT: Additional explanation
So let me explain the scenario. If I login with my username "john" from Firefox and again with username "john" from chrome, next action I do in Firefox, it will log me out and redirect me to Home/Index, because someone else made a new login in Chrome.
That is ok. Since you are not logged in anymore, you get redirected normally to your Home/Index if your action is normal ActionResult and returns view.
The problem I have is, that I have some other functionality in the page, which uses Ajax POST, and since you are logged out you can't POST to that JsonResult action therefore you can't even receive callback of error, which redirects you to Home/Index. I put some alerts into my JS, but no alert triggers which is normal, because I am not allowed on that page anymore anyway. If I want that my onEnter textbox redirects me to Home/Index I have to press enter twice. Is that all that could be done?
I am interested in best approach for this AJAX problem. I don't know how I should call it, but as I read from my previous topic it is called "handling AJAX timeouts"?
Thank you very much.
You can handle errors on AJAX request this way
$.ajax({
type: "POST",
url: url,
data: JSON.stringify({ message: input }),
contentType: "application/json; charset=utf-8",
dataType: "json",
success: function (data) {
if (data.messageSaved) {
$("#txtMsg").val("");
}
else {
window.location.href = urlhome;
}
},
error: function(xhr, status, error) {
// TODO: may be check error or status or xhr.statusCode()
window.location.href = urlhome;
}
});
jQuery $.ajax() docs
If understand it correctly you want to handle the unauthorized ajax request.
In that case you can override the HandleUnauthorizedRequest method in your attribute:
protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
if (filterContext.HttpContext.Request.IsAjaxRequest())
{
filterContext.HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
filterContext.Result = new JsonResult();
}
else
{
filterContext.Result = new HttpStatusCodeResult((int)HttpStatusCode.Forbidden);
}
}