Authorize Attribute always returning false ASP.net MVC Identity - c#

When I decorate a method with an Authorize roles attribute it returns false everytime. I'm trying to limit access to an admin page for users in the "Admin" role only.
I have verified that the user im currently logged in as is in fact in the "Admin" role.
I have tried to use a custom authorization attribute. Same result. I can add the code if needed.
I have found that the authorization attribute works for Users but not for Roles.
I believe this problem is somehow tied into the fact that the following does not work in my application:
User.IsInRole("Admin").
However, this statement does work:
userManager.IsInRole(user.Id, "Admin")
Here is my code:
public class AdminController : Controller
{
//[AuthLog(Roles = "Admin")] //Custom authorization attribute
[Authorize(Roles = "Admin")]
public ActionResult Users()
{
return View();
}
}
Maybe this can help with debugging:
Microsoft.AspNet.Identity.Core: V.2.1.0
Microsoft.AspNet.Identity.EntityFramework: V.2.1.0
I am open to suggestions on anything else I can post from my project in order to debug easier. I have scoured the stack for 2 weeks now.
Update 1: How user is logged in
// POST: /account/login
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Login(AccountLoginModel viewModel)
{
// Ensure we have a valid viewModel to work with
if (!ModelState.IsValid)
return View(viewModel);
// Verify if a user exists with the provided identity information
var user = await _manager.FindByEmailAsync(viewModel.Email);
// If a user was found
if (user != null)
{
// Then create an identity for it and sign it in
await SignInAsync(user, viewModel.RememberMe);
// If the user came from a specific page, redirect back to it
return RedirectToLocal(viewModel.ReturnUrl);
}
// No existing user was found that matched the given criteria
ModelState.AddModelError("", "Invalid username or password.");
// If we got this far, something failed, redisplay form
return View(viewModel);
}
private async Task SignInAsync(IdentityUser user, bool isPersistent)
{
// Clear any lingering authencation data
FormsAuthentication.SignOut();
// Create a claims based identity for the current user
var identity = await _manager.CreateIdentityAsync(user, DefaultAuthenticationTypes.ApplicationCookie);
// Write the authentication cookie
FormsAuthentication.SetAuthCookie(identity.Name, isPersistent);
}

The
FormsAuthentication.SetAuthCookie(identity.Name, isPersistent);
unfortunately doesn't store any roles with the identity. Thus, when the identity is recreated from the cookie, you have no roles. To verify try
this.User.IsInRole("Admin")
and you'll get false, even though the userManager tells you otherwise.
There are multiple workarounds.
You could for example switch to any other identity persistor, like the SessionAuthenticationModule which could store your username and roles in the cookie. You could follow my tutorial on that.
Another approach would be to have an explicit role manager and use its feature that automatically causes your roles to be stored in another cookie, separate from the forms authentication cookie. This involves configuring the role provider and writing your own role provider that would be an adapter over the user manager.
Finally, you could forget forms authentication and use Identity's native way of issuing cookies, which would involve calling SignInAsync on the authentication manager.

Related

Is there a way to get the Auth Cookie data on Anonymous endpoints?

I have this case where I have an anonymous endpoint can work on it's own but if a user have already done authentication with the cookie I need to get from that authenticated cookie session the data about the authenticated user, but as I searched online I haven't found a solution to this and the User.Identity.IsAuthenticated is always false despite the user has the authenticated session cookie but on a Anonymous endpoint it's like being ignored so I can't figure out if the user data in the cookie.
Is there any way to accomplish this?
Example of the needed behaviour:
[HttpGet,AllowAnonymous]
public async Task<IActionResult> GetSomething()
{
if(User.Identity.IsAuthenticated){
//Get data from User.Claims and act upon it
} else {
//Get data without user logic
}
}
You still must use the Authorize attribute. The AllowAnonymous attribute serves to still allow access if the user isn't authorized.
[HttpGet,Authorize,AllowAnonymous]
public async Task<IActionResult> GetSomething()

Correct way to support multiple authorization attributes on ASP.Net Web API 2

My ASP.Net Web API app created a JWT Token upon successful login.
public IHttpActionResult LogOn([FromBody] LoginRequest request)
{
var result = _service.LogOn(request);
if (result.Success)
{
var token = CreateToken(request.UserName);
return Ok(OpResult<string>.SuccessResult(token));
}
return Ok(result);
}
I have all controller methods decorated with an "Authorize" attribute which delegates to my TokenValidationHandler (inherits from DelegatingHandler) to validate the token in subsequent requests.
[HttpGet]
[Authorize]
public IHttpActionResult GetAccount(){ // get user details here}
Now I have a requirement to not let the user in unless they have created an account and verified their eMail address. So my idea is that in the first method (user login), instead of just checking result.success and issuing a token, I'd check also check if the retrieved account is eMail verified. If not I'd issue a jwt token with an additional claim "emailverified" set to false. So users hwo haven't activated their eMail can still login and get this jwt token, but the only operation they are allowed is VerifyEmail.
How do I go about implementing this VerifyEmail controller method? Ideally I want it to look like below
[HttpGet]
[AuthorizeEvenIfEmailNotVerified]
public IHttpActionResult GetAccount()
How do I implement AuthorizeEvenIfEmailNotVerified ? Is it another handler that inherits from DelegatingHandler ? But if I have two such handlers (my existing handelr for the regular authorize and this new one), then how does the ASP.Net engine know which handler to send [Authorize] attribute to and which to send [AuthorizeEvenIfEmailNotVerified] to ?
Or should I be using an AuthenticationFilter?
But in that case, it seems weird that I have two attributes doing pretty much the same thing (one authenticating a verified user and the other authenticating a non verified user). yet one of those is implemented via [Authorize] backed by handler inheriting DelegatingHandler whereas the other is implemented via an attribute backed by an AuthenticationFilter?
Or am I going about this the wrong way? For the record I'd prefer to keep the project free of any MVC related libraries unless absolutely needed. Also this is .Net Framework 4.7 project.
Probably Roles will be easiest solution. Generate token with relevant claim:
identity.AddClaim(new Claim(ClaimTypes.Role, "Verified")); //verified email
identity.AddClaim(new Claim(ClaimTypes.Role, "NotVerified")); //not verified email
Next add attribute to controller:
[Authorize(Roles="NotVerified")]

Check if user is logged in with Token Based Authentication in ASP.NET Core

I managed to implement this token based authentication system in my application, but I have a little question. How can I check if a user is signed it (eg if the there is a valid token in the request) within the method? So with the [Authorize] ?
So I have controller, and in that controller I want to check if the user is signed in. I thought of using this:
if (_signInManager.IsSignedIn(ClaimsPrincipal.Current))
{
...
}
but it does not work since ClaimsPrincipal.Current is always null
You don't need to use the SigninManager or something similar. The user is injected on the pipeline (on the User property of the base controller) and it's info is filled automatically by the authentication middleware (cookie or token). So, on your controller:
bool isAuthenticated = User.Identity.IsAuthenticated;
yes . put [Authorize] attribute above your class or methods to check if user is authenticate or not. You can get user by this code:
var principal = User as ClaimsPrincipal;
var check = User.Identity.IsAuthenticated;

How to Logout of Owin Providers?

I am following this tutorial yet it does not tell you how to logout. I tried to do
Request.GetOwinContext().Authentication.SignOut(Microsoft.AspNet.Identity.DefaultAuthenticationTypes.ExternalCookie);
Request.GetOwinContext().Authentication.SignOut()
Request.GetOwinContext().Authentication.SignOut(Microsoft.AspNet.Identity.DefaultAuthenticationTypes.ApplicationCookie);
You can get the sample code here: https://github.com/AndersAbel/SocialLoginWithoutIdentity
Just need to add one more action
public ActionResult SignOut()
{
Request.GetOwinContext().Authentication.SignOut(Microsoft.AspNet.Identity.DefaultAuthenticationTypes.ExternalCookie);
return RedirectToAction("Index", "Home");
}
This method plus any one of the 3 lines of I posted above
My result right now is, I login, I go to secure page and can see it, I then proceed to my signout and then after signout try to go back to the secure page and I am allowed back to that secure page.
So it actually did not really sign me out.
As mentioned in the tutorial, the middleWare used use the default authentication type but don't override it.
By using only externalCookie as parameter for Owin you are clearing the cookie for Asp, but not the one used to store the Google provider,
to do so, you will have to get the array of all current cookies.
It can be done the easy way like this:
Request.GetOwinContext()
.Authentication
.SignOut(HttpContext.GetOwinContext()
.Authentication.GetAuthenticationTypes()
.Select(o => o.AuthenticationType).ToArray());
This is where it is said on the Tutorial:
The call to UseGoogleAuthentication should be quite obvious why it’s needed.
But the first one toSetDefaultSignInAsAuthenticationType is not as
obvious.
login middleware normally relies on the external cookie middleware
registered before the social login middleware.
external cookie middleware, it sets itself as the default signin type.
That’s how the social login middleware knows that it should use the
external cookie. In this setup there is no external cookie, so we have
to manually set the main cookie middleware as the default signin type.
The cookie middleware will only issue a cookie if the
AuthenticationType matches the one in the identity created by the
social login middleware.Looking at the owin external authentication pipeline a socialIn the setup of the
Try setting the cache control headers.
public ActionResult SignOut() {
var authenticationTypes = new string[] {
DefaultAuthenticationTypes.ApplicationCookie,
DefaultAuthenticationTypes.ExternalCookie
};
AuthenticationManager.SignOut(authenticationTypes);
// HACK: Prevent user from being able to go back to a logged in page once logged out
Response.Cache.SetExpires(DateTime.UtcNow.AddMinutes(-1));
Response.Cache.SetCacheability(HttpCacheability.NoCache);
Response.Cache.SetNoStore();
// now redirect
return RedirectToAction("Index", "Home");
}
private IAuthenticationManager AuthenticationManager {
get {
return Request.GetOwinContext().Authentication;
}
}
There is no stopping the user clicking the back button on the browser, unless you try JavaScript, which can be disabled. The user can go back a page and view what was on the previous page, but if they try to click any protected links or refresh the page, they will be redirected to log in.
Use the [Authorize] attribute on classes which need authorization:
[Authorize]
public class MeController : ApiController
{
// GET api/<controller>
public IEnumerable<object> Get()
{
var identity = User.Identity as ClaimsIdentity;
return identity.Claims.Select(c => new
{
Type = c.Type,
Value = c.Value
});
}
}
source: http://www.asp.net/aspnet/overview/owin-and-katana/owin-oauth-20-authorization-server

ASP.NET MVC Single Sign-on and Roles

I have basic Single Sign-On working across 2 MVC sites (call them SiteA and SiteB) using something along the lines of the following method:
http://forums.asp.net/p/1023838/2614630.aspx
They are on sub-domains of the same domain and share hash\encryption keys etc in web.config. I've modified the cookie so it is accessible to all Sites on the same domain. All of this seems to be working ok.
The sites are on separate servers without access to the same SQL database, so only SiteA actually holds the user login details. SiteB has a membership database, but with empty users.
This works fine for my required scenario which is:
1) User logs into SiteA
2) The application loads data from SiteA (by AJAX) and SiteB (by AJAX using JSONP)
I have the following LogOn Action on my AccountController for SiteA, which is where the "magic" happens:
[HttpPost]
public ActionResult LogOn(LogOnModel model, string returnUrl)
{
if (ModelState.IsValid)
{
if (MembershipService.ValidateUser(model.UserName, model.Password))
{
FormsService.SignIn(model.UserName, model.RememberMe);
//modify the Domain attribute of the cookie to the second level of domain
// Add roles
string[] roles = Roles.GetRolesForUser(model.UserName);
HttpCookie cookie = FormsAuthentication.GetAuthCookie(User.Identity.Name, false);
FormsAuthenticationTicket ticket = FormsAuthentication.Decrypt(cookie.Value);
// Store roles inside the Forms cookie.
FormsAuthenticationTicket newticket = new FormsAuthenticationTicket(ticket.Version, model.UserName,
ticket.IssueDate, ticket.Expiration, ticket.IsPersistent, String.Join("|", roles), ticket.CookiePath);
cookie.Value = FormsAuthentication.Encrypt(newticket);
cookie.HttpOnly = false;
cookie.Domain = ConfigurationManager.AppSettings["Level2DomainName"];
Response.Cookies.Remove(cookie.Name);
Response.AppendCookie(cookie);
if (!String.IsNullOrEmpty(returnUrl))
{
return Redirect(returnUrl);
}
else
{
return RedirectToAction("Index", "Home");
}
}
else
{
ModelState.AddModelError("", "The user name or password provided is incorrect.");
}
}
This does some stuff which I don't strictly need for the initial scenario, but relates to my question. It inserts the Roles list for the user on login to SiteA into the UserData of the authentication ticket. This is then "restored" on SiteB by the following in global.asax:
protected void Application_AuthenticateRequest(Object sender, EventArgs e)
{
if (Context.Request.IsAuthenticated)
{
FormsIdentity ident = (FormsIdentity) Context.User.Identity;
string[] arrRoles = ident.Ticket.UserData.Split(new[] {'|'});
Context.User = new System.Security.Principal.GenericPrincipal(ident, arrRoles);
}
}
All of the stuff above works until I add Roles into the mix. Things work fine if I only decorate my Controllers\Actions on SiteB with [Authorize] attributes. But as soon as I add [Authorize(roles="TestAdmin")] users can no longer access that Controller Action. Obviously I have added the user to the TestAdmin Role.
If I debug the global.asax code on SiteB, it looks ok as I leave the global.asax code, BUT then when I hit a break point in the controller itself the Controller.User and Controller.HttpContext.User is now a System.Web.Security.RolePrincipal without the roles set anymore.
So my question is: Does anybody have any idea how I can restore the roles on SiteB or another way to do this?
You already worked it out, but here we go:
make it work: turn off the role manager. Its not an odd behavior that asp.net is doing that, since you are explicitly telling it to use look for the user's roles with the configuration specified.
another way to do it: enable the role manager in both. Use the configuration to share the cookie as you are doing in your custom code. Based on your description, you shouldn't need to worry about it trying to get roles for the user, as long as you use a matching configuration for the authentication cookie
should you use Application_AuthorizeRequest to set the roles cookies? imho opinion earlier (Authenticate) is best, I have always done it that way and never ran into issues.
Since this seems to have stagnated I can partially answer this one with some additional findings. After debugging\testing this a bit more, it seems MVC2 is doing something odd after leaving Application_AuthenticateRequest but before entering my Controller. More details here:
http://forums.asp.net/t/1597075.aspx
A workaround is to use Application_AuthorizeRequest instead of Application_AuthenticateRequest.
EDIT: I believe I found the cause of my issues. On my MVC1 project I had roleManager disabled but my test MVC2 project had roleManager enabled. Once I enabled my roleManager in the MVC1 test project, the behaviour between MVC1 and MVC2 is the same. I also have a question to one of the MVC Microsoft team open about this, and will post back here if Application_AuthorizeRequest is the correct place to restore the Roles from Authentication ticket cookie...

Categories