Cached or not thread safe HttpContextAccessor? - c#

I was running into strange user access issues which currently stands resolved. I have this scoped service as below.
services.AddScoped<IJwtService, JwtService>();
The following custom middleware, which has the above service DI through constructor.
app.UseMiddleware<AccessCheckToRoutesMiddleware>();
The following was the original code for the JwtService
public JwtService(IHttpContextAccessor httpContextAccessor)
{
this.httpContextAccessor = httpContextAccessor;
//Later removed this section of code below to make it work consistently
try
{
_identity = httpContextAccessor.HttpContext.User.Identity as ClaimsIdentity;
}
catch { }
}
private void _getIdentity()
{
if (_identity==null) _identity = httpContextAccessor.HttpContext.User.Identity as ClaimsIdentity;
//Later changed the above section of code as below to make it work consistently
_identity = httpContextAccessor.HttpContext.User.Identity as ClaimsIdentity;
}
public bool IsPrivilegedUser()
{
_getIdentity();
var val = _identity.FindFirst("IsPrivilegedUser")?.Value;
return val.Equals("True", StringComparison.OrdinalIgnoreCase);
}
As explained in the code above, if I don't store the httpContextAccessor.HttpContext.User.Identity into a variable and get it every time straight, it gives strange results (basically the claims gets mixed up between various users accessing the app at that time. Can somebody exlain what is going on here? Is this a thread safety issue or is it some type of caching issue?

Related

How can I get a custom exception handler written in ASP.Net Core 2.0 to work in ASP.Net Core 3.1?

Having worked through a course on Pluralsight (.NET Logging Done Right: An Opinionated Approach Using Serilog by Erik Dahl) I began implementing a similar solution in my own ASP.Net Core 3.1 MVC project. As an initial proof of concept I downloaded his complete sample code from the course and integrated his logger class library into my project to see if it worked.
Unfortunately, everything seems to work apart from one critical element. In the Configure method of my project's Startup.cs file rather than app.UseExceptionHandler("/Home/Error"); I now have app.UseCustomExceptionHandler("MyAppName", "Core MVC", "/Home/Error"); - in theory this is meant to hit some custom middleware, passing in some additional data for error logging, then behave like the normal exception handler and hit the error handling path. In practice, it doesn't hit the error handling path and users are shown the browser's error page.
The comments on the middleware code say this code is:
// based on Microsoft's standard exception middleware found here:
// https://github.com/aspnet/Diagnostics/tree/dev/src/
// Microsoft.AspNetCore.Diagnostics/ExceptionHandler
This link no longer works. I found the new link but it's in a GitHub archive and it didn't tell me anything useful.
I am aware that there were changes to the way routing works between .Net Core 2.0 and 3.1, but I'm not sure whether these would cause the issue I am experiencing. I do not think the issue is in the code below that gets called from Startup.cs.
public static class CustomExceptionMiddlewareExtensions
{
public static IApplicationBuilder UseCustomExceptionHandler(
this IApplicationBuilder builder, string product, string layer,
string errorHandlingPath)
{
return builder.UseMiddleware<CustomExceptionHandlerMiddleware>
(product, layer, Options.Create(new ExceptionHandlerOptions
{
ExceptionHandlingPath = new PathString(errorHandlingPath)
}));
}
}
I believe the issue is likely to be in the Invoke method in the actual CustomExceptionMiddleware.cs below:
public sealed class CustomExceptionHandlerMiddleware
{
private readonly RequestDelegate _next;
private readonly ExceptionHandlerOptions _options;
private readonly Func<object, Task> _clearCacheHeadersDelegate;
private string _product, _layer;
public CustomExceptionHandlerMiddleware(string product, string layer,
RequestDelegate next,
ILoggerFactory loggerFactory,
IOptions<ExceptionHandlerOptions> options,
DiagnosticSource diagSource)
{
_product = product;
_layer = layer;
_next = next;
_options = options.Value;
_clearCacheHeadersDelegate = ClearCacheHeaders;
if (_options.ExceptionHandler == null)
{
_options.ExceptionHandler = _next;
}
}
public async Task Invoke(HttpContext context)
{
try
{
await _next(context);
}
catch (Exception ex)
{
WebHelper.LogWebError(_product, _layer, ex, context);
PathString originalPath = context.Request.Path;
if (_options.ExceptionHandlingPath.HasValue)
{
context.Request.Path = _options.ExceptionHandlingPath;
}
context.Response.Clear();
var exceptionHandlerFeature = new ExceptionHandlerFeature()
{
Error = ex,
Path = originalPath.Value,
};
context.Features.Set<IExceptionHandlerFeature>(exceptionHandlerFeature);
context.Features.Set<IExceptionHandlerPathFeature>(exceptionHandlerFeature);
context.Response.StatusCode = 500;
context.Response.OnStarting(_clearCacheHeadersDelegate, context.Response);
await _options.ExceptionHandler(context);
return;
}
}
private Task ClearCacheHeaders(object state)
{
var response = (HttpResponse)state;
response.Headers[HeaderNames.CacheControl] = "no-cache";
response.Headers[HeaderNames.Pragma] = "no-cache";
response.Headers[HeaderNames.Expires] = "-1";
response.Headers.Remove(HeaderNames.ETag);
return Task.CompletedTask;
}
}
Any suggestions would be really appreciated, I've been down so many rabbit holes trying to get this working over the last few days to no avail, and quite aside from my own project, I'd love to be able to leave a comment on the Pluralsight course for anyone else trying to do this to save them having to go through the same struggles as I have.
I actually recommend an easier approach than what I had originally showed in the .NET Logging Done Right course (that course was built around .NET Framework for the most part and the ASP.NET Core module was added after original publication). A better course for doing ASP.NET Core logging is the newer Effective Logging in ASP.NET Core. But rather than simply send you off to another course to watch, allow me to answer your question.
I think you should use the UseExceptionHandler(string path) middleware. You are free to log the exception in the error code (controller or razor page code). You can see this concretely in this code repo:
https://github.com/dahlsailrunner/aspnetcore-effective-logging
Specifically look at these files in the BookClub.UI project:
Startup.cs (Configure method)
Pages/Error.cshtml
Pages/Error.cshtml.cs
That will keep your custom code to a minimum (always a good thing).
HTH
You can try to reset the endpoint and route values in your custom exception handler middleware, like below.
try
{
context.Response.Clear();
context.SetEndpoint(endpoint: null);
var routeValuesFeature = context.Features.Get<IRouteValuesFeature>();
routeValuesFeature?.RouteValues?.Clear();
var exceptionHandlerFeature = new ExceptionHandlerFeature()
{
Error = ex,
Path = originalPath.Value,
};
//...
For more information, please check the source code of ExceptionHandlerMiddleware in github:
https://github.com/dotnet/aspnetcore/blob/5e575a3e64254932c1fd4937041a4e7426afcde4/src/Middleware/Diagnostics/src/ExceptionHandler/ExceptionHandlerMiddleware.cs#L107

.Net Core Identity SignInManager in Console App w/o DI/WebHost

I'm attempting to write a generic .Net Core 2.2 Console Application that allows me to use Identity. Specifically I have a database and am simply tring to call SignInManager.PasswordSignInAsync() to authenticate the username/password against my DB.
If I run this in a full blown .NetCore WebApp, where the HttpContext and DI are all built out, it works fine. If I strip it down and simply call the base services I get the same error every time.
I've been trying variants for a few days now and simply cannot figure out what I'm missing.
Any assistance would be greatly appreciated.
I have a class which manages the buildout of the services available for the console app.
public class FXLoginProvider
{
private readonly IServiceCollection _svccoll;
private UserManager<FXUser> _um = null;
private SignInManager<FXUser> _sm = null;
public UserManager<FXUser> UserMgr
{
get { return _um ?? (_um = _svccoll.BuildServiceProvider().GetService<UserManager<FXUser>>()); }
}
public SignInManager<FXUser> SignInMgr
{
get { return _sm ?? (_sm = _svccoll.BuildServiceProvider().GetService<SignInManager<FXUser>>()); }
}
public FXLoginProvider()
{
string s = "Data Source=.\\SQLEXPRESS;Initial catalog=csNextGen;Integrated Security=True;TrustServerCertificate=True;ApplicationIntent=ReadWrite";
_svccoll = new ServiceCollection();
_svccoll.AddDbContext<FXDataContext>(options => { options.UseSqlServer(s); });
_svccoll.AddIdentity<FXUser, FXUserRole>().AddDefaultTokenProviders();
_svccoll.AddTransient<IUserStore<FXUser>, FXUserStore>();
_svccoll.AddTransient<IRoleStore<FXUserRole>, FXRoleStore>();
_svccoll.AddLogging();
_svccoll.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
}
}
Then in my main app...
class Program
{
static void Main(string[] args)
{
try
{
FXUser uu = null;
string sUsername = "user";
string sPassword = "P$sSw0rrD#!";
// create the service provider
FXLoginProvider icp = new FXLoginProvider();
// grab the sign in manager
SignInManager<FXUser> sm1 = icp.SignInMgr;
// fetch the user from the db, this works.
uu = icp.UserMgr.FindByNameAsync(sUsername).Result;
// update my security stamp, this works too
sm1.UserManager.UpdateSecurityStampAsync(uu).GetAwaiter().GetResult();
// I was receiving a Null context error, so I added a default context.
sm1.Context = new DefaultHttpContext();
var r = sm1.PasswordSignInAsync(sUsername, sPassword, false, false).GetAwaiter().GetResult();
Console.WriteLine(r);
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
}
and it always throws the same exception:
Value cannot be null.\r\nParameter name: provider
I do see in the StackTrace it is throwing down in DependencyInjection.ServiceProviderServiceExtensions (source code for DI.SPSE) because the IServiceProvider is null; so I guess I'm missing a service in my list?
I was able to figure out the problem with my implementation. My error was simply that I had not completely filled in the default http context properly.
sm1.Context = new DefaultHttpContext();
should have been
sm1.Context = new DefaultHttpContext() { RequestServices = icp._svccoll.BuildServiceProvider() };
Note: I needed to change the access level of the _svccoll too.
With this change in place I was able to use the SignInManager to authenticate against my back end database.
I battled this problem for days so I'm happy to share my solution (solution is available on GitHub). I hope this helps!
SingInManager relies on cookie, you can’t use it in console app. Instead of it use UserManager<> there a method to verify password

Seeding initial user on Identity module without double-seeding

I'm trying to use the ABP's identity module and have a seed for my first (admin) user.
In the identity module seed contributor's source code I see this:
public Task SeedAsync(DataSeedContext context)
{
return _identityDataSeeder.SeedAsync(
context["AdminEmail"] as string ?? "admin#abp.io",
context["AdminPassword"] as string ?? "1q2w3E*",
context.TenantId
);
}
So in my migrator module I added this:
public override void OnApplicationInitialization(ApplicationInitializationContext context)
{
using (var scope = context.ServiceProvider.CreateScope())
{
var dataSeeder = scope.ServiceProvider.GetRequiredService<IDataSeeder>();
var dsCtx = new DataSeedContext
{
["AdminEmail"] = "my#admin-email",
["AdminPassword"] = "my-admin-password"
};
AsyncHelper.RunSync(() => dataSeeder.SeedAsync(dsCtx));
}
base.OnApplicationInitialization(context);
}
This works... however there's probably another module creating a data seeder (more likely the one that actually gets executed on the migrator, but I can't really find it), so all my contributors (and probably the module contributors) get executed twice (that's to be expected, I guess).
Is there any way I can change the seeding context without actually running an IDataSeeder? and if this can't be done... is there a way I can "unregister" all IDataSeeders previous to mine so only mine gets executed?
The solution to this specific question (although I was hoping to find a more "general" solution), was to change the actual contributor. On your domain module (or wherever you see fit: your migrator or whatever), just do:
// Remove the contributor for the module
context.Services.RemoveAll(t => t.ImplementationType == typeof(IdentityDataSeedContributor));
// Add my custom constributor
context.Services.AddTransient<IDataSeedContributor, MyCustomConstributor>();
Where the implementation of the contributor is simply a copy of the default:
public class MyCustomConstributor : IDataSeedContributor
{
private readonly IIdentityDataSeeder _identityDataSeeder;
public IdentityDataSeedContributor(IIdentityDataSeeder identityDataSeeder)
{
_identityDataSeeder = identityDataSeeder;
}
public Task SeedAsync(DataSeedContext context)
{
return _identityDataSeeder.SeedAsync(
context["AdminEmail"] as string ?? "my#admin-email",
context["AdminPassword"] as string ?? "my-admin-password",
context.TenantId
);
}
}
Notice that you still get the username admin here... if you want to change it, you can just replace also the IIdentityDataSeeder implementation (using the same method, or the easier Services.Replace, which you can use since there should only be one implementation of IIdentityDataSeeder) and copy your own from the default one, changing the searched username.
For now, replacing the services on your module seems the way to go. Maybe the possibility to directly intercept the initialization stages of other modules might be there on future versions, but I haven't seen how for now.

Dynamics CRM Online Object caching not caching correctly

I have a requirement where we need a plugin to retrieve a session id from an external system and cache it for a certain time. I use a field on the entity to test if the session is actually being cached. When I refresh the CRM form a couple of times, from the output, it appears there are four versions (at any time consistently) of the same key. I have tried clearing the cache and testing again, but still the same results.
Any help appreciated, thanks in advance.
Output on each refresh of the page:
20170511_125342:1:55a4f7e6-a1d7-e611-8100-c4346bc582c0
20170511_125358:1:55a4f7e6-a1d7-e611-8100-c4346bc582c0
20170511_125410:1:55a4f7e6-a1d7-e611-8100-c4346bc582c0
20170511_125342:1:55a4f7e6-a1d7-e611-8100-c4346bc582c0
20170511_125437:1:55a4f7e6-a1d7-e611-8100-c4346bc582c0
20170511_125358:1:55a4f7e6-a1d7-e611-8100-c4346bc582c0
20170511_125358:1:55a4f7e6-a1d7-e611-8100-c4346bc582c0
20170511_125437:1:55a4f7e6-a1d7-e611-8100-c4346bc582c0
To accomplish this, I have implemented the following code:
public class SessionPlugin : IPlugin
{
public static readonly ObjectCache Cache = MemoryCache.Default;
private static readonly string _sessionField = "new_sessionid";
#endregion
public void Execute(IServiceProvider serviceProvider)
{
var context = (IPluginExecutionContext)serviceProvider.GetService(typeof(IPluginExecutionContext));
try
{
if (context.MessageName.ToLower() != "retrieve" && context.Stage != 40)
return;
var userId = context.InitiatingUserId.ToString();
// Use the userid as key for the cache
var sessionId = CacheSessionId(userId, GetSessionId(userId));
sessionId = $"{sessionId}:{Cache.Select(kvp => kvp.Key == userId).ToList().Count}:{userId}";
// Assign session id to entity
var entity = (Entity)context.OutputParameters["BusinessEntity"];
if (entity.Contains(_sessionField))
entity[_sessionField] = sessionId;
else
entity.Attributes.Add(new KeyValuePair<string, object>(_sessionField, sessionId));
}
catch (Exception e)
{
throw new InvalidPluginExecutionException(e.Message);
}
}
private string CacheSessionId(string key, string sessionId)
{
// If value is in cache, return it
if (Cache.Contains(key))
return Cache.Get(key).ToString();
var cacheItemPolicy = new CacheItemPolicy()
{
AbsoluteExpiration = ObjectCache.InfiniteAbsoluteExpiration,
Priority = CacheItemPriority.Default
};
Cache.Add(key, sessionId, cacheItemPolicy);
return sessionId;
}
private string GetSessionId(string user)
{
// this will be replaced with the actual call to the external service for the session id
return DateTime.Now.ToString("yyyyMMdd_hhmmss");
}
}
This has been greatly explained by Daryl here: https://stackoverflow.com/a/35643860/7708157
Basically you are not having one MemoryCache instance per whole CRM system, your code simply proves that there are multiple app domains for every plugin, so even static variables stored in such plugin can have multiple values, which you cannot rely on. There is no documentation on MSDN that would explain how the sanboxing works (especially app domains in this case), but certainly using static variables is not a good idea.Of course if you are dealing with online, you cannot be sure if there is only single front-end server or many of them (which will also result in such behaviour)
Class level variables should be limited to configuration information. Using a class level variable as you are doing is not supported. In CRM Online, because of multiple web front ends, a specific request may be executed on a different server by a different instance of the plugin class than another request. Overall, assume CRM is stateless and that unless persisted and retrieved nothing should be assumed to be continuous between plugin executions.
Per the SDK:
The plug-in's Execute method should be written to be stateless because
the constructor is not called for every invocation of the plug-in.
Also, multiple system threads could execute the plug-in at the same
time. All per invocation state information is stored in the context,
so you should not use global variables or attempt to store any data in
member variables for use during the next plug-in invocation unless
that data was obtained from the configuration parameter provided to
the constructor.
Reference: https://msdn.microsoft.com/en-us/library/gg328263.aspx

How to invalidate a C# WCF session if login is incorrect

I am writing a remote service for an application using WCF, in which login information is kept in a database. The service requires session establishment through a login or account creation call. There is no ASP involved.
Now, when a client starts a session by calling an exposed IsInitiating method, I check the account data provided against the information on the database and, if it is not correct, I want to invalidate that session and force the client to start again with a call to an IsInitiating method.
Looking at some other questions, I have found pros and cons for two ways to invalidate a session. One does so the hard way, by throwing a FaultException; the other with softer manners, storing accepted session IDs.
Now, the first one, although achieving what I desire, is way too aggressive, given that incorrect logins are part of the normal flow of the application. The second one, on the other hand, allows the client to continue calling non-initiating methods, eventhough they will be rejected, while also incurring in a considerable code overhead on the service due to the added thread safety requirements.
So, the question: Is there a third path which allows the service to invalidate the session initialization and communicate it to the client, so it is forced to make a new IsInitiating call?
A reduced version of the code I have:
[DataContractAttribute]
public class AccountLoginFault
{
public AccountLoginFault (string message)
{
this.Message = message;
}
[DataMemberAttribute]
public string Message { get; set; }
}
[ServiceContract (SessionMode = SessionMode.Required)]
public interface IAccountService
{
[OperationContract (
IsInitiating = true)]
[FaultContractAttribute (
typeof (AccountLoginFault),
ProtectionLevel = ProtectionLevel.EncryptAndSign)]
bool Login (AccountData account, out string message);
}
[ServiceBehavior (
ConcurrencyMode = ConcurrencyMode.Single,
InstanceContextMode = InstanceContextMode.PerSession)]
public class AccountService : IAccountService
{
public bool Login (AccountData account, out string message)
{
UserManager userdb = ChessServerDB.UserManager;
bool result = false;
message = String.Empty;
UserData userData = userdb.GetUserData (account.Name);
if (userData.Name.Equals (account.Name)
&& userData.Password.Equals (account.Password))
{
// Option one
// Get lock
// this.AcceptedSessions.Add (session.ID);
// Release lock
result = true;
} else
{
result = false;
// Option two
// Do something with session context to mark it as not properly initialized.
// message = "Incorrect account name or password. Account provided was " + account.Name;
// Option three
throw new FaultException<AccountLoginFault> (
new AccountLoginFault (
"Incorrect account name or password. Account provided was " + account.Name));
}
return result;
}
}
Throwing an exception is by far the easiest option because WCF enforces that the session cannot be re-used. From what I gather, what you would like the third party component to accomplish comes quite close to this functionality. But, instead of forcing the client to call IsInitialized again, you would force the client to create a new connection. This looks like a very small difference to me.
An alternative would be to have a private variable bool _authorised and check this variable at every method call.
Do something like this:
public ConnectResponseDTO Connect(ConnectRequestDTO request) {
...
if(LoginFailed)
OperationContext.Current.OperationCompleted += FaultSession;
}
private void FaultSession(object sender, EventArgs e) {
var context = (OperationContext) sender;
context.Channel.Abort();
}
This will fault the channel and the client will havce to reesatablish the session.

Categories