Null Context.User - c#

When I try to access Context.User in my Hub I keep getting Context.User is null errors. I have tried moving app.MapSignalR() under the ConfigureAuth() but that causes SignalR not to map at all. I'm not sure where I need to pass my cookie to SignalR at.
SignalR is working in my app for sending messages to all users I just can't get the OnConnect override to work without the Context.User
Startup.CS
public partial class Startup
{
public void Configuration(IAppBuilder app)
{
app.MapSignalR();
ConfigureAuth(app);
}
}
Startup.Auth.cs
public partial class Startup
{
private static string clientId = ConfigurationManager.AppSettings["ida:ClientId"];
private static string appKey = ConfigurationManager.AppSettings["ida:ClientSecret"];
private static string aadInstance = ConfigurationManager.AppSettings["ida:AADInstance"];
private static string tenantId = ConfigurationManager.AppSettings["ida:TenantId"];
private static string postLogoutRedirectUri = ConfigurationManager.AppSettings["ida:PostLogoutRedirectUri"];
private static string authority = aadInstance + tenantId;
public void ConfigureAuth(IAppBuilder app)
{
app.MapWhen(context => !IsDataPath(context.Request), appBuilder =>
{
appBuilder.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
appBuilder.UseCookieAuthentication(new CookieAuthenticationOptions());
appBuilder.UseOpenIdConnectAuthentication(
new OpenIdConnectAuthenticationOptions
{
//AuthenticationMode = AuthenticationMode.Passive,
ClientId = clientId,
Authority = authority,
PostLogoutRedirectUri = postLogoutRedirectUri,
Notifications = new OpenIdConnectAuthenticationNotifications()
{
SecurityTokenValidated = (context) =>
{
var claimsIdentity = context.AuthenticationTicket.Identity;
CleanupClaims(claimsIdentity);
AddHboClaims(claimsIdentity);
context.AuthenticationTicket.Properties.ExpiresUtc = DateTime.Now.AddDays(1).ToUniversalTime();
return Task.CompletedTask;
},
AuthenticationFailed = (context) =>
{
if (context.Exception.Message.StartsWith("OICE_20004") || context.Exception.Message.Contains("IDX10311"))
{
context.SkipToNextMiddleware();
return Task.FromResult(0);
}
return Task.FromResult(0);
},
}
});
});
}
private bool IsDataPath(Microsoft.Owin.IOwinRequest request)
{
return request.Path.Value.StartsWith("/data");
}
private void CleanupClaims(ClaimsIdentity claimsIdentity)
{
//Remove long unecessary claim types to make the cookie smaller
claimsIdentity.RemoveClaim(ClaimTypes.Surname);
claimsIdentity.RemoveClaim(ClaimTypes.GivenName);
claimsIdentity.RemoveClaim("onprem_sid");
claimsIdentity.RemoveClaim("http://schemas.microsoft.com/identity/claims/tenantid");
claimsIdentity.RemoveClaim("http://schemas.microsoft.com/claims/authnmethodsreferences");
claimsIdentity.RemoveClaim("ipaddr");
}
private void AddHboClaims(ClaimsIdentity claimsIdentity)
{
var depResolver = AutofacDependencyResolver.Current;
var permissionRespository = (IUserPermissionsRepository)depResolver.GetService(typeof(IUserPermissionsRepository));
var emailClaim = claimsIdentity.FindFirst(ClaimTypes.Upn);
var userPermissions = permissionRespository.GetPermissionForUser(emailClaim.Value);
foreach (var permission in userPermissions)
{
claimsIdentity.AddClaim(HboClaimsNames.Permission, ((int)permission).ToString());
}
var db = (Database.HboDbContext)depResolver.GetService(typeof(Database.HboDbContext));
var resource = db.Resources.SingleOrDefault(r => r.HmbEmail == emailClaim.Value);
if (resource != null)
{
//if (resource.IsActive)
//{
claimsIdentity.AddClaim(HboClaimsNames.ResourceId, resource.Id.ToString());
//}
//else
//{
// var ex = new Exception("Inactive user attempting to log into HBO: " + emailClaim.Value);
// Elmah.ErrorSignal.FromCurrentContext().Raise(ex);
//
//}
}
else
{
var ex = new Exception("User attempting to log into HBO that is not in Db: " + emailClaim.Value);
Elmah.ErrorSignal.FromCurrentContext().Raise(ex);
}
}
}

You need to change this:
public void Configuration(IAppBuilder app)
{
app.MapSignalR();
ConfigureAuth(app);
}
To this:
public void Configuration(IAppBuilder app)
{
ConfigureAuth(app);
}
And call MapSignalR() inside your ConfigureAuth()
The reason you can't just call MapSignalR() after ConfigureAuth() is due to this line:
app.MapWhen(context => !IsDataPath(context.Request), appBuilder =>
The source of the problem lies in the fact that the MapWhen() method branches the request pipeline and if you just call app.MapSignalR() in the Owin Startup you'll be initializing SignalR in the wrong request pipeline.
So your ConfigureAuth() method should look something like:
public void ConfigureAuth(IAppBuilder app)
{
app.MapWhen(context => !IsDataPath(context.Request), appBuilder =>
{
appBuilder.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
appBuilder.UseCookieAuthentication(new CookieAuthenticationOptions());
appBuilder.UseOpenIdConnectAuthentication(
new OpenIdConnectAuthenticationOptions
{
ClientId = clientId,
Authority = authority,
PostLogoutRedirectUri = postLogoutRedirectUri,
Notifications = new OpenIdConnectAuthenticationNotifications()
{
SecurityTokenValidated = (context) =>
{
...
},
AuthenticationFailed = (context) =>
{
...
},
}
});
appBuilder.MapSignalR();
});
}

Related

c# Web API 500 error from method after adding authentication

I have been trying to fix this all day. I am making a test API to practice my development. I tried adding a bearer authentication error and now none of the methods work.
namespace WebApiTest
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; set; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddLogging(loggingBuilder =>
{
loggingBuilder.AddConsole();
});
// Register the Swagger generator, defining 1 or more Swagger documents
services.AddControllers();
services.AddSwaggerGen(c =>
{
c.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme()
{
Name = "Authorization",
Type = SecuritySchemeType.Http,
Scheme = "Bearer",
BearerFormat = "JWT",
In = ParameterLocation.Header,
Description = "JWT Authorization header. \r\n\r\n Enter the token in the text input below."
});
c.OperationFilter<AddAuthorizationHeaderParameterOperationFilter>();
});
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ILogger<Startup> logger)
{
try
{
app.UseSwagger();
app.UseSwaggerUI(c =>
{
c.SwaggerEndpoint("/swagger/v1/swagger.json", "Web API Test");
});
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseHttpsRedirection();
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
catch (Exception ex)
{
logger.LogError(ex, "An error occurred while configuring the application.");
throw;
}
}
}
public class AddAuthorizationHeaderParameterOperationFilter : IOperationFilter
{
public void Apply(OpenApiOperation operation, OperationFilterContext context)
{
var authAttributes = context.MethodInfo.DeclaringType.GetCustomAttributes(true)
.Union(context.MethodInfo.GetCustomAttributes(true))
.OfType<AuthorizeAttribute>();
if (authAttributes.Any())
{
operation.Security = new List<OpenApiSecurityRequirement>
{
new OpenApiSecurityRequirement
{
{
new OpenApiSecurityScheme
{
Reference = new OpenApiReference { Type = ReferenceType.SecurityScheme, Id = "Bearer" },
},
new string[] {}
}
}
};
}
}
}
}
namespace AzureWebApiTest.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class MainController : ControllerBase
{
private readonly LoginRequest loginInformation = new LoginRequest("username", "password");
[HttpPost("GetToken")]
public IActionResult GetToken([FromBody] LoginRequest loginRequest)
{
if (loginRequest == null)
{
return BadRequest("Bad Login Request");
}
if (loginRequest.Equals(loginInformation))
{
var token = GenerateBearerToken(loginRequest);
return Ok(new { token });
}
else
{
return Unauthorized("Incorrect Login Information");
}
}
[HttpGet("GetHello")]
[Authorize(AuthenticationSchemes = "Bearer")]
public IActionResult GetHello([FromQuery] string name)
{
try
{
var token = Request.Headers["Authorization"].ToString().Replace("Bearer ", "");
var tokenHandler = new JwtSecurityTokenHandler();
var tokenValidationParameters = new TokenValidationParameters
{
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes("secretKey"))
};
var claimsPrincipal = tokenHandler.ValidateToken(token, tokenValidationParameters, out var securityToken);
return Ok("Hello " + name);
}
catch (SecurityTokenExpiredException)
{
return Unauthorized("Token has expired.");
}
catch (SecurityTokenInvalidSignatureException)
{
return Unauthorized("Invalid token signature.");
}
catch (Exception)
{
return Unauthorized("Invalid token.");
}
}
private string GenerateBearerToken(LoginRequest loginRequest)
{
if (ValidateCredentials(loginRequest))
{
var tokenHandler = new JwtSecurityTokenHandler();
var key = Encoding.ASCII.GetBytes("secretKey");
var tokenDescriptor = new SecurityTokenDescriptor
{
Expires = DateTime.UtcNow.AddHours(1),
SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature)
};
var token = tokenHandler.CreateToken(tokenDescriptor);
return tokenHandler.WriteToken(token);
}
return null;
}
private bool ValidateCredentials(LoginRequest loginRequest)
{
if(loginRequest.Equals(loginInformation))
{
return true;
}
return false;
}
}
}
When I try the method in swagger, I am getting the response:
I've tried getting ChatGPT to fix it but I'm getting nowhere and it's going in circles. Anyone have any ideas?
Edit:
The Validate function returns false at the end, I changed it for testing purposes. Edited back.
You are trying to get authorization to work, but you lack a few things.
Authentication
services.AddAuthentication(..options..).AddJwtBearer(..options..)
You need to add authentication to the pipeline, by adding UseAuthentication() before UseAuthorization(), like:
app.UseAuthentication();
app.UseAuthorization();
You need to add / register the authorization service using
services.AddAuthorization(..options..);

IdentityServer4 Resource owner password and Win auth: unauthorized

in the last few days I've been reading IdentityServer4 docs and putting together my sample server + sample client using Resource owner password. Now I'd like to add Windows authentication (will be done via Active Directory) in parallel, so the client app (not a web app but a desktop app) could either prompt the user for credentials or login using Windows authentication via Active Directory.
The documentation about Windows Authentication explains how to configure IIS or HTTP.Sys, but what I want is to:
user opens the app
the app use single sign on to post a request to the web api to request token and refresh token
the web api uses windows authentication to validate the identity of the user and returns the token
I've tried to follow this answer, but it doesn't work (It returns unauthorized).
web api: Startup.cs
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddMvcCore()
.AddAuthorization()
.AddJsonFormatters();
services.AddAuthentication("Bearer")
.AddJwtBearer(options =>
{
// base-address of your identityserver
options.Authority = "http://localhost:5000";
options.RequireHttpsMetadata = false;
// name of the API resource
options.Audience = "api/user";
});
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
services.AddIdentityServer(options => { options.PublicOrigin = "http://localhost:5000"; options.MutualTls.Enabled = false; })
.AddExtensionGrantValidator<WinAuthGrantValidator>()
.AddDeveloperSigningCredential()
.AddTestUsers(Config.GetUsers())
.AddInMemoryApiResources(Config.GetApiResources())
.AddInMemoryClients(Config.GetClients())
.AddInMemoryIdentityResources(Config.GetIdentityResources());
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env,
ILogger<Startup> logger, IServer server)
{
app.Use(async (context, next) =>
{
context.Features.Get<IHttpMaxRequestBodySizeFeature>()
.MaxRequestBodySize = 10 * 1024;
var serverAddressesFeature =
app.ServerFeatures.Get<IServerAddressesFeature>();
var addresses = string.Join(", ", serverAddressesFeature?.Addresses);
logger.LogInformation($"Addresses: {addresses}");
await next.Invoke();
});
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
// Enable HTTPS Redirection Middleware when hosting the app securely.
//app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseCookiePolicy();
app.UseMvc();
app.UseIdentityServer();
app.UseAuthentication();
}
}
internal static class Config
{
public static List<TestUser> GetUsers()
{
return new List<TestUser>
{
new TestUser
{
SubjectId = "1",
Username = "alice",
Password = "password"
},
new TestUser
{
SubjectId = "2",
Username = "bob",
Password = "password"
}
};
}
public static IEnumerable<Client> GetClients()
{
return new List<Client>
{
// other clients omitted...
// resource owner password grant client
new Client
{
ClientId = "ro.client",
AllowedGrantTypes = GrantTypes.ResourceOwnerPassword,
ClientSecrets =
{
new Secret("secret".Sha256())
},
// AllowedScopes = { "api1" }
AllowedScopes = { "api/user" }
},
new Client
{
ClientId = "winauth",
AllowedGrantTypes = new List<string>{ "windows_auth" },
ClientSecrets =
{
new Secret("secret".Sha256())
},
// AllowedScopes = { "api1" }
AllowedScopes = { "api/user" }
}
};
}
internal static IEnumerable<ApiResource> GetApiResources()
{
return new List<ApiResource>
{
new ApiResource { Name = "api1",Scopes = new List<Scope> { new Scope { Name = "api1",
DisplayName = "Full access to API 2"} }, Enabled = true, ApiSecrets = new List<Secret>
{
new Secret("secret".Sha256())
}
},
new ApiResource { Name = "api/user",Scopes = new List<Scope> { new Scope { Name = "api/user",
DisplayName = "Full access to API 2"} }, Enabled = true, ApiSecrets = new List<Secret>
{
new Secret("secret".Sha256())
}
}};
}
public static List<IdentityResource> GetIdentityResources()
{
return new List<IdentityResource>
{
new IdentityResources.OpenId(),
new IdentityResources.Profile()
};
}
}
public class WinAuthGrantValidator : IExtensionGrantValidator
{
private readonly HttpContext httpContext;
public string GrantType => "windows_auth";
public WinAuthGrantValidator(IHttpContextAccessor httpContextAccessor)
{
httpContext = httpContextAccessor.HttpContext;
}
public async Task ValidateAsync(ExtensionGrantValidationContext context)
{
// see if windows auth has already been requested and succeeded
var result = await httpContext.AuthenticateAsync("Windows");
if (result?.Principal is WindowsPrincipal wp)
{
context.Result = new GrantValidationResult(wp.Identity.Name, GrantType, wp.Claims);
}
else
{
// trigger windows auth
await httpContext.ChallengeAsync("Windows");
context.Result = new GrantValidationResult { IsError = false, Error = null, Subject = null };
}
}
}
}
web API: Program.cs
public class Program
{
public static void Main(string[] args)
{
var isService = !(Debugger.IsAttached || args.Contains("--console"));
if (isService)
{
var pathToExe = Process.GetCurrentProcess().MainModule.FileName;
var pathToContentRoot = Path.GetDirectoryName(pathToExe);
Directory.SetCurrentDirectory(pathToContentRoot);
}
var builder = CreateWebHostBuilder(
args.Where(arg => arg != "--console").ToArray());
var host = builder.Build();
if (isService)
{
// To run the app without the CustomWebHostService change the
// next line to host.RunAsService();
host.RunAsCustomService();
}
else
{
host.Run();
}
}
public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.ConfigureLogging((hostingContext, logging) =>
{
logging.AddEventLog();
})
.ConfigureAppConfiguration((context, config) =>
{
// Configure the app here.
})
.UseStartup<Startup>()
.UseHttpSys(options =>
{
options.AllowSynchronousIO = true;
options.Authentication.Schemes = Microsoft.AspNetCore.Server.HttpSys.AuthenticationSchemes.Kerberos | Microsoft.AspNetCore.Server.HttpSys.AuthenticationSchemes.NTLM;
options.Authentication.AllowAnonymous = true;
options.MaxConnections = null;
options.MaxRequestBodySize = 30000000;
options.UrlPrefixes.Add("http://localhost:5000");
});
}
web API UserController.cs
[Route("api/[controller]")]
[Authorize(AuthenticationSchemes = "Bearer")]
[ApiController]
public class UserController : ControllerBase
{
// GET api/user
[HttpGet]
public ActionResult<IEnumerable<string>> Get()
{
return new string[] { User.Identity.Name, User.Identity.AuthenticationType };
}
}
client code:
using (var client = new HttpClient())
{
disco = await client.GetDiscoveryDocumentAsync(new DiscoveryDocumentRequest
{
Address = baseUrl,
Policy = { RequireHttps = false }
});
if (disco.IsError)
{
Console.WriteLine(disco.Error);
Console.ReadLine();
return;
}
var httpHandler = new HttpClientHandler
{
UseDefaultCredentials = true,
};
using (var client = new HttpClient())
{
// request token
TokenResponse tokenResponse = await client.RequestTokenAsync(new TokenRequest
{
GrantType = "windows_auth",
Address = disco.TokenEndpoint,
ClientId = "winauth",
ClientSecret = "secret"
});
if (tokenResponse.IsError)
{
Console.WriteLine(tokenResponse.Error);
Console.ReadLine();
return;
}
}
It returns unauthorized.
I found a solution to this: I need to configure a TestUser with SubjectId = MYDOMAIN\myusername then it worked.
The error was getting was overly confusing.

SignalR - AuthenticationManager.GetTypes() is empty?

Lets say I have a hub:
public class MyHub: Hub<IMyHub> {
public MyHub(){}
public Task DoWork(){
var principal = this.Context.User; // Currently WindowsIdentity as its not authenticated
var auth = new OwinContext(this.Context.Request.Environment).Authentication;
var types = auth.GetAuthenticationTypes(); // Empty list
// ....
}
}
If I execute same code inside an WebApi2 Controller the .GetAuthenticationTypes() would give me the correct result of pre'configured authProviders.
Any ideas why its not behaving like within an controller? Is that by design?
Update 1
Startup.cs
public class Startup
{
public void Configuration(IAppBuilder app)
{
var httpConfig = new HttpConfiguration();
// .. Ioc Registering Hubs
WebApiConfig.Register(httpConfig);
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
// Have also tried registering signalr here before auth
// app.MapSignalR("/signalR", new HubConfiguration() { .... });
app.UseOAuthIntrospection(options =>
{
//...
options.AuthenticationType = OAuthDefaults.AuthenticationType;
options.ClientId = clientId;
options.ClientSecret = clientSecret;
options.RequireHttpsMetadata = false;
options.AuthenticationMode = AuthenticationMode.Passive;
options.Events = new OAuthIntrospectionEvents()
{
OnRetrieveToken = context =>
{
// Getting token from QueryString passed from js app.
var token = context.Request.Query["Authorization"];
if (!string.IsNullOrWhiteSpace(token))
{
context.Token = token;
}
return Task.CompletedTask;
}
}
//...
};
app.UseCors(CorsOptions.AllowAll);
app.MapSignalR("/signalR", new HubConfiguration() { .... });
app.UseWebApi(httpConfig); // Have tried swapping these 0 effect
}
In your Owin startup part, you should configure app.MapSignalR(); before registering authentication.
public void Configuration(IAppBuilder app)
{
app.MapSignalR();//Configure it first
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
LoginPath = new PathString("/Home/Index")
});
}

Owin Authentication.SignIn not working

I try to use owin authentication manager to authenticate users but User.Identity.IsAuthenticated is still false.
Startup.cs
public partial class Startup
{
public void Configuration(IAppBuilder app)
{
app.MapSignalR();
}
}
Startup.Auth.cs
public partial class Startup
{
public static Func<UserManager<ApplicationUser>> UserManagerFactory { get; set; }
public Startup()
{
UserManagerFactory = () =>
{
var userManager = new UserManager<ApplicationUser>(new CustomUserStore());
return userManager;
};
}
public void ConfigureAuth(IAppBuilder app)
{
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
LoginPath = new PathString("/Account/Login"),
LogoutPath = new PathString("/Account/LogOff"),
ExpireTimeSpan = TimeSpan.FromDays(7)
});
}
}
Some part of authentication action:
private async Task SignInAsync(ApplicationUser user, bool isPersistent)
{
var authManager = return HttpContext.GetOwinContext().Authentication;
authManager.SignOut(DefaultAuthenticationTypes.ApplicationCookie);
var identity = await userManager.CreateIdentityAsync(user, DefaultAuthenticationTypes.ApplicationCookie);
authManager.SignIn(new AuthenticationProperties { IsPersistent = isPersistent }, identity);
}
The identity creates successfully but SignIn method doesn't sign in a user. What's wrong?
It's a very stupid mistake. I have forgotten to call ConfigureAuth method.
public partial class Startup
{
public void Configuration(IAppBuilder app)
{
ConfigureAuth(app); // <-- this
app.MapSignalR();
}
}

HttpStatusCodeResult(401) returns “200 OK”

Using ASP.NET MVC 5, I would like to return appropriate HTTP status code for different scenarios (401 for user is not authenticated, 403 when user has no right for some resource, etc.), then handle them in jQuery.
But the problem is, when I try to return 401, it always returns "200: OK". MVC 5 RC1 was giving "302: Found" instead of 401, so I could use a workaround (HttpStatusCodeResult(401) returns "302 Found").
But now I moved from MVC 5 RC1 to MVC 5 and this behaviour changed. Now it is always "200: OK". So my workaround is useless, of course I can't replace 200 with anything else.
public ActionResult My()
{
if (User.Identity.IsAuthenticated == false)
{
return new HttpStatusCodeResult(401, "User is not authenticated.");
// Returns "200: OK"
}
// ... other code ...
}
How to solve this?
The MVC 5+ Pipeline modifies 401 response codes.
Option 1 With .net 4.5
you can set HttpContext.Response.SuppressFormsAuthenticationRedirect to true.
e.g. in your custom AuthoriseAttribute.cs
protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
if (filterContext.HttpContext.Request.IsAjaxRequest())
{
filterContext.Result = new JsonResult
{
Data = "_Logon_",
JsonRequestBehavior = JsonRequestBehavior.AllowGet
};
filterContext.HttpContext.Response.StatusCode = (int)HttpStatusCode.Unauthorized;
filterContext.HttpContext.Response.SuppressFormsAuthenticationRedirect = true;
}
Option 2. If not using .net 4.5
public class SuppressFormsAuthenticationRedirectModule : IHttpModule
{
private static readonly object SuppressAuthenticationKey = new object();
public static void Register()
{
DynamicModuleUtility.RegisterModule(
typeof(SuppressFormsAuthenticationRedirectModule));
}
public static void SuppressAuthenticationRedirect(HttpContext context)
{
context.Items[SuppressAuthenticationKey] = true;
}
public static void SuppressAuthenticationRedirect(HttpContextBase context)
{
context.Items[SuppressAuthenticationKey] = true;
}
public void Init(HttpApplication context)
{
context.PostReleaseRequestState += OnPostReleaseRequestState;
context.EndRequest += OnEndRequest;
}
public void Dispose()
{
}
private void OnPostReleaseRequestState(object source, EventArgs args)
{
var context = (HttpApplication)source;
var response = context.Response;
var request = context.Request;
if (response.StatusCode == 401 && request.Headers["X-Requested-With"] == "XMLHttpRequest")
{
SuppressAuthenticationRedirect(context.Context);
}
}
private void OnEndRequest(object source, EventArgs args)
{
var context = (HttpApplication)source;
var response = context.Response;
if (context.Context.Items.Contains(SuppressAuthenticationKey))
{
response.TrySkipIisCustomErrors = true;
response.ClearContent();
response.StatusCode = 401;
response.RedirectLocation = null;
}
}
}
and in web.config
<modules>
<add name="SuppressFormsAuthenticationRedirectModule" type="SuppressFormsAuthenticationRedirectModule"/>
</modules>
See here for more info
Solution to the issue can be found in http://kevin-junghans.blogspot.in/2013/12/returning-401-http-status-code-on.html
You need to modify your Startup class like this:
public partial class Startup
{
private static bool IsAjaxRequest(IOwinRequest request)
{
IReadableStringCollection query = request.Query;
if ((query != null) && (query["X-Requested-With"] == "XMLHttpRequest"))
{
return true;
}
IHeaderDictionary headers = request.Headers;
return ((headers != null) && (headers["X-Requested-With"] == "XMLHttpRequest"));
}
// For more information on configuring authentication, please visit http://go.microsoft.com/fwlink/?LinkId=301864
public void ConfigureAuth(IAppBuilder app)
{
// Enable the application to use a cookie to store information for the signed in user
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
LoginPath = new PathString("/Account/Login"),
Provider = new CookieAuthenticationProvider
{
OnApplyRedirect = ctx =>
{
if (!IsAjaxRequest(ctx.Request))
{
ctx.Response.Redirect(ctx.RedirectUri);
}
}
}
});
// Use a cookie to temporarily store information about a user logging in with a third party login provider
app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);
}
}
I use simuler code that return 404. Your code could be:
public ActionResult My()
{
if (User.Identity.IsAuthenticated == false)
{
return new HttpUnauthorizedResult();
}
// ... other code ...
}
In Statup.cs I had to set AutomaticChallenge to false. Once I did that it stopped doing the URL redirect (which resulted in 200 status) and gave me the 401 status I desired.
public void ConfigureServices(IServiceCollection services)
{
services.AddIdentity<ApplicationUser, ApplicationRole>(options =>
{
options.Cookies.ApplicationCookie.AutomaticChallenge = false; //<#######
//...
})
.AddEntityFrameworkStores<ApplicationDbContext, int>()
.AddDefaultTokenProviders();
}
If you have set some cookie middleware:
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory, IAntiforgery antiforgery)
{
app.UseCookieAuthentication(new CookieAuthenticationOptions()
{
AuthenticationScheme = "__auth__",
LoginPath = "/account/login",
AccessDeniedPath = "/account/forbidden",
AutomaticAuthenticate = true,
AutomaticChallenge = false //<#######
});
}
For Identity middleware, redirect can be disabled by removing in LoginPath option in Startup.Auth.cs:
public void ConfigureAuth(IAppBuilder app)
{
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
...
LoginPath = new PathString("/Account/Login"), // Remove this line
});

Categories