I have problem with creating a session in application hosted using Owin. I have tried using RedisSession, but I didn't know how to configure it so it gave me an error.
I was looking for a solution for some time, tried different things and finally decided to ask here for help.
Scenario:
I'm logging in the application using HTTP POST request,
User login and password should be stored in session,
For each next GET/POST request which need previous login session is
empty (login and password are null).
Object HTTPContext is empty.
I'm using Ninject for dependency injections.
I tried something like that: Can OWIN middleware use the http session?
Does anybody have an idea how to store login data in Owin session?
Below is Owin configuration file, included in it things are from link posted above.
[assembly: OwinStartup(typeof(Service.Startup))]
namespace Service
{
public class Startup
{
public void Configuration(IAppBuilder appBuilder)
{
var config = new HttpConfiguration();
config.Routes.MapHttpRoute("DefaultApi", "api/{controller}/{action}/{id}", new { id = RouteParameter.Optional }
);
appBuilder.RequireAspNetSession();
appBuilder.UseNinjectMiddleware(CreateKernel).UseNinjectWebApi(config);
}
public static StandardKernel CreateKernel()
{
var kernel = new StandardKernel(new Module());
return kernel;
}
}
public static class AspNetSessionExtensions
{
public static IAppBuilder RequireAspNetSession(this IAppBuilder app)
{
app.Use((context, next) =>
{
// Depending on the handler the request gets mapped to, session might not be enabled. Force it on.
HttpContextBase httpContext = context.Get<HttpContextBase>(typeof(HttpContextBase).FullName);
httpContext.SetSessionStateBehavior(SessionStateBehavior.Required);
return next();
});
// SetSessionStateBehavior must be called before AcquireState
app.UseStageMarker(PipelineStage.MapHandler);
return app;
}
}
}
I've had some struggling with sessions as well.
Here is the solution, which works for me:
1) Add NuGet Microsoft.AspNetCore.Session
2) Call .AddSession on your IServiceCollection. Note, it might need configuration.
In my case it is:
3) Use your session. Keep in mind, that if there are no values set to a session, on each request SessionID is different.
So you'd have to add some value to a session. This is how it would stay the same across multiple requests.
And here is my session pinning middleware:
Hope it helps.
For anyone wanting to do this with ASP.net Framework, instead of Core, read on.
First, make sure Owin.Extensions is installed.
Next, add this code to your Owin Startup.cs before the middleware that you want to use session.
app.Use((context, next) =>
{
var httpContext = context.Get<HttpContextBase>(typeof(HttpContextBase).FullName);
httpContext.SetSessionStateBehavior(SessionStateBehavior.Required);
return next();
}).UseStageMarker(PipelineStage.MapHandler);
By default, OWIN Middleware run at the last event (PipelineStage.PreHandlerExecute) which is too late for accessing session state.
.UseStageMarker tells OWIN where in the execution pipeline to execute the function passed to .Use().
To use sessions, you need to execute middleware that runs after the session has been initialized by the ASP.net runtime. This middleware must be run in the PostAquireState phase, like so:
.Use((context, next) =>
{
HttpContext.Current.Session["mykey"] = "myvalue";
return next();
}).UseStageMarker(PipelineStage.PostAcquireState);
Related
I have a multi-tenant .NET Core web app where the current user's tenant is resolved via middleware. In particular, tenants are resolved with a library called SaasKit.Multitenancy.
To use this library, you put this line in ConfigureServices():
public IServiceProvider ConfigureServices(IServiceCollection services)
{
// (omitted for brevity)
// The 'Tenant' type is what you resolve to, using 'ApplicationTenantResolver'
services.AddMultitenancy<Tenant, ApplicationTenantResolver>();
// ...
}
And you put this line in Configure() to add it to the middleware pipeline:
public void Configure(IApplicationBuilder app)
{
// ...
app.UseAuthentication();
app.UseMultitenancy<Tenant>(); //this line
app.UseMvc(ConfigureRoutes);
// ...
}
This causes the following method in the middleware to be executed, which resolves the current user's tenant:
public async Task<TenantContext<Tenant>> ResolveAsync(HttpContext context)
{
//whatever you need to do to figure out the tenant goes here.
}
This allows the result of this method (whichever tenant is resolved) to be injected into any class you want, like so:
private readonly Tenant _tenant;
public HomeController(Tenant tenant)
{
_tenant = tenant;
}
Up until now, we have been authenticating users with the .NET Identity platform, storing user data in our app's database. However, a new tenant of ours wants to be able to authenticate their users via SSO.
I have figured out most of the SSO stuff--I am using Azure AD to sign in users, and my organization's Azure AD tenant will be able to federate with their Identity Provider. In short, this code in ConfigureServices adds the Identity and AzureAD authentication:
public IServiceProvider ConfigureServices(IServiceCollection services)
{
// rest of the code is omitted for brevity
services.AddIdentity<ApplicationUser, ApplicationRole>(config =>
{
config.User.RequireUniqueEmail = true;
config.Password.RequiredLength = 12;
}).AddEntityFrameworkStores<ApplicationDbContext>().AddDefaultTokenProviders();
services.AddAuthentication(AzureADDefaults.AuthenticationScheme)
.AddAzureAD(options => _configuration.Bind("AzureAd", options)).AddCookie();
// policy gets user past [Authorize] if they are signed in with Identity OR Azure AD
services.AddMvc(options =>
{
var policy = new AuthorizationPolicyBuilder(
AzureADDefaults.AuthenticationScheme,
IdentityConstants.ApplicationScheme
).RequireAuthenticatedUser()
.Build();
options.Filters.Add(new AuthorizeFilter(policy));
});
}
When using Identity, I have been able to resolve the users's tenant with the UserManager, like so:
public async Task<TenantContext<Tenant>> ResolveAsync(HttpContext context)
{
TenantContext<Tenant> tenantContext = new TenantContext<Tenant>(new ApplicationTenant());
if (context.User.Identity.IsAuthenticated)
{
var user = await _userManager.Users
.Include(x => x.Tenant)
.FirstOrDefaultAsync(x => x.UserName == email);
if (user?.Tenant != null)
{
tenantContext = new TenantContext<Tenant>(user.Tenant);
_logger.LogDebug("The current tenant is " + user.Tenant.Name);
return await Task.FromResult(tenantContext);
}
}
return await Task.FromResult(tenantContext);
}
My plan was to modify this code so grabbed the current User's claims, which can be used to infer which tenant the user belongs to. However, when authenticating a user via Azure AD, HttpContext.User is always empty in the middleware, despite the user being signed in. It's not null, but HttpContext.User.Identity.IsAuthenticated is always false and HttpContext.User.Claims is empty. I only see the value of HttpContext.User populated once routing is complete and the code has reached a Controller.
I have tried reorganizing the middleware in pretty much every feasible way to no avail. What's confusing to me is that HttpContext.User is populated in the tenant resolver when the user is authenticated with Identity. With this in mind, I'm not sure how I can access the user's claims in the middleware when authenticating via Azure AD.
The best solution I can think of is to modify every instance the current tenant is injected into the code with a call to a method that resolves the tenant via claims. If the tenant is null in an area restricted with the [Authorize] attribute, it would imply the user is signed in via Azure AD, which would allow me to look at their claims. However, it really bothers me that I can't access the user's claims in the middleware, as I'm not sure what's really going on here.
Since you are trying to access HttpContext from a custom component you are going to want to add HttpContextAccessor to your service collection as follows:
services.AddHttpContextAccessor();
You can now resolve HttpContext as needed using dependency injection. This may or may not be helpful depending on how much control you have of the middleware that you are using.
For what it's worth, I've had little issue authenticating against AAD just using MSAL without additional third-party middleware. Good luck!
I suspect you might be running on .NET Core 2.0 and running into this issue.
We are trying to understand what is the expected handling of a ChallengeResult when there are multiple authentication schemes registered.
We need to handle such a scenario because we have an ASP.NET core 2.2 app exposing some action methods (we use the MVC middleware) that must be used by an angularjs SPA which relies on cookies authentication and some third parties applications which use an authentication mechanism based on the Authorization HTTP request header. Please notice that the involved action methods are the same for both the users, this means that each one of them must allow authentication using both the cookie and the custom scheme based on Authorization HTTP request header. We know that probably this is not an optimal design but we cannot modify the overall architecture.
This documentation seems to confirm that what we would like to achieve is entirely possible using ASP.NET core 2.2. Unfortunately, the cookie authentication used by the UI app and the custom authentication used by the third parties must behave differently in case of an authentication challenge and their expected behaviors are not compatible with each other: the UI app should redirect the user to a login form, while a thir party application expects a raw 401 status code response. The documentation linked above does not offer a clear explanation of the ChallengeResult handling, so we decided to experiment with a test application.
We created two fake authentication handlers:
public class FooAuthenticationHandler : IAuthenticationHandler
{
private HttpContext _context;
public Task<AuthenticateResult> AuthenticateAsync()
{
return Task.FromResult(AuthenticateResult.Fail("Foo failed"));
}
public Task ChallengeAsync(AuthenticationProperties properties)
{
_context.Response.StatusCode = StatusCodes.Status403Forbidden;
return Task.CompletedTask;
}
public Task ForbidAsync(AuthenticationProperties properties)
{
return Task.CompletedTask;
}
public Task InitializeAsync(AuthenticationScheme scheme, HttpContext context)
{
_context = context;
return Task.CompletedTask;
}
}
public class BarAuthenticationHandler : IAuthenticationHandler
{
private HttpContext _context;
public Task<AuthenticateResult> AuthenticateAsync()
{
return Task.FromResult(AuthenticateResult.Fail("Bar failed"));
}
public Task ChallengeAsync(AuthenticationProperties properties)
{
_context.Response.StatusCode = StatusCodes.Status500InternalServerError;
return Task.CompletedTask;
}
public Task ForbidAsync(AuthenticationProperties properties)
{
return Task.CompletedTask;
}
public Task InitializeAsync(AuthenticationScheme scheme, HttpContext context)
{
_context = context;
return Task.CompletedTask;
}
}
We registered the authentication schemas inside ConfigureServices method as follows:
public void ConfigureServices(IServiceCollection services)
{
services
.AddMvc()
.SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
services.AddAuthentication(options =>
{
options.DefaultChallengeScheme = "Bar";
options.AddScheme<FooAuthenticationHandler>("Foo", "Foo scheme");
options.AddScheme<BarAuthenticationHandler>("Bar", "Bar scheme");
});
}
This is our middleware pipeline:
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseAuthentication();
app.UseMvc();
}
and finally we created a controller with an action method requiring authentication:
[Route("api/[controller]")]
[ApiController]
public class ValuesController : ControllerBase
{
// GET api/values/5
[HttpGet("{id}")]
[Authorize(AuthenticationSchemes = "Foo,Bar")]
public ActionResult<string> Get(int id)
{
return "value";
}
}
We noticed that:
both the FooAuthenticationHandler and BarAuthenticationHandler are called to handle the ChallengeResult
the order is FooAuthenticationHandler before BarAuthenticationHandler and depends on the Authorize attribute (if you swap the authentication schemes inside the Authorize attribute then BarAuthenticationHandler is called first)
the caller gets a raw 500 status code response, but this only depends on the order in which the authorization handlers are called
the call to options.DefaultChallengeScheme = "Bar"; matters if and only if inside the [Authorize] attribute the property AuthenticationSchemes is not set. If you do so, only the BarAuthenticationHandler is called and FooAuthenticationHandler never gets a chance to authenticate the request or handle an authentication challenge.
So, the question basically is: when you have such a scenario, how are you expected to handle the possible "incompatibility" of different authentication schemes regarding ChallengeResult handling since they get both called ?
In our opinion is fine that both have a chance to authenticate the request, but we would like to know if it is possible to decide which one should handle the authentication challenge.
Thanks for helping !
You should not specify the schemes on the Authorize attribute.
Instead, specify one scheme as the default, and setup a forward selector.
The implementation of the selector depends on your case, but usually you can somehow figure out which scheme was used in a request.
For example, here is an example from the setup of an OpenID Connect scheme.
o.ForwardDefaultSelector = ctx =>
{
// If the current request is for this app's API
// use JWT Bearer authentication instead
return ctx.Request.Path.StartsWithSegments("/api")
? JwtBearerDefaults.AuthenticationScheme
: null;
};
So what it does is forward challenges (and well, everything) to the JWT handler if the route starts with /api.
You can do any kind of checks there, headers etc.
So in this case OpenID Connect and Cookies are setup as defaults for everything, but if a call is received that is going to the API, use JWT authentication.
The example here forwards all the "actions" you can do with authentication (challenge, forbid etc.).
You can also setup forward selectors for just challenges etc.
I'm trying to enable sessions in .net core webapp. I have tried following the documentation from here. But issue is the sessions are not getting persisted. With every new request new session id is generated even if previous request has stored something in session. Also I don't see any cookie in dev tools.
Referenced dlls
"Microsoft.AspNetCore.Session": "1.1.1",
"Microsoft.Extensions.Caching.Memory": "1.1.1"
My startup file looks something like this
public void ConfigureServices(IServiceCollection services)
{
// Add framework services.
services.AddMvc(options => { options.Filters.Add(new RequireHttpsAttribute()); });
// Add services needed for sessions
services.AddSession(options =>
{
options.IdleTimeout = TimeSpan.FromMinutes(10);
});
// Add in-memory distributed cache
services.AddDistributedMemoryCache();
// initialising other services, authentication and authorization policies
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
// enable session before uisng it in pipeline
app.UseSession();
// setting custom user middleware
app.UseUserMiddleware();
// set up the mvc default route
app.UseMvc(routes => { routes.MapRoute("default", "myDefaultRoute"); });
// adding few other middlewares
}
And I set and access the session values in my controller something like this
public class MyController : Controller
{
private const string Key = "someKey";
public async Task<ResponseModel> Get()
{
var id = HttpContext.Session.GetInt32(Key);
return new ResponseModel(await _myService.GetAsync(id));
}
public async Task Set([FromBody] RequestModel request)
{
var id = await _myService.GetAsync(request.id);
HttpContext.Session.SetInt32(Key, id);
}
}
In ASP.NET Core, session state is stored in distributed cache, which you've configured to be in-memory. This is basically the same as In Proc session storage in ASP.NET. Since everything stored in memory is tied to the process, whenever the process changes, your session store is wiped.
Now, it should still persist request to request, as long as you keep the application running, but particularly if you stop/start debugging in Visual Studio, you're killing and restarting the process and therefore, wiping the session.
Long and short, if you need your sessions to be persistent, you need to use a persistent store, like SQL Server or Redis. Either can be used in development just as well as in production, if you so desire. Refer to the documentation for details on how to set up a persistent store.
I am using the github example active-directory-b2c-dotnet-webapp-and-webapi.
I configured my own b2c tenant and got the MVC sample to work successfully.
However, I need this to work with a WebForms app. I created a new Webforms app, checked that the references were pointing to the same versions of the dlls as the working sample and adjusted the code to work in WebForms.
Everything works up until the time that I issue the Authentication.Challenge and process the AuthorizationCodeReceivedNotification in the OnAuthorizationCodeReceived function.
The Session object in notification.OwinContext.Environment["System.Web.HttpContextBase"] is null whereas at this point in the MVC sample the session object in notification.OwinContext.Environment["System.Web.HttpContextBase"].Session is a System.Web.HttpSessionStateWrapper with a valid SessionId.
In WebForms, HttpContext.GetOwinContext throws an error as not being available, so I then tried HttpContext.Current.GetOwinContext which returned an HttpContext object instead of a HttpContextBase object. So I finally used
if (!Request.IsAuthenticated)
{
HttpContextBase context = new HttpContextWrapper(HttpContext.Current);
context.GetOwinContext().Authentication.Challenge();
return;
}
instead of the following which is used in the MVC sample
if (!Request.IsAuthenticated)
{
HttpContext.GetOwinContext().Authentication.Challenge();
return;
}
At this point in the code, in both MVC and WebForms the Authentication object has a valid Session object in its OwinContext.Environment prior to the Challenge being issued. The problem is that whilst in the MVC version the resulting notification includes the session object, the WebForms version of the notification has a null object.
The problem finally surfaces when getting the TokenCache by calling the MSALSessionCache which uses the httpContext.Session object (MVC works; Webforms throws Null reference exception)
I am aware that MVC and Webforms treat the session differently but cannot work out how to solve this problem.
EDIT
Whilst this worked it did not address the problem regarding the session id. I added
Notifications = new OpenIdConnectAuthenticationNotifications
{
AuthenticationFailed = AuthenticationFailed,
AuthorizationCodeReceived = OnAuthorizationCodeReceived,
MessageReceived = OnMessageReceived,
SecurityTokenReceived=OnSecurityTokenReceived,
SecurityTokenValidated= OnSecurityTokenValidated
},
to your code with the appropriate stubs for these functions. MessageReceived was invoked as was SecurityTokenReceived and Validated. However, AuthorizationCodeReceived was never invoked. When the first three were invoked, I examined the notification.OwinContext.Environment["System.Web.HttpContextBase"] and in all cases the Session object is null - which is what my problem was to start with.
EDIT (includes answer)
Thanks to #Ramakrishna the solution is to add A RequireAspNetSession helper function at the beginning of ConfigureAuth. The revised code snippet for his sample is as follows:
public void ConfigureAuth(IAppBuilder app)
{
RequireAspNetSession(app);
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
app.UseCookieAuthentication(new CookieAuthenticationOptions());
// Configure OpenID Connect middleware for each policy
app.UseOpenIdConnectAuthentication(CreateOptionsFromPolicy(SignUpPolicyId));
app.UseOpenIdConnectAuthentication(CreateOptionsFromPolicy(ProfilePolicyId));
app.UseOpenIdConnectAuthentication(CreateOptionsFromPolicy(SignInPolicyId));
}
public static void RequireAspNetSession(IAppBuilder app)
{
app.Use((context, next) =>
{
var httpContext = context.Get<HttpContextBase>(typeof(HttpContextBase).FullName);
httpContext.SetSessionStateBehavior(SessionStateBehavior.Required);
return next();
});
// To make sure the above `Use` is in the correct position:
app.UseStageMarker(PipelineStage.MapHandler);
}
you will need to add the following references:
using System.Web.SessionState;
using Microsoft.Owin.Extensions;
if (!Request.IsAuthenticated)
{
HttpContext.Current.GetOwinContext().Authentication.Challenge(
new AuthenticationProperties() { RedirectUri = "/" }, Startup.SignUpPolicyId);
}
Try this in webforms.
EDIT
Sample Application hosted at https://github.com/Zen3InfoSolutions/B2CSamples/tree/master/B2C-WebForms
Zero configuration required to run and verify the policies.
In an OWIN MVC application, I am trying to preserve the BootstrapContext.SecurityToken between requests to allow for creating an ActAs token for federation.
By default the Cookie serialization tries to preserve the BootstrapContext.Token (string form) and ignores the BootstrapContext.SecurityToken (decoded token), this sort of makes sense, avoiding the exposure of the decoded token?
I have therefore tried to use the CookieAuthenticationOptions.SessionStore to preserve the whole BootstrapContext server side. This (sort of) works but with the problem as described below!
Background:
I have lifted the AspNetAuthSessionStore implementation from the sandbox code in the Katana source repository [http://katanaproject.codeplex.com/] which functions as expected saving and restoring the ticket to the HttpContext.Session.
I also lifted the following extension code:
public static class AspNetSessionExtensions {
public static IAppBuilder RequireAspNetSession(this IAppBuilder app) {
app.Use((context, next) => {
// Depending on the handler the request gets mapped to, session might not be enabled. Force it on.
HttpContextBase httpContext = context.Get<HttpContextBase>(typeof(HttpContextBase).FullName);
httpContext.SetSessionStateBehavior(SessionStateBehavior.Required);
return next();
});
// SetSessionStateBehavior must be called before AcquireState
app.UseStageMarker(PipelineStage.MapHandler);
return app;
}
public static IAppBuilder UseAspNetAuthSession(this IAppBuilder app) {
return app.UseAspNetAuthSession(new CookieAuthenticationOptions());
}
public static IAppBuilder UseAspNetAuthSession(this IAppBuilder app, CookieAuthenticationOptions options) {
app.RequireAspNetSession();
options.SessionStore = new AspNetAuthSessionStore();
app.UseCookieAuthentication(options, PipelineStage.PreHandlerExecute);
return app;
}
}
and use the registration functions in my OWIN Startup.
Problem:
In my MVC 4 target action I check the following:
var ctxtuser = HttpContext.User as ClaimsPrincipal;
var claimsPrincipal = Thread.CurrentPrincipal as ClaimsPrincipal;
With the straight cookie implementation (no session store):
app.UseCookieAuthentication(new CookieAuthenticationOptions() {});
the two principals are the same and are the expected value (but without the BootstrapContext content!).
If I simply try to add the Session store to this implementation:
app.UseCookieAuthentication(new CookieAuthenticationOptions() {
SessionStore = new AspNetAuthSessionStore()
});
it fails due to there being no session to store into! Presumably because we aren't allowed a session at Authenticate stage.
If I use the registration from the extension above:
app.UseAspNetAuthSession(new CookieAuthenticationOptions() {
});
version then the code functions almost as expected except that the Thread.CurrentPrincipal is not equal to the HttpContext.User by the time it hits my action. The context user is my claims principle but the Thread.CurrentPrincipal is set to an annonymous WindowsPrincipal.
I have debugged through the code and the claims principal is being applied to both the thread and the context in:
Microsoft.Owin.Host.SystemWeb.OwinCallContext.SetServerUser
But somehow the Thread version gets set back to the annonymous Windows user before hitting my action??
I assume this has something to do with the specific pipeling stage setting in
app.UseCookieAuthentication(options, PipelineStage.PreHandlerExecute);
as opposed to the default value (PipelineStage.Authenticate) used when I request
app.UseCookieAuthentication(new CookieAuthenticationOptions()...
I am wondering if the default code setting the principal at the Authenticate stage makes it 'Stick' as the one ASP.Net wants to use, waiting until PreHandlerExecute is too late?
But can't see how to fix this.
What am I doing wrong?