ASP.NET MVC - HTTP Authentication Prompt - asp.net-mvc

Is it possible to make my application ask for username and password prompting for it before render a view?
Just like on twitter API to get information about your account:
http://twitter.com/account/verify_credentials.xml
So before render the view || file it asks you to insert you username and password, I think this is made directly on the server since the curl request is based on username:password as well like this:
curl -u user:password http://twitter.com/account/verify_credentials.xml
As I'm trying to build an API following the same structure I would like to know how I can do this on ASP.NET MVC C#. I've already used this on ruby rails and its pretty simple like:
before_filter :authenticate
def authenticate
authenticate_or_request_with_http_basic do |username, password|
username == "foo" && password == "bar"
end
I don't think that [Authorize] filter is the same since I believe it's just a redirection,
and it redirects you to the Accounts Internal Controller that is based on the accounts database, in this case I will use another database, specifically from a webservice and do the validation after the information is submitted.
But I need the action to require the user and pass credentials on its request.
Thanks in advance
UPDATE:
Actually to request a page that requires this authentication (i.e. Twitter)
I would have to declare this on its request
request.Credentials = new NetworkCredential("username", "password");
And this would reflect that prompted username and password.
So, it's exactly the same thing but from the other side, if it's possible to provide information to the authentication prompt on request, how could I require this authentication on the request instead?
So everytime somebody tries to make a request to my application on example:
http://myapplication/clients/verify_credentials
it should ask for a username and password with that server prompt
so to retrive information on curl for example it would be like this
curl -u user:password http://myapplication/clients/verify_credentials

Well, to require basic authentication you need to return 401 status code. But doing that will cause the current authentication module to execute its default unauthorized handler (for forms authentication, this means redirecting to login page).
I wrote an ActionFilterAttribte to see if I can get the behaviour you want when there's no authentication module installed in web.config.
public class RequireBasicAuthentication : ActionFilterAttribute {
public override void OnActionExecuting(ActionExecutingContext filterContext) {
var req = filterContext.HttpContext.Request;
if (String.IsNullOrEmpty(req.Headers["Authorization"])) {
var res = filterContext.HttpContext.Response;
res.StatusCode = 401;
res.AddHeader("WWW-Authenticate", "Basic realm=\"Twitter\"");
res.End();
}
}
}
And the controller action :
[RequireBasicAuthentication]
public ActionResult Index() {
var cred = System.Text.ASCIIEncoding.ASCII
.GetString(Convert.FromBase64String(
Request.Headers["Authorization"].Substring(6)))
.Split(':');
var user = new { Name = cred[0], Pass = cred[1] };
return Content(String.Format("user:{0}, password:{1}",
user.Name, user.Pass));
}
That action successfully prints the username and password I enter. But I really doubt that's the best way to do this. Do you have no choice except asking for username and password this way?

You really want to create a service and not a web application, based on what I have read. I am guessing here, but I think you picked ASP.NET MVC to take advantage of the routing and building the URL's the way you want? Correct me if I am wrong.
In my opinion the best way to solve the problem you are having is to build RESTful web services with WCF if you are returning data. This article should help you get started if you want to go this route.
Otherwise, you will need to go further up the stack for handling the request and authenticating it. If this is the case, I can help with providing more info and code.

I modified the çağdaş answer to put the whole logic inside my custom ActionFilter attribute.
public class BasicAuthenticationAttribute : ActionFilterAttribute
{
public string BasicRealm { get; set; }
protected string Username { get; set; }
protected string Password { get; set; }
public BasicAuthenticationAttribute(string username, string password)
{
this.Username = username;
this.Password = password;
}
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
var req = filterContext.HttpContext.Request;
var auth = req.Headers["Authorization"];
if (!String.IsNullOrEmpty(auth))
{
var cred = System.Text.ASCIIEncoding.ASCII.GetString(Convert.FromBase64String(auth.Substring(6))).Split(':');
var user = new { Name = cred[0], Pass = cred[1] };
if (user.Name == Username && user.Pass == Password) return;
}
var res = filterContext.HttpContext.Response;
res.StatusCode = 401;
res.AddHeader("WWW-Authenticate", String.Format("Basic realm=\"{0}\"", BasicRealm ?? "Ryadel"));
res.End();
}
}
It can be used to put under Basic Authentication a whole controller:
[BasicAuthenticationAttribute("your-username", "your-password",
BasicRealm = "your-realm")]
public class HomeController : BaseController
{
...
}
or a specific ActionResult:
public class HomeController : BaseController
{
[BasicAuthenticationAttribute("your-username", "your-password",
BasicRealm = "your-realm")]
public ActionResult Index()
{
...
}
}
NOTE: The above implementation requires the developer to manually insert the username and password as ActionFilter required parameters but can be easily extended to make it support any authorization mechanism (MembershipProvider, ASP.NET Identity, custom userbase on an external DBMS or file, etc.) by removing the custom constructor and modifying the OnActionExecuting method IF block accordingly.
For additional info, you can also read this post I wrote on my blog.

Here's the way that has worked for me. It's a little foot work but it will make IIS and MVC3 behave a lot more like all the other Basic Http authentication systems, like Apache...
Step 1.
Make sure "Basic Authentication" is installed for IIS.
( Example: Control Panel -> Programs and Features -> Turn Windows features on or off )
*I'm using Windows 7 at the moment and am not sure the exact path. [GOOGLE: installing basic authentication in IIS] should get you close.
Step 2.
Make sure Basic Authentication is enabled under your site. If you had to install this in the previous step you need to make sure you reset the IIS service and that all the app pools actually went down.
Step 3.
(Note: I am using MVC3, and feel this should work in most models, including ASP.Net, without a lot of fuss.)
In your project you will need to add the following classes:
public class ServicePrincipal : IPrincipal { // This answers the "What am I allowed to do" question
// In real life, this guy will contain all your user info
// and you can put what ever you like and retrieve it
// later via the HttpContext, on your application side.
// Some fun with casting will be required.
public static IPrincipal Default {
get {
return new ServicePrincipal {
Identity = new ServiceIdentity {
AuthenticationType = "Test",
IsAuthenticated = true,
Name = "Basic"
}
};
}
}
public IIdentity Identity { get; set; }
public bool IsInRole(string role) {
// If you want to use role based authorization
// e.g. [Authorize(Roles = "CoolPeople")]
// This is the place to do it and you can do
// anything from load info from a db or flat file
// or simple case statement...though that would
// be silly.
return true;
}
}
public class ServiceIdentity : IIdentity { // This answers the "Who Am I" Question
public string AuthenticationType { get; set; }
public bool IsAuthenticated { get; set; }
public string Name { get; set; }
}
public class ServiceModule : IHttpModule { // This is the module for IIS
public void Init(HttpApplication context) {
context.AuthenticateRequest += this.BasicAuthenticationRequest;
}
public void BasicAuthenticationRequest(object sender, EventArgs e) {
HttpApplication app = sender as HttpApplication;
if( !ServiceProvider.Authenticate(app.Context) ) {
// Total FAIL!
}
}
public void Dispose() {
// Clean up the mess, if needed.
}
}
public class ServiceProvider {
public static bool Authenticate( HttpContext context ) {
// For the example we are going to create a nothing user
// say he is awesome, pass him along through and be done.
// The heavy lifting of the auth process will go here
// in the real world.
HttpContext.Current.User = ServicePrincipal.Default;
return true;
}
}
Step 3a. [edit]
Here's the different libs you'll be "using"
using System.Security.Principal;
using System.Web;
Just wanted to throw those in. I hate it when folks leave them out. :)
Step 4.
Add the following to your web config. Please note I am including the surrounding structure, for example the "configuration" tag... It's just a road map, if you already have a "configuration" tag don't add the other or IIS gets upset with you.
<configuration>
<system.webServer>
<modules runAllManagedModulesForAllRequests="true">
<add name="ServiceCredentialModule" type="{Namespace}.ServiceModule"/>
</modules>
</system.webServer>
<configuration>
Please note that the Namespace in {Namespace}.ServiceModule is the Namespace you put the classes from Step 3 into.
...and that's pretty much it.

Related

Activity based Authorization in ASP.NET Core

We have an asp.net mvc application which I'm porting to aspnet core mvc.
In the old solution authentication is done using Windows authentication.
On top of that we have an "activity based authentication" (like http://ryankirkman.com/2013/01/31/activity-based-authorization.html); a user is connected to roles and the roles are connected to rights. The users roles and corresponding rights is stored in a separate application that serves as authorization service for our application and handful of other systems.
A query to the authorization service api for the rights of user "Jon Doe" would get a response like this:
{
Email:"Jon.Doe#acme.com",
FirstName:"Jon",
LastName:"Doe",
Resources:
[
"CanAccessWebApplication",
"CanCopyAppointment",
"CanEditAppointment",
"CanEditContact",
"CanSaveContact"
...
]
Alias:"1234567",
UserId:"1234"
}
In our current application these rights are checked using attributes (that we have implemented our selves) on the controller methods:
public ContactController
{
[ActionUserAccess("CanSaveContact")]
public ActionResult SaveContact
{
...
}
}
The current legacy implementation of the ActionUserAccessAttribute filter looks like this:
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class)]
public sealed class ActionUserAccessAttribute : ActionFilterAttribute
{
private readonly string _accessRight;
public ActionUserAccessAttribute(string accessRight)
{
_accessRight = accessRight;
}
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
if (!filterContext.HttpContext.User.Identity.IsAuthenticated)
{
throw new InvalidOperationException("ActionUserAccessAttribute can not be used for controllers or actions configured for anonymous access");
}
base.OnActionExecuting(filterContext);
var securityService = ContainerResolver.Container.GetInstance<ISecurityService>();
var hasResource = securityService.HasAccess(_accessRight);
if (!hasResource)
{
filterContext.Result =
new HttpStatusCodeResult(
403,
string.Format(
"User {0} is not authorized to access the resource:'{1}' ",
filterContext.HttpContext.User.Identity.Name,
_accessRight));
}
}
}
}
Porting the attribute/filter to aspnetcore seems quite straightforward, but according to this answer https://stackoverflow.com/a/31465227/1257728 by "asp.net security person" #blowdart we shouldn't.
If not porting the custom filter to aspnetcore, what would be the best fit to implement here?
Maybe we could use the Role based authentication https://learn.microsoft.com/en-us/aspnet/core/security/authorization/roles?
We could create a middleware that populates the users access rights from the authorization service api and flatten the rights and add them as ClaimTypes.Role to the users' ClaimsIdentity ? Then we would use on the method above like:
[Authorize(Roles = "CanSaveContact")]
public ActionResult Save()
The misfit of this approach is that this is not really about roles, but more about the access rights.
I've also looked at the Policy based authorization:
https://learn.microsoft.com/en-us/aspnet/core/security/authorization/policies
Which could look like this in the controller:
[Authorize(Policy = "CanSaveContact")]
public ActionResult Save()
But as I read the code in microsoft's policy based example above I would then have to add all available access rights that exists in the security service api as policies in the ConfigureService method of the Startup class to be able to use them. I think seems awkward (pseudo code):
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
IEnumerable<string> allAccessRights = _securtiyService.GetAllAccessRights();
services.AddAuthorization(options =>
{
foreach(var accessRight in allAccessRights)
{
options.AddPolicy(accessRight, policy => policy.Requirements.Add(new AccessRightRequirement(accessRight));
}
});
services.AddSingleton<IAuthorizationHandler, AccessRightHandler>();
}
The AccessRightHandler would then be resposible to validate the access right for the user. Writing an AccessRightHandler is ok, but it seems unnecessary to have to add all the rights as policies.
What would be the best approach to implement this kind of authorization in our aspnetcore application?
Great question, and I think a number of people would have the same problem upgrading to ASP.NET Core.
Barry Dorrans (#blowdart) is absolutely correct, you shouldn't write your own custom authorize attributes - Authorization in ASP.NET Core has been greatly improved, and you can definitely mould it to your needs.
It would of course greatly depend on your current application, and what roles do you have, so I'll make some assumptions based on the snippets you provided above.
Before I start, I REALLY recommend you read through the new Authorization docs for ASP.NET Core, as well as Barry Dorran's Authorization workshop on GitHub. I highly recommend you go through the latter, and he has a .NET Core 2.0 branch there as well.
Depending how you want to implement it, you could either go with Claims based authorization, or go resource based.
Looking at your roles, it seems like Resource based auth could actually work great in your case!
For example:
Identify possible operations (the operation Name is to be picked up from your Resources):
public static class Operations
{
public static OperationAuthorizationRequirement Access = new OperationAuthorizationRequirement { Name = "Access" };
public static OperationAuthorizationRequirement Copy = new OperationAuthorizationRequirement { Name = "Copy" };
public static OperationAuthorizationRequirement Edit = new OperationAuthorizationRequirement { Name = "Edit" };
public static OperationAuthorizationRequirement Save = new OperationAuthorizationRequirement { Name = "Save" };
public static OperationAuthorizationRequirement Delete = new OperationAuthorizationRequirement { Name = "Delete" };
}
Create a base resource authorization handler:
public abstract class BaseResourceAuthorizationHandler<TResource> : AuthorizationHandler<OperationAuthorizationRequirement, TResource>
{
private readonly string _resourceType;
public BaseResourceAuthorizationHandler(string resourceType)
{
_resourceType = resourceType;
}
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, OperationAuthorizationRequirement requirement, TResource resource)
{
if (context.User.HasClaim("Resources", $"Can{requirement.Name}{_resourceType}"))
{
context.Succeed(requirement);
}
return Task.CompletedTask;
}
}
Implement specific resource based handlers. The resources are binding objects in your application to entities in your Resources. This class will be the glue between your current resource roles, the Operations, and the authorization system in ASP.NET Core. These can also be extended to add extra logic for any specific resource types/operations
For example, for Appointments:
public class AppointmentAuthorizationHandler : BaseResourceAuthorizationHandler<Appointment>
{
public AppointmentAuthorizationHandler() : base("Appointment") { }
}
Which you then register:
services.AddSingleton<IAuthorizationHandler, AppointmentAuthorizationHandler>();
Then in your controllers:
public class AppointmentsController : Controller
{
IAppointmentsRepository _appointmentsRepository;
IAuthorizationService _authorizationService;
public AppointmentsController(IAppointmentsRepository appointmentsRepository,
IAuthorizationService authorizationService)
{
_appointmentsRepository = appointmentsRepository;
_authorizationService = authorizationService;
}
public IActionResult Edit(int id)
{
var appointment = _appointmentsRepository.Get(id);
if (appointment == null)
{
return new NotFoundResult();
}
if (!(await _authorizationService.AuthorizeAsync(User, appointment, Operations.Edit)))
{
return new ChallengeResult();
}
return View(appointment);
}
}
You can also do the same in views, to check whether the user is allowed to see the Edit button, for example:
#using Microsoft.AspNetCore.Authorization
#model IEnumerable<Appointment>
#inject IAuthorizationService AuthorizationService
<h1>Document Library</h1>
#foreach (var appointment in Model)
{
if (await AuthorizationService.AuthorizeAsync(User, appointment, Operations.Edit))
{
<p>#Html.ActionLink("Appointment #" + appointment.Id, "Edit", new { id = appointment.Id })</p>
}
}
P.S. Just to add a note - yes, you lose the ability to filter by attributes, but in the end it's better this way. First and foremost - you move away from String based roles, you request permissions based on an operation type and resource type. Secondly, you can handle permissions in a much better (and intelligent way), as well as combine multiple permission checks.
It looks more complex, but it's also MUCH more powerful :)
Going to play the devil's advocate here, and suggest an alternative to my other answer - this could be a simpler option based on #mortb's request, and could fit some people that are migrating from their current systems.
Based on your situation, the Policy based auth really wouldn't fit your usecase - it's a more powerful option, you're not really using any of it, other than checking for the existence of a Resource string from your API.
On the other hand, I wouldn't discard the Roles approach. The resource list you get from the external API isn't strictly resources, but at the same time it maps quite perfectly to your needs. At the end of the day, all you're trying to do is to check whether the user has one (or more) Resource access permissions for a specific request.
Like you mentioned on your post, you'd have to extend your authorization to populate the roles from your external API. Don't forget that your ClaimsIdentity has a RoleClaimType property, which marks the type of the claim used to store the roles. It'll usually be set to ClaimTypes.Role, but not always.
You could even go ahead, and create custom auth attributes, not unlike this:
public class AuthorizeAccessAttribute : AuthorizeAttribute
{
public AuthorizeAccessAttribute(string entity)
{
Roles = "CanAccess" + entity;
}
}
public class AuthorizeEditAttribute : AuthorizeAttribute
{
public AuthorizeEditAttribute(string entity)
{
Roles = "CanEdit" + entity;
}
}
So you could use it as follows:
[AuthorizeEdit("Appointment")]
public IActionResult Edit(int id)
{
return View();
}

non https VssConnection with access token - disable required secure connection?

using TFS in our internal network, want to programatically check in changes with an access token, but get this: InvalidOperationException Basic authentication requires a secure connection to the server. Is there a way to turn off requiring secure connection?
var basicCreds = new VssBasicCredential(string.Empty, BuildUnitTestConstants.AccessToken);
var connection = new VssConnection(new Uri(BuildUnitTestConstants.ProjectUri), basicCreds);
var sourceControlServer = connection.GetClient<TfvcHttpClient>();
Well, it is possible, albeit not recommended; I needed it as well, because internal IT department would not install TFS with HTTPS (sad story). Also, for testing scenarios it can come quite handy.
As always YMMV and I'm not responsible for what happens when you use it when you shouldn't ;-) You have been warned.
For one you could simply not use the .NET client API, but directly use HttpClient and manually put the PAT in the URL to access the REST API, e.g.:
http://<WHATEVER>:<BASE64PAT>#<instance>/_apis/...
(Hence that the tfx-cli works nicely with PATs and non-HTTPS TFS instances, most likely because it does just that internally, not using the .NET client API of course - it is a node.js thing.)
If you want to stay with the .NET client API, you can create your own credentials class like so:
using System;
using System.Linq;
using System.Net;
using Microsoft.VisualStudio.Services.Common;
namespace Utilities
{
/// <summary>
/// Same as VssBasicCredential, but doesn't throw when URL is a non SSL, i.e. http, URL.
/// </summary>
/// <inheritdoc cref="FederatedCredential"/>
internal sealed class PatCredentials : FederatedCredential
{
public PatCredentials()
: this((VssBasicToken)null)
{
}
public PatCredentials(string userName, string password)
: this(new VssBasicToken(new NetworkCredential(userName, password)))
{
}
public PatCredentials(ICredentials initialToken)
: this(new VssBasicToken(initialToken))
{
}
public PatCredentials(VssBasicToken initialToken)
: base(initialToken)
{
}
public override VssCredentialsType CredentialType => VssCredentialsType.Basic;
public override bool IsAuthenticationChallenge(IHttpResponse webResponse)
{
if (webResponse == null ||
webResponse.StatusCode != HttpStatusCode.Found &&
webResponse.StatusCode != HttpStatusCode.Found &&
webResponse.StatusCode != HttpStatusCode.Unauthorized)
{
return false;
}
return webResponse.Headers.GetValues("WWW-Authenticate").Any(x => x.StartsWith("Basic", StringComparison.OrdinalIgnoreCase));
}
protected override IssuedTokenProvider OnCreateTokenProvider(Uri serverUrl, IHttpResponse response)
{
return new BasicAuthTokenProvider(this, serverUrl);
}
private sealed class BasicAuthTokenProvider : IssuedTokenProvider
{
public BasicAuthTokenProvider(IssuedTokenCredential credential, Uri serverUrl)
: base(credential, serverUrl, serverUrl)
{
}
protected override string AuthenticationScheme => "Basic";
public override bool GetTokenIsInteractive => this.CurrentToken == null;
}
}
}
Then create your VssCredentials using this class:
var credentials = new PatCredentials("", personalAccessToken);
var connection = new VssConnection(serverUrl, credentials);
(Shameless plug I use it in my TfsInfoService).
Nowadays you don't need to use the workaround provided by #Christian.K
Simply set the following env variable:
VSS_ALLOW_UNSAFE_BASICAUTH=true
Source: code of Microsoft.VisualStudio.Services.Common.VssBasicCredential:
protected override IssuedTokenProvider OnCreateTokenProvider(
Uri serverUrl,
IHttpResponse response)
{
bool result;
if (serverUrl.Scheme != "https" && (!bool.TryParse(Environment.GetEnvironmentVariable("VSS_ALLOW_UNSAFE_BASICAUTH") ?? "false", out result) || !result))
throw new InvalidOperationException(CommonResources.BasicAuthenticationRequiresSsl());
return (IssuedTokenProvider) new BasicAuthTokenProvider(this, serverUrl);
}
To set the environment variable programatically:
Environment.SetEnvironmentVariable("VSS_ALLOW_UNSAFE_BASICAUTH", "true")
No this is not possible. The problem of enabling PAT over unsecured connections is that anyone could intercept the token easily and would be able to use it for their own purposes.
Either enable SSL on the TSF instance, this is highly recommended. Or use Windows Authentication to use a secure form of authentication to TFS over an unsecured channel.

Derived from AuthorizeAttribute but User.Identity.Name is null unless using AuthorizeAttribute

So I've created a custom authorize attribute I use in a few places that is derived from an abstract base class which is derived from AuthorizeAttribute:
CustomAuthorizeAttributeBase.cs
public abstract class CustomAuthorizeAttributeBase : AuthorizeAttribute
{
public abstract string GetUsers();
public abstract string GetRoles();
protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
if (filterContext.IsChildAction)
{
return;
}
filterContext.Result =
new RedirectToRouteResult(new RouteValueDictionary
{
{"controller", "NotAuthorized"},
{"action", "Index"},
});
}
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
if (GetUsers().IndexOf(httpContext.User.Identity.Name, StringComparison.CurrentCultureIgnoreCase) >= 0 ||
GetRoles().Split(',').Any(s => httpContext.User.IsInRole(s)))
{
return true;
}
return false;
}
}
AreaLevelReadonly.cs
public class AreaLevelReadOnly : CustomAuthorizeAttributeBase
{
public override string GetUsers()
{
return ConfigurationManager.AppSettings["AreaReadonlyUsers"];
}
public override string GetRoles()
{
return ConfigurationManager.AppSettings["AreaReadonlyRoles"];
}
}
I also have some fairly simple code that gets me the currently logged in user:
UserIdentity.cs
public class UserIdentity : IUserIdentity
{
public string GetUserName()
{
return HttpContext.Current.User.Identity.Name.Split('\\')[1];
}
}
However, when I add my AreaLevelReadonly attribute to my controllers, getUserName fails and returns an exception that Name is null. I agonized over it for about an hour before putting authorize attribute on there as well, at which point it magically started working again. So, what is so different on the implementation level that my attribute deriving from authorizeattribute doesn't cause the Name to be populated.
Note: Windows authentication is on for the area, and the code works, but I don't understand why the Readonly attribute isn't enough to trigger authorization and population of the HttpContext.Current.User.Identity.Name.
Edit: Working:
[AreaLevelReadonly]
[Authorize]
public class DeleteAreaDataController : Controller {
//etc
var username = _userIdentity.GetUserName(HttpContext);
//etc
}
Exception on name:
[AreaLevelReadonly]
public class DeleteAreaDataController : Controller {
//etc
var username = _userIdentity.GetUserName(HttpContext);
//etc
}
More likely than not, you're accessing User.Identity.Name before it's populated. By including the standard Authorize attribute, as well, your code is then only running after the user has been authorized already and User.Identity.Name has been populated.
EDIT
Sorry, I misunderstood where the code attempting to call User.Identity.Name was running. Based on the belief that it was happening in your custom attribute, I was suggesting that you're trying to access it too early. However, I now see that you're calling it in your controller (although an explanation of what happens in GetUserAccount(HttpContext) would have helped.)
Anyways, your custom attribute obviously adds extra conditions on whether a user is authorized or not. When you return false, there is no user. It's not a situation where the user is "logged in" but not allowed to see the page. It's either there or it isn't. So the user is failing authorization based on your custom attribute (User.Identity.Name is null) but is authorized when you include Authorize (User.Identity.Name has a value).
Long and short, your GetUserName or GetUserAccount or whatever code needs to account for when the user has failed authorization. Or, if the user shouldn't be failing authorization, you'll need to look into why your custom attribute isn't working. Though, either way, you should still account for User.Identity.Name being null.
Your custom attribute is probably reading User.Identity.Name before you check that the user is authenticated.
In other words, in IsAuthorized(), before you read User.Identity.Name, you should be doing something like this:
if (!user.Identity.IsAuthenticated)
{
// Your custom code...
return false;
}
The reason you need this is because Windows Authentication (at least for NTLM) is a 2-step negotiation process between the client and server (see https://support.citrix.com/article/CTX221693). There will be 2 requests - the first with no name, and the second with a name. You can test this yourself - the source code for AuthorizeAttribute is provided here. Copy/paste that into your code and put a breakpoint in IsAuthorized - you will see that the breakpoint is hit twice. First time, the name is null, second time, it's set to your username.
So I think the solution is to either check user.Identity.IsAuthenticated at the start of your method, if you need to run custom code (as shown above), or alternatively if you only need to return false, simply replace the above code with base.IsAuthorized() which should do it for you.

Extend AuthorizeAttribute to detect logged in non-user (How to handle user authorization)

Environment: ASP.NET MVC 4, Visual Studio 2012
The [Authorize] attribute verifies that the user has a valid login cookie, but it does NOT verify that the user actually exists. This would happen if a user is deleted while that user's computer still holds the persisted credentials cookie. In this scenario, a logged-in non-user is allowed to run a controller action marked with the [Authorize] attribute.
The solution would seem to be pretty simple: Extend AuthorizeAttribute and, in the AuthorizeCore routine, verify that the user exists.
Before I write this code for my own use, I'd like to know if someone knows of a ready-to-go solution to this gaping hole in the [Authorize] attribute.
You need a special authentication global action filter.
Solution to your problem is the following. You have to introduce the global action filter that will be executed before controller action is invoked. This event is named OnActionExecuting. And within this global action filter you can also handle the scenario that user have a valid auth cookie, but does not exists in persistence (DB) anymore (and you have to remove its cookie).
Here is the code example to get an idea:
public class LoadCustomPrincipalAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
CustomIdentity customIdentity;
if (HttpContext.Current.User.Identity.IsAuthenticated)
{
UserData userData = UserRepository.GetUserByName(HttpContext.Current.User.Identity.Name);
if (userData == null)
{
//TODO: Add here user missing logic,
//throw an exception, override with the custom identity with "false" -
//this boolean means that it have IsAuthenticated on false, but you
//have to override this in CustomIdentity!
//Of course - at this point you also remove the user cookie from response!
}
customIdentity = new CustomIdentity(userData, true);
}
else
{
customIdentity = new CustomIdentity(new UserData {Username = "Anonymous"}, false);
}
HttpContext.Current.User = new CustomPrincipal(customIdentity);
base.OnActionExecuting(filterContext);
}
}
Hope it helps to you!
Do not forget to register this action filter as a global one. You can do this like:
private static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new LoadCustomPrincipalAttribute());
}
Just to add this. Leave alone AuthorizeAttribute. It should work as it was meant. It simply check the HttpContext.Current.User.Identity.IsAuthenticated == true condition. There are situations that you would need to overide it, but this is not the one. You really need a proper user/auth handling before even AuthorizeAttribute kicks in.
Agreed with Peter. Here is what I did for an AngularJs app. Create an attribute that checks the lockout date. Change YourAppUserManager out with the correct one.
public class LockoutPolicyAttribute : ActionFilterAttribute
{
public override async Task OnActionExecutingAsync(HttpActionContext actionContext, CancellationToken cancellationToken)
{
if (HttpContext.Current.User.Identity.IsAuthenticated)
{
var now = DateTime.UtcNow;
var currentUserId = Convert.ToInt32(HttpContext.Current.User?.Identity?.GetUserId());
var user = await HttpContext.Current.GetOwinContext().GetUserManager<YourAppUserManager>().FindByIdAsync(currentUserId);
if (user?.LockedOutUntil >= now)
{
actionContext.Response = actionContext.Request.CreateErrorResponse((HttpStatusCode)423, "Account Lockout");
return;
}
}
base.OnActionExecuting(actionContext);
}
}
Then have an AngularJs intercept service for status code 423 to redirect to login page.
switch (response.status) {
case 423: //Account lockout sent by the server.
AuthService.logOut();
window.location.href = '/login';

Two step authentication in MVC?

We have an MVC app which has a custom forms authentication view/controller. The controller will verify things and then do a FormsAuthentication.RedirectFromLoginPage call.
At this point in the Global.asax we'll receive a Application_OnAuthenticateRequest call from where we'll get their Context.User information and make another call to gather information relevant to this account which we then store in their Context.User & System.Threading.Thread.CurrentPrincipal. We also do a little caching of this information since in our system retrieving what we need is expensive which leads to cache invalidation & re-retrieval of this information.
It seems a bit odd at this point that we've got these separated into separate calls. I'm almost wondering if the Login controller shouldn't be gathering the details as part of its authentication check and storing them. Then the Application_OnAuthenticateRequest can only worry about if the cache needs to be invalidated and the users details re-retrieved.
Or maybe there is some other way of handling this I don't even know about..?
You can do what you want in MVC by leveraging RedirectToRouteResult and a custom cache updating ActionFilter. This is called the PRG (Post-Redirect-Get) pattern. You are actually already doing this, but it gets a little confused, because what you are doing is a cross between the classic ASP.NET way of doing things and the MVC way of doing things. There's nothing wrong with your initial approach (provided it is working correctly), but to do the same sort of thing and have more control and understanding of how it works in the scheme of things you could do something like:
public class AuthenticationController :Controller
{
[HttpPost]
public RedirectToRouteResult Login(string username, string password)
{
//authenticate user
//store authentication info in TempData like
bool authenticated = true|false; // do your testing
if(authenticated)
{
TempData["MustUpdateCache"] = true | false;
return RedirectToAction("LoginSuccess", new{userId = membershipUser.UserId});
}
else
{
TempData["MustUpdateCache"] = true | false;
return RedirectToAction("Login");
}
}
[HttpGet, UpdateCache]
public ActionResult LoginSuccess(Guid userId, string url)
{
HttpContext.User = LoadUser(userId);
return View();
}
[HttpGet, UpdateCache]
public ViewResult Login()
{
return View();
}
}
public class UpdateCacheAttribute:ActionFilterAttribute
{
public override void OnResultExecuting(ResultExecutingContext filterContext)
{
var tempData = filterContext.Controller.TempData;
if (tempData.ContainsKey("MustUpdateCache") && (bool)tempData["MustUpdateCache"])
{
UpdateCache(filterContext);
}
}
void UpdateCache(ControllerContext controllerContext)
{
//update your cache here
}
}

Resources