Best practise for populating access token with external claims - identityserver4

We have implemented IdSrv4 on top of AspNetCore Identity and we use ADFS as external IdP. From ADFS we wan´t to get the users AD-groups, upn and som other claims. The claims will be used both inside our IdSrv4 implementation, but will also be sent to our API-resources as part of the access token.
The current situation in our IdSrv4 implementation:
ADFS has been configured so that it emits the claims that we want and in our IdSrv4 implementation those claims are received as expected in the "ExternalLoginCallback" method of the AccountController.
IProfileService has been implemented in order to fill the "IssuedClaims" list with claims.
BUT, I haven't managed to build the connection between those to steps. What is the preferred way to preserve the claims received in "ExternalLoginCallback" and put them into the generated access_token in the IProfileService class?
Right now I have managed to get it working by saving the token using the method "UpdateExternalAuthenticationTokensAsync", which will save the token in the database. Then in the profile service I fetch the token and read the claims into the emitted token.
But this doesn't feel right and while searching for the proper way I´ve seen examples use the class IdentityServerUser that has "AdditionalClaims" property, but I can't find a way to plug that type into the event flow.
Also, when configuring the external IdP you have these "ClaimActions" that can be mapped, but I don't understand what they are.
Finally, I assume that the database tables "IdentityClaims" and "ClientClaims" with corresponding entities should be used for this purpose but I can´t figure out how. Or should they be saved in the "AspNetUserClaims" table to save the actual claim type/values and not only claim mappings?
So basically, there must be a best practice for this scenario that seem to avoid me and I would be greatful if someone could share it.

The main issue in my problem was that the problems I first encountered with persisting the Claims in the AspNetIdentity-Db led me to a wild goose chase.
Returning to this after a week or so made me give this another shot. Turns out that the DI injected "_userManager" wasn't "connected" to the current DI injected "_signInManager". If someone has an explanation for this, please share!
What did work was to use the "_signInManager.UserManager" to update Claims on the user. This properly stores the Claims in the "AspNetUserClaims" table, and can then be retrieved in the profile service.
UPDATE 1:
Of course there was a logical answer to that as well. A user manager is created by default even if you don't call "AddUserManager" on your identity setup during startup. BUT, in my case I have extended the IdentityUser class and now by doing it like this it all works as excpected (where "UserIdentity" is my derived class):
.AddIdentity<TUserIdentity, TUserIdentityRole>(options =>
{
options.User.RequireUniqueEmail = true;
})
.AddEntityFrameworkStores<TIdentityDbContext>()
.AddSignInManager<SignInManager<UserIdentity>>()
.AddUserManager<UserManager<UserIdentity>>()
.AddDefaultTokenProviders();

Related

Logic App how to read secret info for use within a workflow from app settings/some other secure place?

Currently, I'm trying to access Graph API from within a (Standard) Logic App to search for Sharepoint documents. To do so, I try using the following flow (I need delegated permissions, application permissions cannot use search endpoint):
https://techcommunity.microsoft.com/t5/integrations-on-azure-blog/calling-graph-api-from-azure-logic-apps-using-delegated/ba-p/1997666
As one can see in the blog post above, there is a step where the following string gets passed into the body of the first request to get an access token for a delegated user:
grant_type=password&resource=https://graph.microsoft.com&client_id=client_id&username=serviceaccountusername&password=serviceaccountpassword&client_secret=clientsecret
Now the client secret and service account password are two things which I absolutely don't want to have visible in the Logic App code and/or designer screen. Is there a way to securely read these from for instance the 'app settings' (in which I could reference them from a KeyVault)? I really can't find a good way on how to achieve this and I think it's a must to not be able to read these secrets/passwords from the Designer/code view.
Definitely use a KeyVault and make sure that for all steps involved, secure the inputs/outputs where ever that secret information may be visible.
The below example is the call to get the secret and therefore, I only want the outputs to be secured.
Result
With your HTTP call, it's likely that you'll only want the inputs to be secured.
Be sure to use a managed identity on your LogicApp and then assign that managed identity to the KeyVault Secrets User role on the KV itself.
There's plenty of documentation on this topic ...
https://learn.microsoft.com/en-us/azure/logic-apps/create-managed-service-identity?tabs=consumption

AAD hosted Blazor Webassembly - How to get subset of user's AD groups when user is member of large number of groups

I have asked this question on ASP.Net Core github repo, but I do not think my query was sufficiently answered, so posting the question here.
I followed the instructions here to get a user's AD groups as claims. However, the user is member of a large number of groups, and I just see a single claim "hasGroups".
My question is: what is the suggested best practice/pattern to follow in such cases, as I am interested in retrieving only a few of the user's groups that match a string pattern. Where would I even write the code to perform this filter?
I know how to code the graph api query, but where/when exactly to make this call is unclear. And once I retrieve the groups from Graph Api, how do I reflect the group information into the token claims, so the client app can use regular Authorization mechanism, without knowing the details of how the user's groups were populated.
I'm not sure if this is the recommended way of doing it, but the docs here talk about adding groups and roles to use the user object claims for blazor. https://learn.microsoft.com/en-us/aspnet/core/security/blazor/webassembly/azure-active-directory-groups-and-roles?view=aspnetcore-3.1
I'm not sure if it's even possible, but in the customuserfactory, I would try to replace the
foreach (var group in account.Groups)
{
userIdentity.AddClaim(new Claim("group", group));
}
with some code to try to call the graph api (somehow get the token to call the api with, that's the part I'm not sure on), parse the groups and add them to the user claims,
then follow the rest of the example of how to incorporate that into the [authorize] mechanism.
again, I'm not super familiar with blazor wasm yet, and I couldn't find any docs other than this to add groups/roles into the authorization mechanism.
if anyone else has a better method of doing it or why this wouldn't work feel free to chime in, but I hope that this can at least give you some ideas and is slightly useful.

Profile Management with Identity Server

So from what I have read on IdentityServer I should be storing details about the user such as first name and last name inside claims. How would a web application then be able to access the claim information? Since the User Info endpoint requires a valid access token representing the user, I suppose I would need to make an API that could access that returned the profile information of other users? Is this the right way to do it? (use case, web page needs to display contact details that are stored in claims of another user)
Also what would be the way for multiple language profile information be stored and retrieved in the claims? For example a user can have a name/title in multiple languages. I'm thinking of making [LanguageCode]_[ClaimType] (fr_first_name) naming convention and either adding all languages to just the profile IdentityResource or creating separate resources per language.
Your best bet is to set up a project using the IdentityServer4 QuickstartUI example and review that code to better understand how it all works. As of version 4, Identity Server is only focused on the sign-in / sign-out process and the various flows around authentication. They also provide a basic EF-driven persistence model, and they also support the ASP.NET Core Identity persistence model (also EF-driven), but both of those are not meant to be production-ready code.
Basically, persistence of user details is considered your responsibility. That being said, the cookies used for ASP.NET Core authentication greatly restricts how much data you can/should store as claims. The best model is to keep "real" identity provider (IDP) claims as claims, don't add new claims to that list, copy what you need into some other separate user-data table you manage completely, and use the unique claims identifier (almost always "subject id") as the key to your user data. This also makes it easier to migrate a user to another IDP (for example, you'll know user details for "Bob" but he can re-associate his user data away from his Facebook OIDC auth to his Google auth).
Basic persistence isn't too difficult (it's only 12 or 13 SQL statements) but it's a lot more than will fit in a Stackoverflow answer. I blogged about a non-EF approach here -- also not production-ready code (for example, it has ad-hoc SQL to keep things simple), but it should get you started.

Enabling multiple tenants using OWIN Active Directory bearer tokens

I've recently started playing with Azure Active Directory to authenticate users against my website built on AngularJS.
Using blogs and sample code on GitHub, I've gotten it working with single-tenant using a combination of ADAL.js and Katana's Bearer Token AD integration.
However, I'm now running into some issues with supporting multiple tenants.
I've got a page set up that displays the user as ADAL sees them (found through the root scope's userInfo), as well as makes a call to my server that gets picked up by OWIN, and serializes context.Authentication.User.
Client-side, everything seems to be working properly. I can log in with any of my tenants, and it gives me the object I'd expect (with isAuthenticated: true, username populated, and all sorts of properties on profile describing the user, login, and tenant).
This is accomplished client-side by leaving off the tenant argument to my adalAuthenticationServiceProvider.init call, as described in the documentation.
Server-side, however, the UseWindowsAzureActiveDirectoryBearerAuthentication method doesn't like having no value for Tenant (in that it throws an exception). I've tried a few values for this, including the tenant with which my app was originally registered and, my logical favorite, "common," but no matter what I put in there (unless it's the tenant I'm trying to log in with, and if my ADAL is set up with that tenant), it seems to just skip over this.
For what it's worth, an actual API call is failing on the [Authorize] filter and returning a 401, which tells me this isn't an issue with my OWIN interceptor.
How can I tell UseWindowsAzureActiveDirectoryBearerAuthentication to support multi-tenant authentication?
I figured this out while writing the question. I think. But I spent all day on this finding almost no documentation on the matter, so I figured I'd post it anyway.
My solution (found through yet another blog post) was to include ValidateIssuer = false as a parameter. This makes sense, since we no longer want to validate that the tenant giving us a token is the one that we've listed.
Here's my code that solved the problem.
app.UseWindowsAzureActiveDirectoryBearerAuthentication(
new WindowsAzureActiveDirectoryBearerAuthenticationOptions
{
TokenValidationParameters = new TokenValidationParameters
{
ValidAudience = ConfigurationManager.AppSettings["ida:Audience"],
ValidateIssuer = false // This line made it work
},
AuthenticationMode = Microsoft.Owin.Security.AuthenticationMode.Active,
Tenant = "common" // I don't know whether this has any impact,
// but it's a required parameter regardless.
});
I'd love if someone else wanted to correct me if this has any unforeseen circumstances--it's a tad daunting flipping a "validate" switch to off when you're working on authentication. But I think this all makes enough sense.
When you are developing a multi tenant application, you can no longer rely 100% on the default authentication logic. The default authentication logic assumes that you declare the azure AD tenant form where you want to receive tokens, and will enforce than only tokens form that tenant are accepted. This is done by examining the metadata document associated to every tenant, which contains (among other things) the identifier of the tenant itself - that identifier must be present in the token you receive, in the iss claim: any other value means that the token comes form another tenant, hence it must be rejected.
By definition, multitenant applications must accept tokens from multiple tenants. This is done by using a parametric endpoint (the common endpoint, see this post) which allows you to "late bind" which tenant will be used to issue a token. However the common endpoint will serve a generic metadata document, which cannot contain a specific iss value: instead, it contains a placeholder that at runtime will always be substituted with the issuer identifier of the tenant you actually got the token from.
This means that in multitenant apps you have to take over the tenant validation logic. If you are just debugging you can turn it off, as you appear to have done - that will prevent the default issuer validation logic from kicking in and refusing the incoming token because its iss value does not correspond to the placeholder found for common. In more realistic cases, however, you will write your own logic in the TokenValidationParameters.IssuerValidator delegate. For example, you might want to compare the iss value in the incoming token against a list of tenants that bought a monthly subscription to your service. HTH

What's the simplest way to get user Groups from WAAD?

I've got AngularJS and Web.API WAAD authentication up and running. For client side I use great library ADAL.JS. For backend I use Microsoft.Owin.Security.OAuth. This part went quite smooth.
Now I want to implement authorization based on roles (which will be mapped to WAAD groups). Groups are not included in authentication token so I must ask Azure Graph API for them. I saw various ways to do it, using custom claims providers, adding web services to project, etc. Some examples already providing mapping between groups and roles to use in [Authorize] attribute.
But what is just the simplest example of how to get a list of group ids/names from WAAD providing User ID or username, when I'm already authenticated?
Also, is there any way to get this data in JS to use in Angular frontend, or should I create an API service which Angular should call for roles info?
In the non-JS case, the simplest way of getting groups in the token is by opting in. Download your application’s manifest, locate the “groupMembershipClaims” entry, change its value to “SecurityGroup” or “All”, upload back the manifest.
However note that this won't work for your scenario, because it uses the implicit grant - here the token is returned in an URI fragment, hence a big token would risk blowing past the URL length limits of the browser.
You can always request groups to the Graph and make it available to your frontend via custom action on your API, but from what you wrote you are already familiar with that. Let me discuss the matter here - if there's a simpler route to make this work in SPAs, I'll get back to this thread.
HTH
V.
Update: I verified and in the implicit grant case you will receive groups always via the overage claim. Please refer to https://github.com/AzureADSamples/WebApp-GroupClaims-DotNet/tree/master/WebApp-GroupClaims-DotNet - it will show you how to process the overage claim to retrieve groups. All you need to do is apply the same guidance to a web API instead, and if you need to make the info available to the client expose one or more actions doing so.

Resources