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")
});
}
Related
I'm new to OWIN and ADFS. I'm trying to authenticate users from ADFS using OWIN middleware. But when i run the app and perform login, the return HttpContext.Current.GetOwinContext() is not initialized properly.
owin_middleware_startup.cs
public void Configuration(IAppBuilder app)
{
// For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=316888
ConfigureAuth(app);
}
public void ConfigureAuth(IAppBuilder app)
{
app.UseCookieAuthentication(
new CookieAuthenticationOptions
{
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie, // application cookie which is generic for all the authentication types.
LoginPath = new PathString("/login.aspx"), // redirect if not authenticated.
AuthenticationMode = AuthenticationMode.Passive
});
app.UseWsFederationAuthentication(
new WsFederationAuthenticationOptions
{
MetadataAddress = "https://adfs-server/federationmetadata/2007-06/federationmetadata.xml", //adfs meta data.
Wtrealm = "https://localhost/", //reltying party
Wreply = "/home.aspx" // redirect
});
app.SetDefaultSignInAsAuthenticationType(DefaultAuthenticationTypes.ApplicationCookie);
}
login.aspx.cs
private IAuthenticationManager AuthenticationManager
{
get { return HttpContext.Current.GetOwinContext().Authentication; }
}
protected void Page_Load(object sender, EventArgs e)
{
}
protected void loginSSObtn_Click(object sender, EventArgs e)
{
IdentitySignin("administrator");
}
private void IdentitySignin(string userName)
{
//Create list of claims for Identity
var claims = new List<Claim>();
claims.Add(new Claim(ClaimTypes.Name, userName));
var identity = new ClaimsIdentity(claims, DefaultAuthenticationTypes.ApplicationCookie);
AuthenticationManager.SignIn(new AuthenticationProperties()
{
AllowRefresh = true,
IsPersistent = true,
IssuedUtc = DateTime.UtcNow,
ExpiresUtc = DateTime.UtcNow.AddDays(2)
}, identity);
//Response.Redirect("/home.aspx");
}
My goal is to redirect to the ADFS login and authenticate the user. Highly appreciate any help. Thanks.
Found the issue, I had missed the RUN method - app.Run() in the middle-ware. This inserts the extension to the OWIN startup. And executes it for all the requests.
Fix :
public void Configuration(IAppBuilder app)
{
// For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=316888
ConfigureAuth(app);
}
public void ConfigureAuth(IAppBuilder app)
{
app.SetDefaultSignInAsAuthenticationType(DefaultAuthenticationTypes.ApplicationCookie);
app.UseCookieAuthentication(
new CookieAuthenticationOptions
{
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie, // application cookie which is generic for all the authentication types.
LoginPath = new PathString("/login.aspx"), // redirect if not authenticated.
AuthenticationMode = AuthenticationMode.Passive
});
app.UseWsFederationAuthentication(
new WsFederationAuthenticationOptions
{
AuthenticationType = "test auth",
MetadataAddress = "https://adfs-server/federationmetadata/2007-06/federationmetadata.xml", //adfs meta data.
Wtrealm = "https://localhost/", //reltying party
Wreply = "/home.aspx"//redirect
});
AuthenticateAllRequests(app, "test auth");
}
private static void AuthenticateAllRequests(IAppBuilder app, params string[] authenticationTypes)
{
app.Use((context, continuation) =>
{
if (context.Authentication.User != null &&
context.Authentication.User.Identity != null &&
context.Authentication.User.Identity.IsAuthenticated)
{
return continuation();
}
else
{
context.Authentication.Challenge(authenticationTypes);
return Task.Delay(0);
}
});
}
But if we want to execute the extensions/middle-wares only for some specific path then we can use app.Use() this is just one usage of it.
feel free to correct me if i'm wrong.
OwinStartup.cs
public class OwinStartup
{
internal static IDataProtectionProvider DataProtectionProvider { get; private set; }
public void Configuration(IAppBuilder app)
{
DataProtectionProvider = app.GetDataProtectionProvider();
var config = new HttpConfiguration();
SimpleInjectorConfig.Configure(app);
ConfigureOAuth(app);
WebApiConfig.Register(config);
app.UseCors(CorsOptions.AllowAll);
app.UseWebApi(config);
}
private static void ConfigureOAuth(IAppBuilder app)
{
app.CreatePerOwinContext(
() => (IDisposable)GlobalConfiguration.Configuration.DependencyResolver.GetService(
typeof(AppUserManager)));
var options = new OAuthAuthorizationServerOptions
{
TokenEndpointPath = new PathString("/Token"),
Provider = new AppAuthProvider(),
AllowInsecureHttp = true,
};
app.UseOAuthAuthorizationServer(options);
app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions());
}
}
SimpleInjectorConfig.cs
public static class SimpleInjectorConfig
{
public static void Configure(IAppBuilder app)
{
var container = new Container();
container.Options.DefaultScopedLifestyle = new AsyncScopedLifestyle();
//allows scoped instances to be resolved during OWIN request
app.Use(async (context, next) =>
{
using (AsyncScopedLifestyle.BeginScope(container))
{
await next();
}
});
container.Register<AppIdentityDbContext>(Lifestyle.Scoped);
container.Register<AppUserManager>(Lifestyle.Scoped);
container.Register(
() =>
container.IsVerifying
? new OwinContext().Authentication
: HttpContext.Current.GetOwinContext().Authentication, Lifestyle.Scoped);
container.Register<AppSignInManager>(Lifestyle.Scoped);
container.Verify();
GlobalConfiguration.Configuration.DependencyResolver =
new SimpleInjectorWebApiDependencyResolver(container);
}
}
So in my implemenation of OAuthAuthorizationServerProvider called AppAuthProvider Im trying to get instance of AppUserManager ( I need to find user ) using this code:
var manager = context.OwinContext.Get<AppUserManager>();
But dont know why I still get null. I really dont know what to do because everythings seems to be configured correctly. Any ideas ? Thanks !
I found a solution. Updated code below:
OwinStartup.cs
public class OwinStartup
{
internal static IDataProtectionProvider DataProtectionProvider { get; private set; }
public void Configuration(IAppBuilder app)
{
DataProtectionProvider = app.GetDataProtectionProvider();
var container = SimpleInjectorConfig.Configure();
//allows scoped instances to be resolved during OWIN request
app.Use(async (context, next) =>
{
using (AsyncScopedLifestyle.BeginScope(container))
{
await next();
}
});
var config = new HttpConfiguration
{
DependencyResolver = new SimpleInjectorWebApiDependencyResolver(container)
};
ConfigureOAuth(app, config);
WebApiConfig.Register(config);
app.UseCors(CorsOptions.AllowAll);
app.UseWebApi(config);
}
private static void ConfigureOAuth(IAppBuilder app, HttpConfiguration config)
{
app.CreatePerOwinContext(
() => (AppUserManager)config.DependencyResolver.GetService(
typeof(AppUserManager)));
var options = new OAuthAuthorizationServerOptions
{
TokenEndpointPath = new PathString("/Token"),
Provider = new AppAuthProvider(),
//TODO: Change in production.
AllowInsecureHttp = true,
};
app.UseOAuthAuthorizationServer(options);
app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions());
}
}
SimpleInjectorConfig.cs
public static class SimpleInjectorConfig
{
public static Container Configure()
{
var container = new Container();
container.Options.DefaultScopedLifestyle = new AsyncScopedLifestyle();
container.Register<AppIdentityDbContext>(Lifestyle.Scoped);
container.Register<AppUserManager>(Lifestyle.Scoped);
container.Register(
() =>
container.IsVerifying
? new OwinContext().Authentication
: HttpContext.Current.GetOwinContext().Authentication, Lifestyle.Scoped);
container.Register<AppSignInManager>(Lifestyle.Scoped);
container.Verify();
return container;
}
}
Maybe someone will use it.
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();
});
}
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();
}
}
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
});