user isn't authenticated in custom authorize attribute - asp.net-mvc-4

I've made my own authorize attribute, and this is what it looks like
public class RedirectAuthorize : AuthorizeAttribute
{
protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
if (!filterContext.HttpContext.User.Identity.IsAuthenticated)
{
filterContext.Result = new RedirectToRouteResult(new
RouteValueDictionary(new { controller = "NotExist" }));
}
}
}
So if the user isn't authenticated, I want them to get redirected to the NotExist controller. I've debugged and it seems that unauthorized users get in the if clause, which is correct. But I've also tried doing this with logged in users, and they get in the if clause as well which is wrong.
I dont understand why this is happening. It makes me hesitate about whether my log-in didnt work. Is this the right way of logging a user in?
FormsAuthentication.SetAuthCookie(acc.username, false);
I've never made a log-in system in asp.net mvc before, so please tell me what I'm doing wrong.
Edit:
It seems that the default [Authorized] attribute isn't working either... I really think the problem lays in the log in:
[HttpPost]
public ActionResult Login(User acc)
{
if(ModelState.IsValid)
{
if (Validate(acc.username, acc.password))
{
FormsAuthentication.SetAuthCookie(acc.username, false);
return RedirectToAction("Index", "System");
}
}
ModelState.AddModelError("IncorrectDetails", "Wrong details. Please try again.");
return View(acc);
}

The custom authorize attribute looks correct.
Since you are setting the cookie yourself I would guess you are not using the built-in membership provider.
If you set the cookie yourself, you also need to read the auth cookie and set the Identity and Principal objects on each request. Otherwise, HttpContext.User.Identity.IsAuthenticated will always be false, which seems to be what you are experiencing.

Related

Net Core - Cookie SameSite

I found the problem in my project. The problem is that the cookie is not saved when the user is logged in via the iframe. Cookies are saved when you log in to the site normally, that is, directly through the domain. There are 2 different problems for me right now:
When logging into the site directly (www.example.com), the relevant cookie is saved. But this cookie is saved as Lax. I want it to be saved as none.
If there is no direct login to the site before, if you want to log in from within the iframe, the cookie is not saved. I want to create cookies on logins made via iframe.
I did not perform anything on the cookie. I guess SignAsync() creates the relevant cookies automatically. I am also attaching my codes inside the Account Controller.
public IActionResult Login(string returnurl)
{
return View(new LoginViewModel {ReturnUrl=returnurl, IsPersistent=true });
}
[HttpPost]
public async Task<IActionResult> Login(LoginViewModel model)
{
var result = await signInManager.PasswordSignInAsync(model.Username, model.Password, model.IsPersistent, true);
if (result.Succeeded)
{
return Redirect(model.ReturnUrl ?? "/");
}
else
{
ModelState.AddModelError("","Invalid User Login");
return View(model);
}
}
I also tried the solutions in the links below but it didn't work for me. Maybe I have applied these solutions incorrectly.
https://learn.microsoft.com/tr-tr/aspnet/core/security/samesite/rp31?view=aspnetcore-3.1&viewFallbackFrom=aspnetcore-6.0
https://learn.microsoft.com/tr-tr/aspnet/core/security/samesite?view=aspnetcore-6.0
And here is a photo:
Cookie View with Cookie Editor

Asp.Net core type of actionfilter to use for the scenerio

The requirement is that a logged in user MUST accept privacy statement before accessing other areas of the application. I can write a Middleware or an actionfilter but not sure what's better suited.
Currently the flow will be something like below (Assuming it's a actionfilter).
Authenticate user and load claims from db including whether privacy statement is accepted and redirect to application dashboard.
Below things happen inside the actionfilter
Is user authenticated?
Has the user accepted the privacy statement if any available? (read claims for "PrivacyAccepted" = true)
If no privacy accepted claim available, redirect user to a page showing a message with buttons to accept/reject
If accepted, save it in database, update current user claims with a value like "PrivacyAccepted" = true (using IClaimsTransformation)?
If rejected, show a message and no matter what user does he'll always get the privacy statement page since action filter will redirect here until he accepts it.
From a design/best practice/performance standpoint what is the best thing to do here? Use a middleware or an ActionFilter?
Also point 5 using IClaimsTransformation should be used to update the current claims in logged in user if he accepts the privacy statement. But I haven't found any resources saying whether I can call IClaimsTransformation.TransformAsync() from my code. Everywhere it seems to be working as a middleware rather than I calling it manually.
Maybe you can define a Policy and go for Policy-Based Authorization to achieve your goal.
First when the user accepted the privacy terms add the user a new claim. (like "IsPrivacyAccepted", true)
await _userManager.AddClaimsAsync(user, new List<Claim>
{
new Claim("IsPrivacyAccepted", true),
//other claims
});
Then define a policy with the required claim.
services.AddAuthorization(x =>
{
x.AddPolicy("PrivacyAccepted", policy =>
{
policy.RequireClaim("IsPrivacyAccepted", true); //claim based authorization
});
});
Use the policy wherever you want to restrict users to access your actions.
[Authorize(Policy = "PrivacyAccepted")]
public ActionResult Index() { //... }
If you do like this, you don't need to create an action filter or middleware.
But as I understand you also want to redirect the user to the privacy policy page if he/she is not accepted yet (does not have IsPrivacyAccepted claim). If you want to do this you can write a basic middleware as below
app.Use(async (context, next) =>
{
await next();
if (context.Response.StatusCode == 401)
{
context.Request.Path = "/PrivacyPolicyPage";
await next();
}
});
If you don't want to define the [Authorize(Policy = "PrivacyAccepted")] for each of your controller, maybe you can create a base controller and inherit all of your controller from it.
[Authorize(Policy = "PrivacyAccepted")]
public class MyBaseController : Controller
{
}
and inherit all your controllers from this class instead of Controller class
public class MyController : MyBaseController
{
}

ASP.Net OWIN Setting Cookie but User is Immediately Logged Out

I have been having this problem for a couple months. The issue is sporadic and difficult to reproduce, but I have managed to find a somewhat consistent way to do it. I am debugging and see that as soon as it should redirect to /home/index, it goes to /account/logoff instead. If I test locally then it will work on the first login fine then it will fail if I use a different account. I set the CookieName and CookieSecure after seeing a couple of posts related to them. It didn't seem to help though.
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
CookieManager = new SystemWebChunkingCookieManager(),
CookieName = "NewImprovedCookieName",
CookieSecure = CookieSecureOption.SameAsRequest,
LoginPath = new PathString("/Account/Login"),
LogoutPath = new PathString("/Account/LogOff"),
Provider = new CookieAuthenticationProvider
{
// Enables the application to validate the security stamp when the user logs in.
// This is a security feature which is used when you change a password or add an external login to your account.
OnValidateIdentity =
SecurityStampValidator.OnValidateIdentity<ApplicationUserManager, ApplicationUser>(
validateInterval: TimeSpan.FromMinutes(30),
regenerateIdentity: (manager, user) => user.GenerateUserIdentityAsync(manager))
}
});
The LogOff happens right after this last line of code here.
private ActionResult RedirectToLocal(string returnUrl)
{
if (Url.IsLocalUrl(returnUrl))
{
return Redirect(returnUrl);
}
return RedirectToAction("Index", "Home");
}
I tried updating everything in the project to see if it was a bug that was fixed at some point (this was an old project), but nothing seemed to help it.
Is there a way to fix this, or should I consider migrating to .Net Core?
I don't know if this is the best solution, but I did find one that works. I noticed that the SessionID was persisting after logging out. I added code to remove abandon the session and change the session ID, and it appears to be working now.
public ActionResult LogOff()
{
AuthenticationManager.SignOut();
Session.Clear();
Session.Abandon();
Response.Cookies.Add(new HttpCookie("ASP.NET_SessionId", ""));
return RedirectToAction("Index", "Home");
}

how to perform "dry-run" authorization check in .NET Core?

Consider that I have .NET Controller with Policy-based authorization:
public class ImportantController: Controller {
[HttpGet]
[Authorize(Policy = "CanAccessVIPArea")]
public IActionResult ShowInformation() {
...
return OK(VipData);
}
[HttpPost]
[Authorize(Policy = "CanChangeVIPData")]
public IActionResult SaveInformation([FromBody] VipData) {
...
return CreatedAtAction(...);
}
}
Obviously, the real example is much more complex; I apologize if my simplification leads to too much make-believe in it. Also, real application is SPA with Angular front end; but I don't think it makes any difference for the purposes of this question.
When the user calls ShowInformation() I show a lot of data. On that page I have Save button that calls SaveInformation(). Authorization middleware checks for the right policy and it all works fine.
The problem is that by the time the user presses Save, she entered a lot of data, only to find out that she doesn't have the permissions to save. Obviously, leading to bad experience. I want to check for permissions on SaveInformation in the middleware that gets invoked when the user calls ShowInformation. I would prefer not to check for the hardcoded policy because it is on the server and it can change (we have pretty sophisticated permission management system that manipulates permissions at runtime). Invocation of SaveInformation is in the same Angular service as ShowInformation, and it is very easy to check...
I would like to invoke something like /api/SaveInformation?dryrun that will short-circuit the pipeline after authorization middleware with success or failure.
You can inject an IAuthorizationService and ask to evaluate a policy by name:
public class ImportantController: Controller
{
private readonly IAuthorizationService authorization;
public ImportantController(IAuthorizationService authorization)
{
this.authorization = authorization;
}
public async Task<IActionResult> ShowInformation()
{
// ...
var result = await authorizationService.AuthorizeAsync(User, "IsLucky");
return OK(VipData);
}
}
My pratice is to include all permission claims in the id token, when the user first login to the system, the id token will return to the client side. The client side then render the page according to the permission claims.

How am I supposed to use ReturnUrl = ViewBag.ReturnUrl in MVC 4

I'm working on 'ASP.NET MVC 4' application. I'm using/learning SimpleMembershipProvider and try to stick to the default logic created by VS2012 with the Internet template (if I'm not mistaken, the one with 'SimpleMembershipProvider' out of the box).
I'm stuck at the AccountController where I just can't figure put how exactly I can use this method:
private ActionResult RedirectToLocal(string returnUrl)
{
if (Url.IsLocalUrl(returnUrl))
{
return Redirect(returnUrl);
}
else
{
return RedirectToAction("Index", "Home");
}
}
From what I understand the whole idea is to get redirected to the location from where you've decided to log in (exactly what I want to accomplish). I took a look at how it's used in the view :
#using (Html.BeginForm(new { ReturnUrl = ViewBag.ReturnUrl }))
Look for a place where actually ViewBag.ReturnUrl is set with some value and I only got this method here:
[AllowAnonymous]
public ActionResult Login(string returnUrl)
{
ViewBag.ReturnUrl = returnUrl;
return View();
}
and I'm getting pretty confused about how exactly I'm supposed to get the location/url. I set some breakpoints and I have never seen returnUrl to be something different from null which in this scenario seems pretty logical to me since it doesn't get value anywhere (unless I miss something of course).
So I really can't figure out how this work. I post the above just to show that I tried to do my homework, I investigate as much as I could but I didn't found an answer so I ask here. Could you provide explanation/example on how this actually work?
When using forms authentication and the user is not authenticated or authorized the ASP.NET security pipeline will redirect to the login page and pass as a parameter in the query string the returnUrl equal to the page that redirected to the login page. The login action grabs the value of this parameter and puts it in the ViewBag so it can be passed to the View.
[AllowAnonymous]
public ActionResult Login(string returnUrl)
{
ViewBag.ReturnUrl = returnUrl;
return View();
}
The View then stores this value in the form as shown by this line of code in the View.
#using (Html.BeginForm(new { ReturnUrl = ViewBag.ReturnUrl }))
The reason it is stored in the View is so that when the user does a Submit after entering their user name and password, the controller action that handles the post back will have access to this value.
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public ActionResult Login(LoginModel model, string returnUrl)
{
if (ModelState.IsValid && WebSecurity.Login(model.UserName, model.Password, persistCookie: model.RememberMe))
{
return RedirectToLocal(returnUrl);
}
// If we got this far, something failed, redisplay form
ModelState.AddModelError("", "The user name or password provided is incorrect.");
return View(model);
}
If the model state is valid and they are authenticated by calling the WebSecurity.Login method then it calls the method RedirectToLocal with the value of returnUrl which came from the View, which originally came form the login action that created the View.
The returnUrl value will be null if the user is not redirected to the login page as is the case when they just click on the login link at the top of the page in the default layout. In this case the user will be redirected to the home page after successful login. The whole purpose of the returnUrl is to automatically send the user back to the page they were trying to access before they were authenticated/authorized.
That's because the default ASP.NET MVC template is using Forms authentication, and controllers are decorated with [Authorize] attribute:
<authentication mode="Forms">
<forms loginUrl="~/Account/Login" timeout="2880" />
</authentication>
[Authorize]
public class AccountController : Controller
{
//...
}
That means that if the user is not authenticated it will be redirected to the logon page defined in the LoginUrl attribute of the forms element.
During the redirection, FormsAuthentication which is an HttpModule will append the url which was requested in the query string automatically.
So if you navigate to /Account/Login, you wont get anything in the query string since it is decorated with [AllowAnonymous] attribute.
But if you navigate to /Account/Manage you'll notice that the returnUrl in the query string becomes /Account/Manage (/Account/Login?ReturnUrl=%2fAccount%2fManage)
So you are not setting the returnUrl, the framework does it for you, you just use it in the AccountController to know where to redirect the user after he is authenticated.
When an unauthenticated user tries to get into a section of your application which requires authentication, then returnUrl comes into the picture. The Url requested by the unauthenticated user is basically stored in returnUrl.
You can go through the PluralSight tutorial: Building Applications with ASP.NET MVC 4