How can I get OwinContext from SignalR Hub? - asp.net-mvc

I have an ASP.NET MVC 5 app that is written on the top of ASP.NET MVC 5 framework using c#.
In this app, I am using SignalR to create a WebSocket connection between my app and the user's browser.
In my SignalR hub, I want to be able to access the OwinContext object.
How can I access the OwinContext object from my hub?
This is what I tried
Context.Request.GetHttpContext().GetOwinContext();
However, this is giving me the following error
No owin.Environment item was found in the context.
I added the <add key="owin:AutomaticAppStartup" value="true" /> to my Web.Config file.
This is how Startup class look like
using Microsoft.Owin;
using Microsoft.Owin.Security.Cookies;
using Owin;
using System;
[assembly: OwinStartup(typeof(TestProject.App_Start.Startup))]
namespace TestProject.App_Start
{
public class Startup
{
public void Configuration(IAppBuilder app)
{
// For more information on how to configure your application, visit http://go.microsoft.com/fwlink/?LinkID=316888
ConfigureAuth(app);
// Any connection or hub wire up and configuration should go here
app.MapSignalR();
}
public void ConfigureAuth(IAppBuilder app)
{
// need to add UserManager into owin, because this is used in cookie invalidation
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = Settings.ApplicationCookie,
LoginPath = new PathString("/Login"),
Provider = new CookieAuthenticationProvider(),
CookieName = Settings.ApplicationCookieName,
CookiePath = Settings.CookiePath,
CookieHttpOnly = true,
SlidingExpiration = true,
ExpireTimeSpan = TimeSpan.FromHours(24),
});
}
}
}
UPDATED Case use explanation
In some case I need to update the user claims from inside the Hub. This requires to inform the Authentication Manager about the new claims. So I use the following method to update the ClaimsIdentity
private static void InformAuthManager(ClaimsIdentity identity, HttpContextBase context = null)
{
IAuthenticationManager authenticationManager;
if (context != null)
{
authenticationManager = context.GetOwinContext().Authentication;
}
else
{
authenticationManager = HttpContext.Current.GetOwinContext().Authentication;
}
var claims = new ClaimsPrincipal(identity);
var authProperties = new AuthenticationProperties() { IsPersistent = true };
authenticationManager.AuthenticationResponseGrant = new AuthenticationResponseGrant(claims, authProperties);
}

Related

How to setup MVC requests to use Oauth 2.0 access token?

I am working on an Asp.net Web API + MVC project. While creating the project, I selected 'Individual User Accounts' option for authentication. After login, the provider issues access token and I use this token in HTTP header of ajax calls and it works fine with Ajax calls. I also have MVC actions which are used to navigate to different pages. These actions are protected using [Authorize] attribute of System.web.MVC. While navigating to these actions authorization fails and I am redirected to login page. How do I configure Oauth 2.0 access token to be used in non ajax requests (MVC requests) also.
Startup.Auth.cs:
public partial class Startup
{
public static OAuthAuthorizationServerOptions OAuthOptions { get; private set; }
public static string PublicClientId { get; private set; }
// For more information on configuring authentication, please visit http://go.microsoft.com/fwlink/?LinkId=301864
public void ConfigureAuth(IAppBuilder app)
{
// Configure the db context and user manager to use a single instance per request
app.CreatePerOwinContext(ApplicationDbContext.Create);
app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create);
// 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("/Home/Login"),
LogoutPath = new PathString("/Account/LogOff"),
ExpireTimeSpan = TimeSpan.FromHours(1.0),
});
app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);
// Configure the application for OAuth based flow
PublicClientId = "self";
OAuthOptions = new OAuthAuthorizationServerOptions
{
TokenEndpointPath = new PathString("/Token"),
Provider = new ApplicationOAuthProvider(PublicClientId),
AuthorizeEndpointPath = new PathString("/api/Account/ExternalLogin"),
AccessTokenExpireTimeSpan = TimeSpan.FromDays(14),
AllowInsecureHttp = true // dont use this for production,
};
// Enable the application to use bearer tokens to authenticate users
app.UseOAuthBearerTokens(OAuthOptions);
}
WebApiConfig.cs:
public static void Register(HttpConfiguration config)
{
// Web API configuration and services
// Configure Web API to use only bearer token authentication.
config.SuppressDefaultHostAuthentication();
config.Filters.Add(new HostAuthenticationFilter(OAuthDefaults.AuthenticationType));
// Web API routes
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}
ApplicationOAuthProvider.cs:
public class ApplicationOAuthProvider : OAuthAuthorizationServerProvider
{
private readonly string _publicClientId;
public ApplicationOAuthProvider(string publicClientId)
{
if (publicClientId == null)
{
throw new ArgumentNullException("publicClientId");
}
_publicClientId = publicClientId;
}
public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
{
var userManager = context.OwinContext.GetUserManager<ApplicationUserManager>();
ApplicationUser user = await userManager.FindAsync(context.UserName, context.Password);
if (user == null)
{
context.SetError("invalid_grant", "The user name or password is incorrect.");
return;
}
ClaimsIdentity oAuthIdentity = await user.GenerateUserIdentityAsync(userManager,
OAuthDefaults.AuthenticationType);
ClaimsIdentity cookiesIdentity = await user.GenerateUserIdentityAsync(userManager,
CookieAuthenticationDefaults.AuthenticationType);
AuthenticationProperties properties = CreateProperties(user.UserName);
AuthenticationTicket ticket = new AuthenticationTicket(oAuthIdentity, properties);
context.Validated(ticket);
context.Request.Context.Authentication.SignIn(cookiesIdentity);
}
public override Task TokenEndpoint(OAuthTokenEndpointContext context)
{
foreach (KeyValuePair<string, string> property in context.Properties.Dictionary)
{
context.AdditionalResponseParameters.Add(property.Key, property.Value);
}
return Task.FromResult<object>(null);
}
public override Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context)
{
// Resource owner password credentials does not provide a client ID.
if (context.ClientId == null)
{
context.Validated();
}
return Task.FromResult<object>(null);
}
public override Task ValidateClientRedirectUri(OAuthValidateClientRedirectUriContext context)
{
if (context.ClientId == _publicClientId)
{
Uri expectedRootUri = new Uri(context.Request.Uri, "/");
if (expectedRootUri.AbsoluteUri == context.RedirectUri)
{
context.Validated();
}
}
return Task.FromResult<object>(null);
}
public static AuthenticationProperties CreateProperties(string userName)
{
IDictionary<string, string> data = new Dictionary<string, string>
{
{ "userName", userName }
};
return new AuthenticationProperties(data);
}
}
ASP.Net MVC is a server side web app technology and [Authorize] actions expect to receive a cookie rather than checking for a token.
Not sure if this is overridable and in this type of solution you often end up having to deal with an ugly mix of cookies / tokens / server side code / client side code.
A more modern / cleaner separation is to develop a single page app + REST API instead. Code tends to be simpler and it feels like a better fit for the requirements you mention.
To see what I mean, maybe have a quick browse of the UI code in this sample:
https://github.com/gary-archer/oauth.websample1
The repo links to some blog posts if it's an option you're interested in.

How to get Owin identity in Signalr hub

I have an application written in ASP.NET MVC using SignalR with Owin external authentication (steam).
Problem is that I can't obtain any identity information inside SignalR hub. Identity.Name returns empty string, Identity.Claims is empty.
Startup.cs
public class Startup
{
public void ConfigureAuth(IAppBuilder app)
{
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
LoginPath = new PathString("/Home/Index"),
AuthenticationMode = AuthenticationMode.Active
});
app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);
app.UseTwoFactorSignInCookie(DefaultAuthenticationTypes.TwoFactorCookie, TimeSpan.FromMinutes(5));
app.UseTwoFactorRememberBrowserCookie(DefaultAuthenticationTypes.TwoFactorRememberBrowserCookie);
app.UseSteamAuthentication("API KEY");
}
public void Configuration(IAppBuilder app)
{
ConfigureAuth(app);
app.MapSignalR();
}
}
In SteamCallBack
authenticateResult?.Identity.Claims
is not empty, it returns correct Identity.Name provided by Steam.
public async Task<ActionResult> SteamCallback()
{
....
var authenticateResult =
await HttpContext.GetOwinContext().Authentication.AuthenticateAsync("ExternalCookie");
var firstOrDefault = authenticateResult?.Identity.Claims.FirstOrDefault(claim => claim.Issuer == "Steam" && claim.Type.Contains("nameidentifier"));
...
}
Inside Hub all of the following are null/empty
var z = Context.User.Identity.Name;
var b = Context.User.Identity.AuthenticationType;
var x = ((ClaimsIdentity)Context.User.Identity).Claims.ToList();
I solve my problem. I forgot to sign in user using IAuthenticationManager.SignIn method.
Example use of that method:
var identity = new ClaimsIdentity(claims, DefaultAuthenticationTypes.ApplicationCookie);
HttpContext.GetOwinContext().Authentication.SignIn(new AuthenticationProperties()
{
AllowRefresh = true,
IsPersistent = true,
ExpiresUtc = DateTime.UtcNow.AddDays(7)
}, identity);

MVC 5.2 Cookie Sign In OWIN and Injecting Authenticated User Information

I am in a bit of a pickle with my MVC Application due to the way I have coded up (currently) the implementations of my application services and the way they are configured for dependency injection.
I'm looking to separate the layers of the application by following SOLID principles.
The problem is that in some of these services, the constructor requires an instance of IUserContext. IUserContext contains the various information about the logged in user and will be passed around a few different layers.
public class ProjectDataLoader : DataLoaderBase, IProjectDataLoader
{
public ProjectDataLoader(IMyDbContext dbContext, IUserContext userContext)
: base (dbContext, userContext)
{
}
...
public IEnumerable<ProjectViewModel> Find(string filter = "")
{
...
}
}
And an implementation of IUserContext:
public class AspNetUserContext : IUserContext
{
...
}
I could pass IUserContext on every method call but I feel it belongs in the constructor. But that is not the question here.
When I sign in from the login page via AccountController, MyAppSignInManager.SignInOrTwoFactor gets called via the OWIN pipeline. At this point I was creating a new instance of AspNetUserContext in the session:
HttpContext.Current.Session["UserContext"] = aspNetUserContext;
Now I have custom SignInManager implementation:
public class MyAppSignInManager : SignInManager<MyAppUser, string>
{
...
}
I have a custom IUserStore implementation:
public class MyAppUserStore : IUserPasswordStore<MyAppUser>,
IUserStore<MyAppUser>
{
...
}
All of the above have been hooked up for Dependency Injection with Simple Injector my choice of container.
public static class DependencyConfig
{
public static Container Initialize(IAppBuilder app)
{
Container container = GetInitializeContainer(app);
container.Verify();
DependencyResolver.SetResolver(
new SimpleInjectorDependencyResolver(container));
return container;
}
private static Container GetInitializeContainer(IAppBuilder app)
{
var container = new Container();
RegisterCommon(container);
RegisterRepositories(container);
RegisterDataLoaders(container);
RegisterAppServices(container);
RegisterMvc(app, container);
return container;
}
private static void RegisterCommon(Container container)
{
container.Register<IUserContext>(() =>
{
IUserContext context = null;
if (HttpContext.Current.Session == null)
context = new AspNetUserContext(Guid.Empty, Guid.Empty);
else
context = (IUserContext)HttpContext.Current.Session["UserContext"];
return context;
}, Lifestyle.Transient);
}
private static void RegisterRepositories(Container container)
{
container.RegisterPerWebRequest<IUserRepository>(() =>
new UserRepository(container.GetInstance<IMyApp4Context>()));
container.Register<IMyApp4Context>(() => new MyApp4Context(),
Lifestyle.Transient);
}
private static void RegisterDataLoaders(Container container)
{
container.Register<IProjectDataLoader, ProjectDataLoader>();
container.Register<ContractDataLoader>();
container.Register<DrawingDataLoader>();
container.Register<WeldDataLoader>();
}
private static void RegisterAppServices(Container container)
{
}
private static void RegisterMvc(IAppBuilder app, Container container)
{
container.RegisterSingle(app);
container.RegisterPerWebRequest<MyAppUserManager>();
container.RegisterPerWebRequest<SignInManager<MyAppUser, string>,
MyAppAppSignInManager>();
container.RegisterPerWebRequest(() =>
{
if (HttpContext.Current != null &&
HttpContext.Current.Items["owin.Environment"] == null &&
container.IsVerifying())
{
return new OwinContext().Authentication;
}
return HttpContext.Current.GetOwinContext().Authentication;
});
container.RegisterPerWebRequest<IUserStore<MyAppUser>>(() =>
new MyAppUserStore(container.GetInstance<IUserRepository>()));
app.UseOwinContextInjector(container);
container.RegisterMvcControllers(
Assembly.GetExecutingAssembly());
}
private static void InitializeUserManager(MyAppUserManager manager, IAppBuilder app)
{
manager.UserValidator =
new UserValidator<MyAppUser>(manager)
{
AllowOnlyAlphanumericUserNames = false,
RequireUniqueEmail = true
};
manager.PasswordValidator = new PasswordValidator()
{
RequiredLength = 6,
RequireNonLetterOrDigit = false,
RequireDigit = true,
RequireLowercase = true,
RequireUppercase = true,
};
IDataProtectionProvider dataProtectionProvider =
app.GetDataProtectionProvider();
if (dataProtectionProvider != null)
{
manager.UserTokenProvider =
new DataProtectorTokenProvider<MyAppUser>(
dataProtectionProvider.Create(purposes: new string[] { "ASP.NET Identity" }));
}
}
}
And also:
public partial class Startup
{
public void ConfigureAuth(IAppBuilder app, Container container)
{
app.CreatePerOwinContext(() => container.GetInstance<MyAppUserManager>());
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
LoginPath = new PathString(value: "/Account/Login"),
Provider = new CookieAuthenticationProvider
{
OnValidateIdentity = SecurityStampValidator.OnValidateIdentity<MyAppUserManager, MyAppUser>(
validateInterval: TimeSpan.FromMinutes(value: 30),
regenerateIdentity: (manager, user) =>
{
return user.GenerateUserIdentityAsync(manager);
})
}
});
app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);
app.UseTwoFactorSignInCookie(DefaultAuthenticationTypes.TwoFactorCookie, TimeSpan.FromMinutes(5));
app.UseTwoFactorRememberBrowserCookie(DefaultAuthenticationTypes.TwoFactorRememberBrowserCookie);
}
}
Then these are to be used in the controllers:
public class ProjectController : MyBaseContextController
{
public ProjectController(IProjectDataLoader loader)
: base(context)
{
...
}
}
My initial question was going to be how can I get MyAppUser after the cookie authentication has taken place. Maybe asking this is still valid.
The better question is to ask what I am trying to accomplish. Essentially what I want is to inject IUserContext into my services. This needs to be injected into the constructor of the various service implementations registered in my DI container. However, this instance won't be available until a user has logged in/authenticated.
NOTE: All of the user information is stored in SQL and I use Entity Framework to access all of this.
So given that once a user has authenticated by logging in via the login page via the MyAppSignInManager.SignInOrTwoFactor method and also by a cookie, how can I make my AspNetUserContext (IUserContext) instance available to my DI container?
NOTE: I just want to get the user information from the database once - rather that every call to the controllers where it is required.
"I just want to get the user information from the database once."
You should consider storing your required user data in claims.
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
LoginPath = new PathString(value: "/Account/Login"),
Provider = new CookieAuthenticationProvider
{
OnValidateIdentity = SecurityStampValidator.OnValidateIdentity<MyAppUserManager, MyAppUser>(
validateInterval: TimeSpan.FromMinutes(value: 30),
regenerateIdentity: (manager, user) =>
{
return user.GenerateUserIdentityAsync(manager);
})
}
})
The GenerateUserIdentityAsync method adds the core identity claims but you can override this and store custom claims that your services need. Then instead of passing in a IUserContext to your service you could pass in an IClaimsIdentity.
This would mean you don't have to query the database all the time to get the data you need. The claims would be automatically updated after the 30 minute interval as specified in your code.
Hope this helps.

Autofac in Asp.net MVC, WebApi, SignalR, Owin yet again

How to register in a good way one Autofac dependency resolver or resolvers if one not possible for Asp.net MVC, WebApi, SignalR working together with Owin? There are guidelines for each of them. But as stated below it does not seem to work. Here is the code which is somewhat bad as it uses different dependency resolves, someones static and they seem to have one reason to exists (so looks like code duplication).
public class Startup
{
// This two static resolvers does not look nice
public static IDependencyResolver SignalRDependencyResolver { get; private set; }
public static System.Web.Http.Dependencies.IDependencyResolver WebApiDependencyResolver { get; private set; }
public void Configuration(IAppBuilder app)
{
var httpConfiguration = new HttpConfiguration();
var container = BuildAutofacContainer();
var hubConfiguration =
new HubConfiguration
{
Resolver = new AutofacDependencyResolver(container),
EnableDetailedErrors = true
};
// The resolver to be used as here. Seems to be replaced by SignalR further?
// 1. http://stackoverflow.com/questions/20139127/signalr-sending-data-using-globalhost-connectionmanager-not-working/20202040#20202040
// 2. http://stackoverflow.com/questions/20561196/signalr-calling-client-method-from-outside-hub-using-globalhost-connectionmanage
SignalRDependencyResolver = hubConfiguration.Resolver;
DependencyResolver.SetResolver(new Autofac.Integration.Mvc.AutofacDependencyResolver(container));
WebApiDependencyResolver = new AutofacWebApiDependencyResolver(container);
// Why the following does not work (throws that needs parameterless constructor) ?
// so the static resolver used
// see http://docs.autofac.org/en/latest/integration/webapi.html#owin-integration
// httpConfiguration.DependencyResolver = new AutofacWebApiDependencyResolver(container);
app.UseAutofacMiddleware(container);
app.UseAutofacWebApi(httpConfiguration);
app.UseAutofacMvc();
app.UseWebApi(httpConfiguration);
app.MapSignalR("/signalr", hubConfiguration);
// AspNetIdentity hook:
app.UseCookieAuthentication(
new CookieAuthenticationOptions
{
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
LoginPath = new PathString("/Account/Login")
});
}
private static IContainer BuildAutofacContainer()
{
var builder = new ContainerBuilder();
// and http://autofac.readthedocs.org/en/latest/integration/mvc.html#using-plugin-assemblies
builder.RegisterModule<AutofacDalModule>();
builder.RegisterModule<AutofacDomainModule>();
builder.RegisterType<OperatorHubInternal>().As<IOperatorHubInternal>().SingleInstance();
RegistrationExtensions.RegisterControllers(builder, typeof(MvcApplication).Assembly);
builder.RegisterApiControllers(typeof(MvcApplication).Assembly).InstancePerRequest();
builder.RegisterHubs(Assembly.GetExecutingAssembly());
var mvcContainer = builder.Build();
return mvcContainer;
}
}

Owin running before Structure Map configuration

On an ASP.NET MVC 5 project using OWIN I have the following:
[assembly: OwinStartup(typeof(MvcProj.Site.OwinStartup), "Configure")]
namespace MvcProj.Site {
public partial class OwinStartup {
public void Configure(IAppBuilder application) {
UrlHelper url = new UrlHelper(HttpContext.Current.Request.RequestContext);
application.UseCookieAuthentication(new CookieAuthenticationOptions {
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
CookieSecure = CookieSecureOption.SameAsRequest,
LoginPath = new PathString(url.Action(MVC.User.SignIn())),
ReturnUrlParameter = "redirect"
});
application.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);
} // Configure
} // OwinStartup
}
As you can see I am defining the login path as follows:
LoginPath = new PathString(url.Action(MVC.User.SignIn())),
I get an error from StructureMap saying I do not have ITranslator defined ...
In fact it is defined but all my controllers are base on a BaseController:
public abstract class BaseController : Controller, ITranslator_ {
public readonly ITranslator _translator;
protected BaseController() {
_translator = ObjectFactory.Container.GetInstance<ITranslator>();
} // BaseController
public String _(String value) {
return _translator.Translate(value);
} // _
}
So what I think it happens is that Owin runs before my IoC code in global.asax Application Start.
If I remove the code line LoginPath = new PathString(url.Action(MVC.User.SignIn())) then everything works fine.
Could someone, please, tell me how to solve this?
Thank You,
Miguel
The Microsft.Owin.SystemWeb host uses the PreApplicationStartMethodAttribute to bootstrap itself which runs before your Application_Start method. This is why you're seeing the crash. You'll need to move your DI setup into the Startup class.
I've since switched from ASP.NET MVC to Nancy but your setup should be similar aside from the need to also setup a dependency resolver for MVC. For this you'll need to install StructureMap.MVC4 and then remove the StructuremapMvc class it adds since your setup code is now in the Startup class.
public class Startup
{
public void Configuration(IAppBuilder app)
{
var container = SetupStructureMap();
// sets up the mvc dependency resolver
DependencyResolver.SetResolver(new StructureMapDependencyResolver(container));
GlobalConfiguration.Configuration.DependencyResolver = new StructureMapDependencyResolver(container);
SetupAuth(app, container);
}
private static IContainer SetupStructureMap()
{
ObjectFactory.Initialize(x =>
{
// ...
});
return ObjectFactory.Container;
}
public static void SetupAuth(IAppBuilder app, IContainer container)
{
app.SetDataProtectionProvider(container.GetInstance<IDataProtectionProvider>());
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = Constants.AppAuthType,
CookieHttpOnly = true,
CookieName = "app.id",
LogoutPath = new PathString("/logout"),
Provider = container.GetInstance<ICookieAuthenticationProvider>(),
ReturnUrlParameter = string.Empty
});
}
}
My Startup class is loosely based on the one from JabbR.

Resources