How to allow specific roles to access API using identityserver3.accesstokenvalidation - asp.net-web-api

I have an Identityserver4 that is providing access tokens to clients.
On my API, I want to be sure that client is allowed to access specific scope and that User belongs to a specific role before I give this user access to API.
To do that I am using Identityserver3.accesstokenvalidation package.
app.UseIdentityServerBearerTokenAuthentication(new IdentityServerBearerTokenAuthenticationOptions
{
Authority = "Authority",
RequiredScopes = new[] { "MyScope" },
});
This is blocking users that do not have access token from accessing my API, also it is checking if the provided scope is "MyScope".
My question is how do I also check that user has a specific role before allowing access to API.

You could put attribute [Authorize(Roles = "Admin")] for specific controller. If you need more advanced logic with claims you need to specify your own attribute e.g. AuthorizePermissionAttribute and use it with controller [AuthorizePermission("Preview")]:
public class AuthorizePermissionAttribute : AuthorizeAttribute
{
private readonly string grantedPermission;
public AuthorizePermissionAttribute(string permission)
{
this.grantedPermission = permission ?? throw new ArgumentNullException(nameof(permission));
}
protected override bool IsAuthorized(HttpActionContext actionContext)
{
var claims = actionContext.ControllerContext.RequestContext.Principal as ClaimsPrincipal;
var permission = claims?.FindFirst(this.grantedPermission);
return permission != null && Convert.ToBoolean(permission.Value);
}
protected override void HandleUnauthorizedRequest(HttpActionContext actionContext)
{
var response = actionContext.Request.CreateErrorResponse(HttpStatusCode.Forbidden, "insufficient_permissions");
actionContext.Response = response;
}
}
Also you need to put in Startup.cs:
JwtSecurityTokenHandler.InboundClaimTypeMap = new Dictionary<string, string>();
app.UseIdentityServerBearerTokenAuthentication(new IdentityServerBearerTokenAuthenticationOptions
{
Authority = ConfigurationManager.AppSettings["IdentityProviderApi"],
PreserveAccessToken = true
});
Without JwtSecurityTokenHandler.InboundClaimTypeMap it will return always Unauthorized status code.

Related

How to automatically retrieve roles from ASP.NET Core identity?

I'm moving my steps to OpenIDDict and I made my application based on Velusia example.
Everything works fine but I have a question: My access token doesn't include roles.
There's a way to automate the retrieving of .NET Core identity user roles and append them to the User property as Claim before accessing the action in my controller?
The purpose of all is being able to use (for example)
User.IsInRole("MyRole");
Thanks to everyone!
Reading this post gets me in the right direction: Is there a way to dynamically load claims in OpenIddict?
public class MyClaimTransformation : IClaimsTransformation
{
private readonly UserManager<UserInfo> _userManager;
public MyClaimTransformation(UserManager<UserInfo> userManager)
{
_userManager = userManager;
}
public async Task<ClaimsPrincipal> TransformAsync(ClaimsPrincipal principal)
{
ClaimsIdentity claimsIdentity = new ClaimsIdentity();
//claimsIdentity.RoleClaimType = OpenIddict.Abstractions.OpenIddictConstants.Claims.Role;
//claimsIdentity.NameClaimType = OpenIddict.Abstractions.OpenIddictConstants.Claims.Name;
var claimType = ClaimTypes.Role;
if (principal.Identity != null && principal.Identity.IsAuthenticated)
{
//Do I already have roles in the claim?
var roleClaimsAvailable = principal.Claims.Any(x => x.Type == claimType);
if (!roleClaimsAvailable)
{
//Roles not found, adding:
var userProfile = await _userManager.GetUserAsync(principal);
if (userProfile != null)
{
var roles = await _userManager.GetRolesAsync(userProfile);
foreach (var role in roles)
{
claimsIdentity.AddClaim(new Claim(claimType, role));
}
principal.AddIdentity(claimsIdentity);
}
}
}
return principal;
}
}
Than we need to register in the Startup.cs as a service:
//Adding roles on access token incoming
builder.Services.AddTransient<IClaimsTransformation, MyClaimTransformation>();

Adding role claims - should i use the IClaimsTransformer

We would like to add a lot of role claims to the current principal (we use the Authorize(Roles) attribute), and found the IClaimsTransformer that looks like a perfect fit.
We've registerd it like this
app.UseClaimsTransformation(new ClaimsTransformationOptions
{
Transformer = new GetRolesFromDatabaseClaimsTransformer(new RoleManager2(Configuration.GetConnectionString("ourcoolapp")))
});
And the transform is like this:
public Task<ClaimsPrincipal> TransformAsync(ClaimsTransformationContext context)
{
// A hacky way to not load on all requests. Better ideas?
if (!context.Context.Request.Path.Value.Contains("api/"))
{
return Task.FromResult(context.Principal);
}
var roleClaims = RoleManager.GetRolesForUser(context.Principal.Identity.Name).Select(roleName => new Claim("role", roleName));
var claims = new List<Claim> { };
var identity = context.Principal.Identity as ClaimsIdentity;
claims.AddRange(identity.Claims);
claims.AddRange(roleClaims);
var userIdentity = new ClaimsIdentity(claims, "local");
var userPrinicpal = new ClaimsPrincipal(userIdentity);
return Task.FromResult(userPrinicpal);
}
Question: Are there alternative, or smarter ways of adding the role claims?
Thanks
Larsi
Another option could be UserClaimsPrincipalFactory
It provides methods to create claims principal for a given user and you can customize it just like ClaimsTransformer.
By default It adds UserName and UserId to claim collection.
In order to customize it you can driver from UserClaimsPrincipalFactory and override CreateAsync
public class AppClaimsPrincipalFactory : UserClaimsPrincipalFactory<User, Role>
{
public AppClaimsPrincipalFactory(UserManager<User> userManager,
RoleManager<Role> roleManager,
IOptions<IdentityOptions> optionsAccessor,
ILogger<AppClaimsPrincipalFactory> logger)
: base(userManager, roleManager, optionsAccessor)
{
logger.LogInformation("AppClaimsPrincipalFactory ctor");
}
public override async Task<ClaimsPrincipal> CreateAsync(User user)
{
var principal = await base.CreateAsync(user);
((ClaimsIdentity)principal.Identity).AddClaims(new []
{
new Claim("Foo", "Bar"),
});
return principal;
}
}
And Register the Factory in DI:
services.AddScoped<IUserClaimsPrincipalFactory<User>, AppClaimsPrincipalFactory>();
It will change/override the claim whenever the user's claims requested.
For more info take a look at source at GitHub.

Custom Claims lost on Identity re validation

I'm implementing Asp.NET MVC application with Identity 2.x Authentication and Authorization model.
During LogIn process I add Custom Claims (not persisted in the DB!), deriving from data passed in the LogIn from, to the Identity and I can correctly access them later on, until the identity gets regenerated.
[HttpPost]
[AllowAnonymous]
[ValidateHeaderAntiForgeryToken]
[ActionName("LogIn")]
public async Task<JsonResult> Login(LoginViewModel model, string returnUrl)
{
if (!ModelState.IsValid)
return Json(GenericResponseViewModel.Failure(ModelState.GetErrors("Inavlid model", true)));
using (var AppLayer = new ApplicationLayer(new ApplicationDbContext(), System.Web.HttpContext.Current))
{
GenericResponseViewModel LogInResult = AppLayer.Users.ValidateLogInCredential(ref model);
if (!LogInResult.Status)
{
WebApiApplication.ApplicationLogger.ExtWarn((int)Event.ACC_LOGIN_FAILURE, string.Join(", ", LogInResult.Msg));
return Json(LogInResult);
}
ApplicationUser User = (ApplicationUser)LogInResult.ObjResult;
// In case of positive login I reset the failed login attempts count
if (UserManager.SupportsUserLockout && UserManager.GetAccessFailedCount(User.Id) > 0)
UserManager.ResetAccessFailedCount(User.Id);
//// Add profile claims for LogIn
User.Claims.Add(new ApplicationIdentityUserClaim() { ClaimType = "Culture", ClaimValue = model.Culture });
User.Claims.Add(new ApplicationIdentityUserClaim() { ClaimType = "CompanyId", ClaimValue = model.CompanyId });
ClaimsIdentity Identity = await User.GenerateUserIdentityAsync(UserManager, DefaultAuthenticationTypes.ApplicationCookie);
AuthenticationManager.SignIn(new AuthenticationProperties() { IsPersistent = true }, Identity);
WebApiApplication.ApplicationLogger.ExtInfo((int)Event.ACC_LOGIN_SUCCESS, "LogIn success", new { UserName = User.UserName, CompanyId = model.CompanyId, Culture = model.Culture });
return Json(GenericResponseViewModel.SuccessObj(new { ReturnUrl = returnUrl }));
}
}
The validation process is defined in the OnValidationIdentity which I havn't done much to customize. When the validationInterval goes by (...or better said the half way to the validationInterval) Identity gets re generatd and Custom Claims are lost.
// Enable the application to use a cookie to store information for the signed in user
// and to use a cookie to temporarily store information about a user logging in with a third party login provider
app.UseCookieAuthentication(new CookieAuthenticationOptions()
{
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
LoginPath = new PathString("/Account/Login"),
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(1d),
regenerateIdentity: (manager, user) => user.GenerateUserIdentityAsync(manager, DefaultAuthenticationTypes.ApplicationCookie))
},
/// TODO: Expire Time must be reduced in production do 2h
ExpireTimeSpan = TimeSpan.FromDays(100d),
SlidingExpiration = true,
CookieName = "RMC.AspNet",
});
I think I should some how be able to pass the current Claims to the GenerateUserIdentityAsync so that I can re add Custom Clims, but I don't know how to.
public async Task<ClaimsIdentity> GenerateUserIdentityAsync(UserManager<ApplicationUser, string> manager, string authenticationType)
{
// Note the authenticationType must match the one defined in CookieAuthenticationOptions.AuthenticationType
var userIdentity = await manager.CreateIdentityAsync(this, authenticationType);
// Add custom user claims here
// ????????????????????????????
return userIdentity;
}
Any help is appreciated.
Thanks
Problem solved (it seemms), I post my solution since I havn't found may appropriate answers and I think it might be useful to others.
The right track was found in an answer to the question Reuse Claim in regenerateIdentityCallback in Owin Identity in MVC5
I just had modify a little the code since the UserId in my case is of type string and not Guid.
Here is my code:
In Startup.Auth.cs
app.UseCookieAuthentication(new CookieAuthenticationOptions()
{
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
LoginPath = new PathString("/Account/Login"),
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(1d),
// regenerateIdentity: (manager, user) => user.GenerateUserIdentityAsync(manager, DefaultAuthenticationTypes.ApplicationCookie))
OnValidateIdentity = context => SecurityStampValidator.OnValidateIdentity<ApplicationUserManager, ApplicationUser, string>(
validateInterval: TimeSpan.FromMinutes(1d),
regenerateIdentityCallback: (manager, user) => user.GenerateUserIdentityAsync(manager, context.Identity),
getUserIdCallback: (ci) => ci.GetUserId()).Invoke(context)
},
/// TODO: Expire Time must be reduced in production do 2h
//ExpireTimeSpan = TimeSpan.FromDays(100d),
ExpireTimeSpan = TimeSpan.FromMinutes(2d),
SlidingExpiration = true,
CookieName = "RMC.AspNet",
});
NOTE: Please note that in my sample ExpireTimeSpan and validateInterval are ridiculously short since the purpose here was to cause the most frequest re validation for testing purposes.
In IdentityModels.cs goes the overload of GenerateUserIdentityAsync that takes care of re attaching all custom claims to the Identity.
/// Generates user Identity based on Claims already defined for user.
/// Used fro Identity re validation !!!
/// </summary>
/// <param name="manager"></param>
/// <param name="CurrentIdentity"></param>
/// <returns></returns>
public async Task<ClaimsIdentity> GenerateUserIdentityAsync(UserManager<ApplicationUser, string> manager, ClaimsIdentity CurrentIdentity)
{
// Note the authenticationType must match the one defined in CookieAuthenticationOptions.AuthenticationType
var userIdentity = await manager.CreateIdentityAsync(this, DefaultAuthenticationTypes.ApplicationCookie);
// Re validate existing Claims here
userIdentity.AddClaims(CurrentIdentity.Claims);
return userIdentity;
}
It works. Not really sure if it is the best solution, but in case anyone has better approaches please feel free to improve my answer.
Thanks.
Lorenzo
ADDENDUM
After some time using it I found out that what implemented in GenerateUserIdentityAsync(...) might give problems if used in conjunction with #Html.AntiForgeryToken(). My previous implementation would keep adding already existing Claims at each revalidation. This confuses AntiForgery logic that throws error. To prevent that I've re implemnted it this way:
/// <summary>
/// Generates user Identity based on Claims already defined for user.
/// Used fro Identity re validation !!!
/// </summary>
/// <param name="manager"></param>
/// <param name="CurrentIdentity"></param>
/// <returns></returns>
public async Task<ClaimsIdentity> GenerateUserIdentityAsync(UserManager<ApplicationUser, string> manager, ClaimsIdentity CurrentIdentity)
{
// Note the authenticationType must match the one defined in CookieAuthenticationOptions.AuthenticationType
var userIdentity = await manager.CreateIdentityAsync(this, DefaultAuthenticationTypes.ApplicationCookie);
// Re validate existing Claims here
foreach (var Claim in CurrentIdentity.Claims) {
if (!userIdentity.HasClaim(Claim.Type, Claim.Value))
userIdentity.AddClaim(new Claim(Claim.Type, Claim.Value));
}
return userIdentity;
}
}
ADDENDUM 2
I had to refine further me mechanism because my previosu ADDENDUM would lead in some peculiar cases to same problem described during re-validation.
The key to the current definitive solution is to Add Claims that I can clearly identify and Add only those during re-validation, without having to try to distinguish betweeb native ones (ASP Identity) and mine.
So now during LogIn I add the following custom Claims:
User.Claims.Add(new ApplicationIdentityUserClaim() { ClaimType = "CustomClaim.CultureUI", ClaimValue = UserProfile.CultureUI });
User.Claims.Add(new ApplicationIdentityUserClaim() { ClaimType = "CustomClaim.CompanyId", ClaimValue = model.CompanyId });
Note the Claim Type which now starts with "CustomClaim.".
Then in re-validation I do the following:
public async Task<ClaimsIdentity> GenerateUserIdentityAsync(UserManager<ApplicationUser, string> manager, ClaimsIdentity CurrentIdentity)
{
// Note the authenticationType must match the one defined in CookieAuthenticationOptions.AuthenticationType
var userIdentity = await manager.CreateIdentityAsync(this, DefaultAuthenticationTypes.ApplicationCookie);
// Re validate existing Claims here
foreach (var Claim in CurrentIdentity.FindAll(i => i.Type.StartsWith("CustomClaim.")))
{
userIdentity.AddClaim(new Claim(Claim.Type, Claim.Value));
// TODO devo testare perché va in loop la pagina Err500 per cui provoco volontariamente la duplicazioen delle Claims
//userIdentity.AddClaims(CurrentIdentity.Claims);
}
return userIdentity;
}
userIdentity does not contain the Custom Claims, while CurrentIdentity does contain both, but the only one I have to "re attach" to the current Identity are my custom one.
So far it is working fine, so I'll mark this as teh answer.
Hope it helps !
Lorenzo
Ohh lord i got tired of trying to get this to work, i just modified the SecurityStampValidator to take a context that i could pull the Identity out of to update accordingly in my User class. as far as i can tell there is no way to directly extend it. Updating claims from manager.CreateIdentityAsync(this, DefaultAuthenticationTypes.ApplicationCookie); had no affect using GenerateUserIdentityAsync
var validator = MySecurityStampValidator
.OnValidateIdentity<ApplicationUserManager, ApplicationUser, Guid>(
validateInterval: TimeSpan.FromSeconds(2),
regenerateIdentityCallback: (manager, user, claims) => user.UpdateUserIdentityAsync(claims),
getUserIdCallback: (id) => id.GetUserGuid());
var cookieAuthenticationOptions = new CookieAuthenticationOptions
{
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
Provider = new CookieAuthenticationProvider
{
// Not called on signin
OnValidateIdentity = validator
}
};
And then copied the owin class but added the context to it that would be passed into my regenerateIdentityCallback
static class MySecurityStampValidator
{
public static Func<CookieValidateIdentityContext, Task> OnValidateIdentity<TManager, TUser, TKey>(
TimeSpan validateInterval,
Func<TManager, TUser, ***ClaimsIdentity***, Task<ClaimsIdentity>> regenerateIdentityCallback,
Func<ClaimsIdentity, TKey> getUserIdCallback)
where TManager : UserManager<TUser, TKey>
where TUser : class, IUser<TKey>
where TKey : IEquatable<TKey>
{
......
And then in my user i just
public override async Task<ClaimsIdentity> UpdateUserIdentityAsync(ClaimsIdentity userIdentity)
{
userIdentity.RemoveClaim(CustomClaimTypes.CLAIM1);
userIdentity.RemoveClaim(CustomClaimTypes.CLAIM2);
if (Access1Service.GetService().UserHasAccess(Id))
{
userIdentity.AddClaim(new Claim(CustomClaimTypes.CLAIM1, "1"));
}
if (Access2Service.GetService().UserHasAccess(Id))
{
userIdentity.AddClaim(new Claim(CustomClaimTypes.CLAIM2, "1"));
}
return userIdentity;
}

How to get user context during Web Api calls?

I have an web front end calling an ASP Web Api 2 backend. Authentication is managed with ASP Identity. For some of the controllers I'm creating I need to know the user making the call. I don't want to have to create some weird model to pass in including the user's identity (which I don't even store in the client).
All calls to the API are authorized using a bearer token, my thought is the controller should be able to determine the user context based on this but I do not know how to implement. I have searched but I don't know what I'm searching for exactly and haven't found anything relevant. I'm going for something like...
public async Task<IHttpActionResult> Post(ApplicationIdentity identity, WalkthroughModel data)
Update
I found the below which looked very promising... but the value is always null! My controller inherits from ApiController and has an Authorize header.
var userid = User.Identity.GetUserId();
Update 2
I have also tried all of the solutions in Get the current user, within an ApiController action, without passing the userID as a parameter but none work. No matter what I am getting an Identity that is valid and auth'd, but has a null UserID
Update 3
Here's where I'm at now.
[Authorize]
[Route("Email")]
public async Task<IHttpActionResult> Get()
{
var testa = User.Identity.GetType();
var testb = User.Identity.GetUserId();
var testc = User.Identity.AuthenticationType;
var testd = User.Identity.IsAuthenticated;
return Ok();
}
testa = Name: ClaimsIdentity,
testb = null,
testc = Bearer,
testd = true
The user is obviously authenticated but I am unable to retrieve their userID.
Update 4
I found an answer, but I'm really unhappy with it...
ClaimsIdentity identity = (ClaimsIdentity)User.Identity;
string username = identity.Claims.First().Value;
That gets me the username without any db calls but it seems very janky and a pain to support in the future. Would love if anyone had a better answer.
What if I need to change what claims are issued down the road? Plus any time I actually need the user's id I have to make a db call to convert username to ID
A common approach is to create a base class for your ApiControllers and take advantage of the ApplicationUserManager to retrieve the information you need. With this approach, you can keep the logic for accessing the user's information in one location and reuse it across your controllers.
public class BaseApiController : ApiController
{
private ApplicationUser _member;
public ApplicationUserManager UserManager
{
get { return HttpContext.Current.GetOwinContext().GetUserManager<ApplicationUserManager>(); }
}
public string UserIdentityId
{
get
{
var user = UserManager.FindByName(User.Identity.Name);
return user.Id;
}
}
public ApplicationUser UserRecord
{
get
{
if (_member != null)
{
return _member ;
}
_member = UserManager.FindByEmail(Thread.CurrentPrincipal.Identity.Name);
return _member ;
}
set { _member = value; }
}
}
I use a custom user authentication (I dont use AspIdentity because my existing user table fields was far different from IdentityUser properties) and create ClaimsIdentity passing my table UserID and UserName to validate my bearer token on API calls.
public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
{
User user;
try
{
var scope = Autofac.Integration.Owin.OwinContextExtensions.GetAutofacLifetimeScope(context.OwinContext);
_service = scope.Resolve<IUserService>();
user = await _service.FindUserAsync(context.UserName);
if (user?.HashedPassword != Helpers.CustomPasswordHasher.GetHashedPassword(context.Password, user?.Salt))
{
context.SetError("invalid_grant", "The user name or password is incorrect.");
return;
}
}
catch (Exception ex)
{
context.SetError("invalid_grant", ex.Message);
return;
}
var properties = new Dictionary<string, string>()
{
{ ClaimTypes.NameIdentifier, user.UserID.ToString() },
{ ClaimTypes.Name, context.UserName }
};
var identity = new ClaimsIdentity(context.Options.AuthenticationType);
properties.ToList().ForEach(c => identity.AddClaim(new Claim(c.Key, c.Value)));
var ticket = new AuthenticationTicket(identity, new AuthenticationProperties(properties));
context.Validated(ticket);
context.Request.Context.Authentication.SignIn(identity);
}
And how I use the ClaimsIdentity to retrieve my User table details on User ApiController Details call.
[HostAuthentication(DefaultAuthenticationTypes.ExternalBearer)]
[Route("Details")]
public async Task<IHttpActionResult> Details()
{
var user = await _service.GetAsync(RequestContext.Principal.Identity.GetUserId<int>());
var basicDetails = Mapper.Map<User, BasicUserModel>(user);
return Ok(basicDetails);
}
Notice the
ClaimTypes.NameIdentifier = GetUserId() and ClaimTypes.Name = GetUserName()

How can I implement/Create role based custom user authentication in asp.net MVC 3

How can I implement role based custom user authentication in asp.net MVC 3. Consider I have two table UserInfo(UserId, UserName, Password,RoleId) and Role(RoleId, RoleName).
I want to validate user from Database(UserInfo Table) and also want to retrieve roles from that table. And want to use like
[Authorize(Roles="Admin")]
Need your help and idea....
You could use a custom authorize attribute and store the roles in the user data part of the authentication cookie. So for example inside your LogOn method once you have verified the credentials you could retrieve the roles for the given user from your database and store them into the user data:
// TODO: fetch roles from your database based on the username
var roles = "Admin|SomeRole";
var ticket = new FormsAuthenticationTicket(
1,
username,
DateTime.Now,
DateTime.Now.AddMilliseconds(FormsAuthentication.Timeout.TotalMilliseconds),
false,
roles
);
var encryptedTicket = FormsAuthentication.Encrypt(ticket);
var authCookie = new HttpCookie(FormsAuthentication.FormsCookieName, encryptedTicket)
{
Domain = FormsAuthentication.CookieDomain,
HttpOnly = true,
Secure = FormsAuthentication.RequireSSL,
};
// Emit the authentication cookie which in addition to the username will
// contain the roles in the user data part
Response.AppendCookie(authCookie);
Then you could write a custom authorize attribute which will be used to read the authentication cookie and extract the roles information:
public class MyAuthorizeAttribute : AuthorizeAttribute
{
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
if (httpContext.User.Identity.IsAuthenticated)
{
var authCookie = httpContext.Request.Cookies[FormsAuthentication.FormsCookieName];
if (authCookie != null)
{
var ticket = FormsAuthentication.Decrypt(authCookie.Value);
var identity = new GenericIdentity(httpContext.User.Identity.Name);
var roles = (ticket.UserData ?? string.Empty).Split('|');
httpContext.User = new GenericPrincipal(identity, roles);
}
}
return base.AuthorizeCore(httpContext);
}
}
Now all that's left is to decorate your controllers/actions with this new attribute:
[MyAuthorize(Roles = "Admin")]
public ActionResult Foo()
{
...
}
UPDATE:
As requested in the comments section here's how you could override the HandleUnauthorizedRequest method in the custom authorize attribute so that if the user is not authorized to access a given action he is redirected to some error view instead of the login page:
protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
filterContext.Result = new ViewResult
{
ViewName = "~/Views/Shared/Unauthorized.cshtml"
};
}

Resources