IdentityServer4 - Use different user stores based on client_id - c#

I am creating an identity service using IdentityServer4 and AspNetCore.Identity. The service will serve multiple websites which need to have separate users. As such I was thinking the simplest way to keep users separate is to have separate user collections in my database (in my case MongoDb), and simply use a different one based on the client_id.
I am registering my IUserRepository like so:
services.AddSingleton<IUserRepository>(x => new UserRepository(mongoClient));
I was thinking the easiest way to achieve what I want is to handle it at the DI level, so I have come up with so far is to change this to:
services.AddScoped<IUserRepository>(x => GetUserRepo(x, mongoClient));
Where GetUserRepo is a method that reads the client_id out of the request and returns a repository for the correct user collection.
This does seem to work but my issue is how "hacky" the implementation of GetUserRepo has to be:
I have to check if the request url is /connect/token and if it is, manually read the request body to find the client_id
If the user is passing a Bearer token (my identity service also includes a management API for creating users etc) then I have to manually decode the token and find the client_id claim
Really I am just wondering if there is a nicer way to do this (anything provided by IdentityServer4?) and also if this is "ok" to do at all?
The only thing I have found that could maybe help is IIdentityServerInteractionService.GetAuthorizationContextAsync however this requires a returnUrl which I don't have as I am using client credentials or resource owner password credentials methods to authenticate.

Not specifically an answer but as it might help put others on the right track:
After thinking more carefully about our scenario I realised it wasn't really necessary to do what I was trying to do. Instead each website will simply use AspNetCore.Identity to handle their own users (with their own data stores) and I am just using IdentityServer to secure my API and set each website up as a client.

Related

Keycloak set custom in access_token claim via API

I need to set a custom claim in the access_token from within a C# application. Is there a way to achive this?
So that I can create custom access_tokens on the fly.
I read though the Keycloak API reference but wan not able to find a solution.
I need this because I have a User that, depending on the application state, should get access to different ressources. I dont want to create different user to achive this. I do not want to save information into the cookies to achive this. And I also do not want to save information in URL to achive this.
I already tried to use a uma-ticket token for this as described here. But all i got was this error:
{
"error": "invalid_grant",
"error_description": "Invalid bearer token"
}
The most common option is to implement dynamic behaviour via claims. At the time of token issuance, the authorization server can reach out to an API endpoint (or database), to send account attributes and receive back custom attributes.
In Keycloak you need to use a protocol mapper for this. The last time I looked you had to develop one in Java, then configure it in the Admin UI for your client app. There is a worked example here.
This is usually a better design than trying to issue new user level access tokens on the fly. Eg an access token contains the important values used for authorization, such as role=manager or subscription_level=gold, so that the claims are trusted. The resources they grant access to could then vary a little based on runtime conditions.

How do I add authentication to an ASP.NET Core minimal API using Identity, but without using Azure?

I'm sure the answer to this must be obvious, as it's an obvious thing want to do, but I can't seem to find any guidance.
I have an ASP.NET Core minimal API, and want to add authentication. I already have Identity set up in a different project, and want to use the same database (ie the same users) for the API.
I saw this blog post, which looked promising until I realised that the code there checks the user name and password as plain text (using admin as both in the sample)...
if (credentials[0] == "admin" && credentials[1] == "admin")
The problem with this is that (thankfully), Identity does not store the passwords in plain text, they are hashed, so I can't do a simple comparison.
I tried hashing the incoming password, as shown in this answer, but that didn't work as the hash came out different every time I called _userManager.PasswordHasher.HashPassword.
I tried using the ASP.NET Core's SignInManager.CanSignInAsync method to check if I could sign in with the credentials, but that required me to add the following to Program...
builder.Services.AddIdentity<User, IdentityRole>(options => {
// options removed for clarity
})
.AddDefaultTokenProviders()
.AddEntityFrameworkStores<AppDbContext>();
However, as soon as I did this, any request to the API attempted to redirect to a log-in page, which is obviously not going to work when the API is being called from code.
All I could find on Microsoft's site was this article, but that assumes you are using Azure. At the moment, I'm still developing this on my local machine, and I don't know yet whether the project owners want to deploy to Azure or their own hosted server, so the code there doesn't help me.
Anyone able to explain to me how I do what seems like such an obvious and simple task? Please let me know if there is any more info I need to provide. Thanks
Have you seen Bipin Joshi's series of articles on this subject? My guess is that you are past the first few, but you might find these useful...
Implement JWT Authentication In ASP.NET Core Minimal APIs
Integrate ASP.NET Core Identity With JWT And Minimal APIs
The one change I made when using that approach was that the getToken API call just takes two string parameters for the user name and password, instead of a User object. Given that it's only two parameters, I find this makes life easier when working with disparate projects, as you don't need the class definition. Up to you though.
In order to call the API, you'll need to call getToken first, passing in the user name and password. Once you have your token, you can then set the authentication on your HtpClient as follows...
client.DefaultRequestHeaders.Add("Authorization", "Bearer " + token);

C# and ASP.NET Core 6 : authentication and user details in "session"

I'm going to get so many "okay grandpa" comments for this one.
I've read a dozen articles and every SO question I could find on this subject.
I must have been away too long or missed something completely, because I swear that user authentication used to be very simple. I seem to recall built-in methods and a session on the server simply knowing who the user was via a cookie or similar, with the ability to store information "in session". I don't recall even setting up authentication in years past, it was just built-in to new applications.
Instead, the most succinct guide I could find is very involved. I think I need a token authorization/authentication setup because there may be consumers (like apps) who don't have a typical cookie pattern these days. In my head, the token works like a cookie except it's manually held on the user end and passed via header with each request?
To its credit, the guide worked, at least for logging in and correctly utilizing the simple Authorize attribute in controllers. However, User.Identity.Name is always empty, even when User.Identity.IsAuthenticated is true, which is perplexing.
How I think auth is working:
User request hits API with username/password
Service checks the combination, and returns an encrypted JWT to the user
The user sends the JWT back with every request
The server decrypts this JWT to identify the user - this is probably where I'm wrong
So here is where my question comes in:
I need more data about the user, like access to the entire UserModel with every request, but I don't want to go to the database to find it every time. This is where I think there should just be a session object in memory, but that doesn't appear to be the case with token authentication.
TL;DR:
Where do I put user-specific, short-term ("session") information for consumption in future requests where a user is identified with a JWT in the Authorization header instead of a cookie?
Session state isn't right, because it's hard-wired to a cookie
HttpContext.Items aren't right, because it's just for the one request
Cache storage isn't right, because it's not user/session specific. I could potentially create a session-like user-keyed storage here but that seems way, way over-engineered for this.
Basically anything where I'm passing all the data (not just a user identifier) to the client then relying on the client to pass it back seems wrong? But feel free to correct me.
The server decrypts this JWT to identify the user This is probably
where I'm wrong
The JWT token is not encrypted, its signed so you can't alter it. You can open it if you look at jwt.io for example.
Where do I put user-specific, short-term ("session") information for
consumption in future requests where a user is identified with a JWT
in the Authorization header instead of a cookie?
You put it in the principle claims of the token. In the guide you linked it wrote:
var claims = new List<Claim>
{
new Claim(JwtRegisteredClaimNames.NameId, user.UserName)
};
So you add whatever you want to the claims to store it on the token and later you can access this data via:
var claim = _contextAccessor.HttpContext.User?.Claims.FirstOrDefault(d =>
d.Type == ClaimTypes.NameIdentifier);
You also can't use any of these other examples that you listed like HttpContext.Items because those are not signed. If the token is altered in any way the system identifies this and returns a 401

Multiple "Forms Authentication" like cookies per application

I am planing to implement a mult- tenant MVC application, where each tenant gets a "sub site" URL, so that rooting would look like:
www.mysite.com/{TenantId}/{Controller}/{Action}
When a user logs into the application, the login shall always be associated with a single tenant (there is no need for single sign on). However, it should be possible that he registers with two or more tenants in the same application. In such case, I need him to be able to simultaneously use both "sub sites".
As I understand it, the FormsAuthentication is using one cookie with a name specified in the web.config, visible in the code through FormsAuthentication.FormsCookieName.
I was thinking about imlementing an approach similar to this one: implement custom cookie creation and checking (using FormsAuthentication.Encrypt\Decrypt for creating the tickets and then manually creating cookies with different names for different tenants). In this way the user could have several cookies, one for each tenant "subsite".
I am wondering, if this approach seems sensible/secure? I was dotPeeking the FormsAuthentication stuff and there is quite some additional stuff under the hood - with a reason I suppose. Also reading articles like this (where the cookie expiration in the secure connection scenario was not handeled properly) makes one wonder, if custom security implementation is really the best way to go...
Alternative to several cookies might also be setting the cookie Path property. If I understand it correctly, the cookie shall be sent only with requests starting with {TenantId} if I set its path when creating it? Will FormsAuthentication know how to handle such cookies? When new ticket will be reissued, will the Path be respected?
And of course, all other suggestions are appreciated as well.

How do I get the requested url from my MembershipProvider class?

I'm currently trying to implement the MembershipProvider class, but my user repository isn't typical to most of the examples I'm finding on the net. Instead of retrieving a user, we simply check the user's identity against an auth server for the requested URL:
User attempts to "POST" to ws.example.com/jobA
Attribute validates the user with the auth server to see if they have access to this action/url
Rejects or accepts the request
A couple other posts pointed me in this direction for implementing a custom membership provider. The way I figure it, in order to make this work, I need to be able to see what the requested URL was, and be able to look at their cookies. In Filters, i have access to the HttpRequestMessage. How do I get at the info i need in this context??
[Despite this thread is not specifically about SharePoint Membership Providers, I will just leave this solution here because I couldn't find it anywhere else, and kept being redirect to this thread while looking for solutions on Google. I hope it helps someone]
For SharePoint Membership Providers, we have the Security Token Service, which is a WCF Service. In this case, System.Web.HttpContext.Current is always null, but you can get the actual Request URL using this property:
System.ServiceModel.OperationContext.Current.RequestContext

Categories

Resources