Authorize user based on API-key supplied in request header in ASP.NET Core - asp.net-core

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.

Related

Adding UserClaims During Startup.cs Seed Database

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.

UserId in SignalR Core

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.

Asp.net 5 web api bearer authentication and multiple areas

I have a webapi backend that several client applications are using. The api is secured with jwt authentication, it is based upon the following example: https://github.com/mrsheepuk/ASPNETSelfCreatedTokenAuthExample. Since I am not yet very comfortable with all the concepts of token based authentication I could use some guidance in this. My issue is that I need my applications to utilize the same api but to limit access for each application to a specific area or controller.
According to the example I can protect methods within an area with:
[Authorize("Api")]
A policy is added in startup with
authOptions.AddPolicy("Api", new AuthorizationPolicyBuilder()
.AddAuthenticationSchemes(JwtBearerDefaults.AuthenticationScheme) // "Bearer" scheme
.RequireAuthenticatedUser().Build());
For secured requests from the client I typically have an angular 2 app that simply adds the jwt in the headers like so:
headers.append('Authorization', 'Bearer ' + jwt);
I don't know of all the mechanics here but I am assuming that when a secure method is requested, the "Api" attribute decoration is what decides/limits which policy is to be used with a certain route in the api.
What is best practice and how do I extend this to work with individually accessible sections?
You can create an ActionFilterAttribute for Authorization and use it on all the actions.
You can implement the FrameworkAuthorise filter methods as per your requirements.
Global.ApiKey is the unique code for your application to identity you have access to that application or not.
[FrameworkAuthorise(Global.ApiKey, AuthorisationType.None)]
public async Task<IHttpActionResult> Get()
{
// code goes here
}
[FrameworkAuthorise(Global.ApiKey, AuthorisationType.Bearer)]
public async Task<IHttpActionResult> Post()
{
// code goes here
}

How to get swagger.io and swashbuckle to pass back api_key via Bearer token?

I've been following a number of sources to try and implement JWT authentication into my WebApi2 project and have also started using swagger.io and Swashbuckle to both document and test my API. I've been inspired by how exceptionless supports an /auth/login method to generate an api_key which is then used to call other methods requiring authorisation.
I should also say my development has been based on the ASP.NET Web API 2 book which introduces Token-Based security using JwtAuthForWebAPI package and so I have my JWT Delegating Handler implemented for me as follows:
// Add Jwt Authentication Web Handler
var builder = new SecurityTokenBuilder();
var reader = new ConfigurationReader();
GlobalConfiguration.Configuration.MessageHandlers.Add(
new JwtAuthenticationMessageHandler
{
AllowedAudience = reader.AllowedAudience,
Issuer = reader.Issuer,
SigningToken = builder.CreateFromKey(reader.SymmetricKey),
//SigningToken = builder.CreateFromKey(reader.SubjectCertificateName),
CookieNameToCheckForToken = reader.CookieNameToCheckForToken
});
(I've set up web.config credentials and the code steps through fine)
The problem is:
Unlike exceptionless, when I call a method after entering my api_key, swagger is putting the key onto the querystring instead of in a "Bearer" authorisation header
I don't know if the above code checks the querystring for the JWT (and even if it did, it doesn't sound that sensible for lots of reasons (security, long URL)
Because the above code builds the Delegating Handler for me, I can't debug it to see what it is actually doing (can I link log4net into it?)
And as a result, my controller method doesn't get hit because [Authorise] fails.
My suppose my question at this point is to resolve item 1 above which will make 2 a non-issue and (hopefully) make 4 work! Thanks.

Custom Auth request in ServiceStack for multi-tenancy

I am already using a custom authentication provider in my ServiceStack based web services application.
I'm overriding the Authenticate method, and validating my user against one of multiple backend tenant databases. I currently determine the tenant database by matching an API key to a database string.
public override object Authenticate(
IServiceBase authService,
IAuthSession session,
Auth request) // <- custom object here, MyCustomAuth request
{
// ...
}
This works when each application is for a single tenant (a tenant/customer can build their own application and use that API key). Moving forward I want to build a multi-tenant mobile application. Thus the API key method cannot be used because I can't expect each user to type it in, hence I can't determine which tenant is using the application.
I wanted to alter the Auth object so that I could include the TenantId (provided by the user on login). However, I can't see how I can customize that object.
Is there anyway to customize that Auth object, or do I have to find an alternative solution?
You can't modify the built-in Authenticate Request DTO used, but you can use its Dictionary<string, string> Meta property to send additional metadata with the Authenticate request, e.g:
client.Post(new Authenticate {
...
Meta = new Dictionary<string,string> {
{"TenantId", tenantId},
}
}
Alternatively you can send additional info in the QueryString or HTTP Headers and access the IRequest with:
var tenantId = authService.Request.QueryString["TenantId"];