I'm using SignalR with ASP.NET Core 2.0 and I'm trying to send a notification for a specific user like this:
_notification.Clients.User(id).InvokeAsync("SendMes");
where _notification is IHubContext.
But it doesn't work. When I send the notification for all users, everything is fine and all users get the notification. But when I send it to a specific user, nothing happens. In connections I have needed user but it seems as if he doesn't have userId. So how can I do this? By access to Identity and claims? If so, how to do this?
I was facing a similar problem and the following article helped me figure it out: https://learn.microsoft.com/en-us/aspnet/core/signalr/groups?view=aspnetcore-2.1
In the Hub (server-side), I checked Context.UserIdentifier and it was null, even though the user was authenticated. It turns out that SignalR relies on ClaimTypes.NameIdentifier and I was only setting ClaimTypes.Name. So, basically I added another claim and it worked out (Context.UserIdentifier is set correctly after that).
Below, I share part of the authentication code I have, just in case it helps:
var claims = userRoles.Split(',', ';').Select(p => new Claim(ClaimTypes.Role, p.Trim())).ToList();
claims.Insert(0, new Claim(ClaimTypes.Name, userName));
claims.Insert(1, new Claim(ClaimTypes.NameIdentifier, userName)); // this is the claim type that is used by SignalR
var userIdentity = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme);
ClaimsPrincipal principal = new ClaimsPrincipal(userIdentity);
Be careful, SignalR users and groups are case-sensitive.
As an addendum to the answer #Chris provided, the DefaultUserIdProvider implementation for IUserIdProvider is actually added using the TryAdd*() method.
services.TryAddSingleton(typeof(IUserIdProvider), typeof(DefaultUserIdProvider));
So all you have to do is add your own custom implementation of IUserIdProvider before calling services.AddSignalR(), and SignalR will skip adding its own when it sees yours.
services.AddSingleton<Microsoft.AspNetCore.SignalR.IUserIdProvider, MyCustomUserIdProvider>();
services.AddSignalR();
The problem was that I created my ClaimsIdentity object used with cookie like this:
new ClaimsIdentity(claims, "ApplicationCookie", ClaimsIdentity.DefaultNameClaimType, ClaimsIdentity.DefaultRoleClaimType);
Where DefaultNameClaimType was my email. When I changed 'ClaimsIdentity.DefaultNameClaimType' to 'ClaimsTypes.NameIdentifier' which is my user id, all worked correctly with this code:
_notification.Clients.User(id).InvokeAsync("SendMes");
If you use JWT, you have to add the NameIdentifier Claim in the SecurityTokenDescriptor:
new Claim(ClaimTypes.NameIdentifier, userId)
The user id provider defaults to using IPrincipal.Identity.Name, which for most Identity deployments, ends up being the email address. In older SignalR, this could be customized by using your own provider.
You would simply implement the following interface:
public interface IUserIdProvider
{
string GetUserId(IRequest request);
}
And then attach it via:
GlobalHost.DependencyResolver.Register(typeof(IUserIdProvider), () => new MyIdProvider());
I'm not sure how much of that has changed in the Core version. IUserIdProvider still exists, though the interface has changed slightly:
public interface IUserIdProvider
{
string GetUserId(HubConnectionContext connection);
}
When you call AddSignalR in ConfigureServices, it sets up the following, among other things of course:
services.AddSingleton(typeof(IUserIdProvider), typeof(DefaultUserIdProvider));
The DefaultUserIdProvider is obviously the default implementation. There doesn't seem to be any configuration option to override this, so if you need to use your own provider, you'll have to replace the service descriptor:
services.Replace(ServiceDescriptor.Singleton(typeof(IUserIdProvider),
typeof(MyCustomUserIdProvider)));
Obviously, that would need to come after the call to AddSignalR. Also, be aware that you must configure auth first, before configuring SignalR. Otherwise, the user will not be available to the hub.
Related
If a user signs in thru my GUI, all is well.
I use SignInManager to sign them in.
I create some claims.
I create a JwtSecurityToken with the claims attached.
I return JWT to client and they use it in header of future Http requests.
I have created a 'Provider' that accesses some of those claims on behalf of backend services. Those backend services are injected with the provider. When the service wants to know some info about the claim it asks the provider, which accesses the HttpContext, extracts claims and provides the requested value to the backend service. It works well.
My challenge is that I have now added a SeedData routine that is called during startup. It will create a user and then seed some business data (in the context of that new user).
The problem I have is that because this has not come from a client request, my HttpContext is NULL during SeedData routine called from startup.cs.
I have tried (within SeedData) to
// Sign in User
SignInResult signInResult = await signInManager.PasswordSignInAsync("username", "password", false, false);
// Create Claim
CustomClaim claim = new CustomClaim();
claim.ValueForBackend = "Foo";
// Add to Claims List
List<Claim> claims = new List<Claim>();
claims.Add(new Claim("custom-claim", JsonSerializer.Serialize(claim)));
// Create Claims Identity
ClaimsIdentity claimsId = new ClaimsIdentity(claims);
// Add the Claims Identity to current ClaimsPrincipal
HttpContextAccessor.HttpContext.User.AddIdentity(claimsId);
thinking that this would put the claims on the context so my provider can extract "Foo" when asked by the backend service.
However, I am getting error:
System.AggregateException: One or more errors occurred. (HttpContext must not be null.)
---> System.InvalidOperationException: HttpContext must not be null.
at Microsoft.AspNetCore.Identity.SignInManager`1.get_Context()
which appears to be thrown as soon as I try the initial sign in during start-up. My take-away from this is the there is not an HttpContext during startup.cs execution.
Is there a way during startup to:
Create initial user
Sign In as that user
Add some claims to that user
Perform seeding (calling backend services) in the context of that user
I could hack around it by creating a special provider of "Foo" that does not get the value from a claim, but is instead hard-fed directly from startup.cs, but wondered if there is a way to set up an HttpContext with claims during startup.cs.
I have a regular ASP.Net Core web site that users access using Windows Authentication to determine which users can access which pages.
In order to render a page for the user, the site needs to call in to a series of web services to fetch various bits of data. These web services don't use Windows Authentication. Instead, they require the user's JWT Token.
So, our WebSite needs to exchange the user's Windows token for a JWT token. We have a special ExchangeToken web service that accepts a request using Windows Authentication, and returns the user's JWT Token.
The difficulty comes when I want WebSite to call this ExchangeToken web service. I need to call it using Impersonation, so that I get the user's JWT Token back. However, it doesn't appear to be possible to use HttpClient with Impersonation.
Initially, I had planned to do this in WebSite:
Repeatedly...
Impersonate the user
Instantiate an HttpClient
Call the TokenExchange service to get the JWT Token
Dispose the HttpClient
Stop impersonation
Return the token
However, according to what I've read, re-creating an HTTP client for every call is bad practice, and I should be using HttpClientFactory instead.
However, I don't see how this approach can work with Impersonation.
I tried this:
Use HttpClientFactory to create an HttpClient
Repeatedly...
Impersonate the user
Call the TokenExchange service to get the JWT Token
Stop impersonation
Return the token
However, what happens is that, despite the impersonation, all calls to the TokenExchange service are made with the same windows credentials - the credentials of the user who happens to access the web site first. AFAIK, this stems from the way that Windows Authentication works - it performs a token exchange the first time you use an HttpClient, and from then on, all calls for that client use the same token.
One option would be to create a separate client for each user... but I have about 7,000 users, so that seems a bit excessive!
Another option would be to trust the WebSite to fetch the tokens on behalf of the user, using its own account. The problem with this is that it entails trusting the WebSite. If it is compromised by an attacker, then I can't stop the attacker stealing JWT tokens for arbitrary user. Whereas, with the impersonation, the attacker still can't get a user's JWT token without first obtaining their Windows token.
So, is there a way to do impersonation + IHttpClientFactory together? Or is there a better way to approach all this?
(If it matters, my company has its own Windows servers - we're not in the cloud, yet)
To demonstrate the problem with the second approach, I made a test application. It doesn't actually use HttpClientFactory, but it does demonstrate the problem.
I started with a web site that just returns the user who made a call:
[Authorize]
[Route("api/[controller]")]
[ApiController]
public class WhoController : ControllerBase
{
[HttpGet]
public ActionResult<string> Get()
{
return User.Identity.Name;
}
}
My client code works like this:
private void CallClient(HttpClient httpClient, string username, string password)
{
LogonUser(username, "MYDOMAIN", password, 2, 0, out IntPtr token);
var accessTokenHandle = new SafeAccessTokenHandle(token);
WindowsIdentity.RunImpersonated(
accessTokenHandle,
() =>
{
string result = httpClient.GetStringAsync("http://MyServer/api/who").Result;
Console.WriteLine(result);
});
}
And my test code invokes it like this:
public void Test()
{
var httpClient = new HttpClient(new HttpClientHandler { UseDefaultCredentials = true });
CallClient(httpClient, "User1", "Password1");
CallClient(httpClient, "User2", "Password2");
}
As described above, I get the following written to the console:
User1
User1
What I want is:
User1
User2
TL;DR: NET Core is doing a lot to fight you on this approach under the hood.
Not entirely an answer on what to do, but hopefully helpful background on the HttpClientFactory approach, based on my understanding of the components.
First, from the ASP NET Core docs in regards to impersonation:
ASP.NET Core doesn't implement impersonation. Apps run with the app's
identity for all requests, using app pool or process identity. If the
app should perform an action on behalf of a user, use
WindowsIdentity.RunImpersonated in a terminal inline middleware in
Startup.Configure. Run a single action in this context and then close
the context.
RunImpersonated doesn't support asynchronous operations and shouldn't
be used for complex scenarios. For example, wrapping entire requests
or middleware chains isn't supported or recommended.
As you call out, there's a lot of progress NET Core has made around how HttpClient instances are handled to resolve socket exhaustion and the expensive operations around the underlying handlers. First, there's HttpClientFactory, which in addition to supporting creating named/typed clients with their own pipelines, also attempts to manage and reuse a pool of primary handlers. Second, there's SocketsHttpHandler, which itself manages a connection pool and replaces the previous unmanaged handler by default and is actually used under the hood when you create a new HttpClientHandler. There's a really good post about this on Steve Gordon's Blog: HttpClient Connection Pooling in NET Core. As you're injecting instances of HttpClient around from the factory, it becomes way safer to treat them as scoped and dispose of them because the handlers are no longer your problem.
Unfortunately, all that pooling and async-friendly reuse makes your particular impersonation case difficult, because you actually need the opposite: synchronous calls that clean up after themselves and don't leave the connection open with the previous credentials. Additionally, what used to be a lower-level capability, HttpWebRequest now actually sits on top of HttpClient instead of the other way around, so you can't even skip it all that well by trying to run the requests as a one off. It might be a better option to look into using OpenID Connect and IdentityServer or something to centralize that identity management and Windows auth and pass around JWT everywhere instead.
If you really need to just "make it work", you might try at least adding some protections around the handler and its connection pooling when it comes to the instance that is getting used to make these requests; event if the new clients per request are working most of the time, deliberately cleaning up after them might be safer. Full disclaimer, I have not tested the below code, so consider it conceptual at best.
(Updated Switched the static/semaphore to a regular instance since the last attempt didn't work)
using (var handler = new SocketsHttpHandler() { Credentials = CredentialCache.DefaultCredentials, PooledConnectionLifetime = TimeSpan.Zero, MaxConnectionsPerServer = 1 })
using (var client = new HttpClient(handler, true))
{
return client.GetStringAsync(uri).Result;
}
I'm trying to rewrite some authorization I currently have for ASP.NET 4.6 in ASP.NET Core.
I understand that Authorization has changed a bit, and I find it difficult to implement my very simple auth strategy in ASP.NET Core.
My requirements:
Every request to the server should include a header called "key". Based on the value of that key, I will be able to query the database and check whether that key represents a regular user or an admin user. If the request does not contain a valid key, the request is not authorized.
How would I implement this in ASP.NET Core? Every example I find seems totally overkill for my needs.
In ASP.NET 4.6 I used my own custom AuthorizeAttributes to use on my controllers, e.g.
[User]
public IHttpActionResult DoSomethingOnlyUsersCanDo() {}
and
[Admin]
public IHttpActionResult DoSomethingOnlyAdminsCanDo() {}
Can I do the same in ASP.NET Core?
In ASP.NET Core, it is recommended that you do not inherit from AuthorizeAttribute. Instead, you can make custom authorization policies: https://learn.microsoft.com/en-us/aspnet/core/security/authorization/claims.
You will need to have an authentication handler that creates a ClaimsIdentity for the user based on the header. Then you can make policies that assert the existence of certain claims on the user.
You can find an implementation of Basic authentication here: https://github.com/blowdart/idunno.Authentication.
Note Barry's comment there of course:
It is meant as a demonstration of how to write authentication middleware and not as something you would seriously consider using.
Its core is in BasicAuthenticationHandler, which inherits from AuthenticationHandler<BasicAuthenticationOptions>.
The principal in this implementation is created in the developer-made event callback, in the sample it is here:
if (context.Username == context.Password)
{
var claims = new[]
{
new Claim(ClaimTypes.NameIdentifier, context.Username, ClaimValueTypes.String, context.Options.ClaimsIssuer),
new Claim(ClaimTypes.Name, context.Username, ClaimValueTypes.String, context.Options.ClaimsIssuer)
};
context.Principal = new ClaimsPrincipal(new ClaimsIdentity(claims, context.Scheme.Name));
context.Success();
}
The authentication ticket is then created in the handler after calling this callback based on the principal:
var ticket = new AuthenticationTicket(validateCredentialsContext.Principal, Scheme.Name);
return AuthenticateResult.Success(ticket);
I also made an article on implementing custom authentication schemes: Creating an authentication scheme in ASP.NET Core 2.0.
I'm following Pluralsights MVC4 Essentials course. In the video about authorization it enables the DefaultRoleProvider in the Web.config (which wasn't present altough I created the project with the same MVC4 Internet template. After installing the Universal Providers via nuget, I followed the tutorial and changed the Seed() method to generate a user and assigning a role.
if (!Roles.RoleExists("Admin"))
{
Roles.CreateRole("Admin");
}
if (Membership.GetUser("sallen") == null)
{
Membership.CreateUser("sallen", "password");
Roles.AddUserToRole("sallen", "admin");
}
Starting the application and trying to login, I was greeted with the To call this method, the "Membership.Provider" property must be an instance of "ExtendedMembershipProvider". message, for which google presents a lot of hits. This article by Jon Galloway seems to explain the issue, but his solution seems cryptic to me:
If you want to use the new AccountController, you'll either need to use the SimpleMembershipProvider or another valid ExtendedMembershipProvider. This is pretty straightforward.
Alright, where do I get it?
A workaround is using SimpleMembership and SimpleRoleProvider instead of the DefaultRoleProvider.. in Web.config, initializing the database connection in the seed() method
WebSecurity.InitializeDatabaseConnection(
"DefaultConnection",
"UserProfile",
"UserId",
"UserName", autoCreateTables: true);
And using
if (!WebSecurity.UserExists("sallen"))
{
WebSecurity.CreateUserAndAccount("sallen", "password");
Roles.AddUserToRole("sallen", "admin");
}
to generate my user.
Is this the SimpleMembershipProvider I need to use? Is it the usual way to use authorization? Why is provider included in the template not an ExtendedMembershipProvider and where can I get one? Why can the tutorial use the template without getting the error?
I started working on Web Api and just want to create a simple basic authentication. I want to know how to do that?
I tried with the given MSDN link but no step wise tutorial is given on MSDN.
http://www.asp.net/web-api/overview/security/basic-authentication
The link you gave provides much of the detail you need, I hope this fills in the blanks.
Note: If using Web.API 2, Microsoft are suggesting a different approach using authentication filters.
Set up https on your server
This is quite important if you need real security otherwise passwords can be gleaned by snooping parties. How you do this depends a entirely on your setup, which you don't detail, but if you're working on an Azure WebRole there's a pretty good step-by-step guide to setting up SSL from Microsoft.
This isn’t required for the next steps, but should be done before you release your code. I mention it first because this part usually involves getting other people involved (sysadmin for server config, finance to purchase the certificate, etc) and it’s good to give them lots of warning.
Write (or steal) a custom IHttpModule to do your authentication
This is the big block of C# code in your link - it parses the values sent by the browser and sets HttpContext.Current.User to the authenticated user. Just copy and paste the meat into a class in your own application and we’ll come back to it later. You’ll need the following using statements in your code.
using System; using System.Net.Http.Headers; using System.Security.Principal;
using System.Text; using System.Threading; using System.Web;
Associate that module with your application
Add a new module to your web.config file (note system.webServer probably already exists)
<system.webServer>
<modules>
<add name="BasicAuth" type="Full.ClassName.Path.BasicAuth, Assembly.Name"/>
</modules>
</system.webServer>
Restrict access to the relevant parts of your site
You can block specific actions by adding the [Authorize] attribute before the action definition. Block a whole controller by adding it before your controller class.
[Authorize] // Restricts access to whole controller
public class StockController : ApiController {
[Authorize] // Restricts access to this action - not necessary if whole controller restricted.
public IEnumerable<StockLevel> Get() {
Or in your App_Start\WebApiConfig.cs file you can add config.Filters.Add(new AuthorizeAttribute()); and it will lock everything down.
Something to watch out for - there’s also a System.Web.Mvc.AuthorizeAttribute so if you have that namespace included you can get confusing results.
At this point you can try it out - user: "user", pass: "password".
Customize your user validation
Go back to the class we stole from the link and you'll see the following block of code:
// TODO: Here is where you would validate the username and password.
private static bool CheckPassword(string username, string password)
Alter this to return true if the username and password are valid. If you're rolling your own you may want to investigate bcrypt (do you trust the implementation you downloaded off the net?), PBKDF2 or the Crypto class (simple but not terribly secure) but there's probably something better from Microsoft as there are lot of concerns around storing passwords properly.
I had to add a few lines of code to the MSDN example to get it to work. Specifically, in OnApplicationAuthenticateRequest(), I set the response status code to 401 if the user could not be validated:
private static void OnApplicationAuthenticateRequest(object sender, EventArgs e)
{
var request = HttpContext.Current.Request;
var authHeader = request.Headers["Authorization"];
bool validated = false;
if (authHeader != null)
{
var authHeaderVal = AuthenticationHeaderValue.Parse(authHeader);
// RFC 2617 sec 1.2, "scheme" name is case-insensitive
if (authHeaderVal.Scheme.Equals("basic",
StringComparison.OrdinalIgnoreCase) &&
authHeaderVal.Parameter != null)
{
validated = AuthenticateUser(authHeaderVal.Parameter);
}
}
if (!validated)
{
HttpContext.Current.Response.StatusCode = 401;
}
}
Once I did that, it worked fine. There's probably better ways to structure the logic, but this is about the smallest change from the example that does the job.
To selectively enable basic authentication on a per-controller or per-method basis, you can derive from AuthorizeAttribute as described in this question.