Manage user restrictions to different apps within a single B2C Tenant - azure-active-directory

Within a single Azure AD B2C Tenant I have a directory of Users. Also, I have 2 Applications registered. Call them App1 and App2.
Assume that a User has registered his/her Account coming from (and being redirected back to) App1, now that the user is in the common User directory, he/she could also sign in to App2 and I can't do anything about it.
It doesn't make much sense to me, and I need to restrict it. How can I control which Apps the Users can access? A User could have access to any amount of registered Applications. I can't seem to find that in the Azure Portal...
Of course I can apply any solution also on App side, e.g. by checking some claim or something, but still, I need to know how to manage such restrictions. (this could be a fully custom way)

You would have to implement in B2C exactly what you said you'd implement in the app
by checking some claim or something
Apart of your B2C user journey you would introduce a precondition to check whether a claim exists or equals a certain value. This would require custom policies. There isn't an easy way to "error" out, but you could create a self asserted page that just shows an error message with a dummy input claim.
You're much better off building this logic in your application.
<OrchestrationStep Order="3" Type="ClaimsExchange">
<Preconditions>
<Precondition Type="ClaimEquals" ExecuteActionsIf="false">
<Value>app_permission</Value>
<Value>portalapp</Value>
<Action>SkipThisOrchestrationStep</Action>
</Precondition>
</Preconditions>
<ClaimsExchanges>
<ClaimsExchange Id="AppRestrictionExchange" TechnicalProfileReferenceId="TechnicalProfileHere" />
</ClaimsExchanges>
</OrchestrationStep>

Related

Azure B2C - Can't get a B2C user flow to work with another Azure AD instance as a custom identity provider

I have set up a B2C instance OK and managed to get a basic Blazor (server) app working with it a using the Microsoft Identity Platform (using AD groups for permissions - it was a hassle but works).
However, I'm trying to use an external Azure AD as a custom identity provider in the user flow, so that I am not just restricted to just email/id/social accounts, but can have guest accounts from other directories use the app without having to manage their sign-in's. To do that I performed a web app registration in the AD tenant that I wanted to use to authenticate those accounts against (as suggested in a couple of tutorials).
The application I registered in the external AD has a Redirect URI in the format "https://{My B2C Directory Name}.b2clogin.com/{My B2C Directory Name}.onmicrosoft.com/oauth2/authresp", which matches the name of my B2C instance, and I have added the client id and secret generated from that app registration and put the details into the custom identity provider I have created for the sign-in flow, as per the instructions here (including the mappings etc.):
https://learn.microsoft.com/en-us/azure/active-directory-b2c/identity-provider-azure-ad-single-tenant?pivots=b2c-user-flow
I also found a slightly older tutorial here, which is pretty similar (different mappings) that I've tried to follow (and adapt the bits that are out-of-date).
https://medium.com/the-new-control-plane/connecting-azure-ad-b2c-to-azure-ad-via-the-b2c-custom-identity-provider-42fbc2832e32
However when I run the user flow I get "AADSTS900971: No reply address provided." - this happens even when I run the flow directly from the User Flows tab in B2C with a 'Reply URL' explicitly set to "http://jwt.ms" (just to capture the token contents).
I'm confused about the reply URL being missing because they exist in both registered apps. Also, it's not saying they're mismatched, just that one isn't set at all (but appears to be).
It feels like I'm missing something simple - does anyone have any idea what that might be?
Ok so I did a couple of things to resolve this:
Re-registered the application in the AD I want to authenticate with (following this tutorial again: https://learn.microsoft.com/en-us/azure/active-directory-b2c/identity-provider-azure-ad-single-tenant?pivots=b2c-user-flow)
I was careful to ensure that the redirect URI in the format:
https://{B2C Instance Name}.b2clogin.com/{B2C Instance Name}.onmicrosoft.com/oauth2/authresp
was all lower case.
I also had to change from just a 'sign-in' user flow to the 'sign-up, sign-in' one, and then applied the custom identity provider to that flow. Apparently you need that even for users from another AD to be able to complete their invite process (otherwise you just end up with a user doesn't exist error - even if you've invited/added them to the B2C users list).
I also elected to 'Grant admin consent for Default directory' under the API Permissions tab for the application being registered in the external AD (to be used for the custom identity provider).
The flow seems to work now. The only thing that would be useful would be to have an invite only sign-up, sign-in flow so that you could invite specific people without breaking the invite process.
If anyone knows how to do that please do post something.

Azure B2C External provider with custom claims and policies

I have an Azure B2C tenant with some identity providers (Facebook, LinkedIn, etc) and a custom claim where the user is able to choose a gender.
During the sing up with one of those, I'm able to get the gender's claim in the token, but this doesn't happen when the user signs in. I was wondering where is the right place to map/include this custom claim since I've been using the base custom policies: SocialAndLocalAccount
Any clue about where to get it? I have my custom claim inside the SelfAsserted-Social's Technical Profile but no luck during the sign in.
The SelfAsserted-Social technical profile will only be executed on the first time the user connects with his social account.
As you can see from the comments in the user journey
Show self-asserted page only if the directory does not have the user account already (i.e. we do not have an objectId).
You need to persist that claim if you want to reuse it as per comment here
If a claim is to be persisted in the directory after having been collected from the user, it needs to be added as a PersistedClaim in the ValidationTechnicalProfile referenced below
You then need to read it from AD in your AAD-UserRead... technical profiles
And output it in your Relaying Party's output claims.
Follow this guide https://learn.microsoft.com/en-us/azure/active-directory-b2c/custom-policy-configure-user-input#add-a-claim-to-the-user-interface for the Social profiles where city in the examples is your gender claim.

Azure AD B2C: Persisting a default value for a claim during local sign up

In my application I'm trying to use a custom user attribute, extension_Role, that should be persisted to the user during sign up.
I'd like one class of user, say "Customer" to sign-up via a mobile application and not need to provide the Role field, just default it to "Customer" behind the scenes. A second class of user will sign-up using a web app, and I'd like them to be able to select the role from a drop-down, say "Support", "Admin", etc... My plan is to provide two versions of SignUpOrSignin.xml to handle this.
Using the sample policy files Microsoft provide in the starter pack I've defined my custom ClaimType (with no UserInputType) in TrustFrameworkBase.xml and configured the client and object IDs. I've also modified the AAD-UserWriteUsingLogonEmail TechnicalProfile to persist the claim.
I'm stuck with actually persisting a default value. I've tried adding:
<OutputClaim ClaimTypeReferenceId="extension_Role" AlwaysUseDefaultValue="true" DefaultValue="Customer" />
to SignUpOrSignin.xml, however when I run the flow, I get a 500 error once I click on sign up. I think this is because it is expecting that the extension_Role field should be filled in by the user.
The only way I've been able to persist extension_Role is by adding it to the LocalAccountSignUpWithLogonEmail TechnicalProfile in TrustFrameworkBase.xml. For example:
<OutputClaim ClaimTypeReferenceId="extension_Role" DefaultValue="Customer"/>
However since this in the base file, it is shared by all login flows, which would prevent me from setting different values for different sign up flows.
What is the correct way to model this kind of dual sign-up flow?
You must specify the UserInputType when you collect information from the user by using a self-asserted technical profile. See reference here. It is why you get 500 error.
Please note that the Claim you set in SignUpOrSignin.xml (with <OutputClaim ClaimTypeReferenceId="extension_Role" AlwaysUseDefaultValue="true" DefaultValue="Customer" />) will be only returned after your sign-up at that time. The custom attribute won't be stored into Azure AD. That means when you want to query the custom attribute later, you won't find it.
So you have to set the value of extension_Role in Base policy file rather than SignUpOrSignin.xml.
In this scene, you should define two TechnicalProfiles for LocalAccountSignUpWithLogonEmail and one accepts user input, and the other one sets DefaultValue for your custom claim.
Then reference them separately from your two SignUpOrSignin.xml.

Is it possible to pass groups to B2C from federated AD to Azure AD B2C

I have an application running on B2C as part of customer deployment we federate with the customers AD so they can log in with their own corporate identity.
We then create matching groups in the B2C to control behaviors in the app.
We have a request that a customer wants to create the groups in their AD and then pass it across so that they can manage their users and access in one place.
I can see how I can add the claims I want when setting up a user flow but I cant see any option for groups. Is it possible to do or do i have to query the external AD (matching the way I query for groups in the B2C directory)
Thanks
While technically possible, I still have question for the benefit of doing this.
I will try to briefly describe how this would technically work. If not clear - I will have to summ-it on Github. This is only possible using custom policy. So you have to follow: https://learn.microsoft.com/en-us/azure/active-directory-b2c/active-directory-technical-profile
Edit the Menifest of the application registration in federated AAD. There you have to indicate that you require group membership claim:
"groupMembershipClaims": "All",
Extend the claims schema in your custom policy extensions by adding a new claim:
<ClaimsSchema>
<ClaimType Id="idpGroupMemberships">
<DisplayName>Group Memberships in the IdP</DisplayName>
<DataType>stringCollection</DataType>
<UserHelpText>This is read only for the user</UserHelpText>
</ClaimType>
</ClaimsSchema>
Later in the technical profile, copy the incoming groups claim into your outputclaims:
<OutputClaims>
...
<OutputClaim ClaimTypeReferenceId="idpGroupMemberships" PartnerClaimType="groups" />
</OutputClaims>
Finally, you have to include that new claim in your relying party policy:
<RelyingParty>
<DefaultUserJourney ReferenceId="SuSiLocalFbStaykovNet" />
<TechnicalProfile Id="PolicyProfile">
<DisplayName>PolicyProfile</DisplayName>
<Protocol Name="OpenIdConnect" />
<OutputClaims>
...
<OutputClaim ClaimTypeReferenceId="idpGroupMemberships" />
</OutputClaims>
<SubjectNamingInfo ClaimType="sub" />
</TechnicalProfile>
</RelyingParty>
Using this approach you will get the Groups as GUIDs (these will be the objec IDs of the groups in federated AAD). If you want to get the Groups as names and not GUIDs, it is only partially supported and more complicated. Check out this doc here: https://learn.microsoft.com/en-us/azure/active-directory/hybrid/how-to-connect-fed-group-claims#configure-the-azure-ad-application-registration-for-group-attributes
Not exactly an answer but some insight why this might be useful.
While technically possible, I still have question for the benefit of doing this.
I have a smaller organization and a public facing B2C offer and a management UI that handles the customer requests. Some sensitive data is on an internal SQL server and we have for historical reasons and some elder software an on-premise AD. I have now running AAD B2B with hybrid connection and SQL via hybrid connection. Customers running on B2C. Because it's technically just one app, it's also just one login --> ADB2C with a social IDP pointing to AADB2B, that internally is backed by the on-premise AAD.
That all works very well and is exactly what we need. I came here with the same question as the OP to forward the security group information to the token to handle some access restrictions in the management UI.
Hence, for me the scenario makes absolutely sense. Just my 2 cent.

How to support multiple login scenarios in multi-tenanted Azure Active Directory (AAD)

Our application (referred to as "XYZ_App" below) is a multi-tenant SaaS application. We are in the process of making it available for Microsoft AppSource as a multi-tenanted "Web app / API" (referred to as "AppSourceXYZ_App" below).
We started our OpenID Connect implementation with endpoints pointing to “common” as per stated in the documentation when multi-tenancy is desired/required.
In XYZ_App, we added information in the system to know what AAD instance each XYZ_App tenant is associated with (using the GUID Microsoft assigned to this AAD instance, we are NOT using the "rename-safe.onmicrosoft.com" representation).
When using the “common” endpoints, we had to manually validate the issuer from the JWT to make sure it was the expected one: a user could access XYZ_App requesting access to XYZ_App’s tenant associated with contoso.onmicrosoft.com, get directed to “login.microsoftonline.com/common” to authenticate and then decide to authenticate with a user from another AAD instance (referred to as "anotherAADInstance.onmicrosoft.com" below). In this scenario, even though a user could successfully authenticate on anotherAADInstance.onmicrosoft.com, XYZ_App’s redirect URI must make sure the JWT issuer is the one from contoso.onmicrosoft.com. I’ll refer to this setup as Scenario_1.
With that scenario in mind, we thought about NOT using “common” and customize the requests going to login.microsoftonline.com on the fly; attempting to “jail” requests to be forced to authenticate against a specific AAD instance. We would still need to perform our validation in the redirect URI to make sure the issuer is the appropriate one, but we thought this approach might make our lives easier. I’ll refer to this setup as Scenario_2.
Do you envision Scenario_2 is viable in the long run or is it too short-sighted ? Based on my current knowledge of OpenID Connect, one limitation I can see with Scenario_2 is that it would become problematic to support “broker accounts” into our app.
Explanation of “broker accounts”: in our industry, some external users are allowed access to the system. Let’s say I have a company called “BrokerCo” (with their own brokerco.onmicrosoft.com AAD instance) who has 2 employees: Broker1 and Broker2. BOTH anotherAADInstance and contoso hired Broker1 and Broker2 to get broker services to perform tasks in XYZ_App; requiring XYZApp to grant them access. What is the ideal way for authentication from an OpenID Connect standpoint ? If XYZ_App were to use “login.microsoftonline.com/common” for authentication (like in Scenario_1; as opposed to “jailed” access like in Scenario_2), Broker1 and Broker2 could authenticate with brokerco.onmicrosoft.com (no AAD "External users" for anotherAADInstance nor contoso), but they would then get to redirect URI with an issuer that is different than what XYZ_App’s anotherAADInstance and contoso tenants are configured for... I feel like I’m back to square 1...
Do you have suggestions or pointers to solve this issue ?
Background context:
While playing with OpenID Connect issuers, I got the following error message:
AADSTS50020: User account 'testuser#anotherAADInstance.onmicrosoft.com' from identity provider 'https://sts.windows.net/XXXXXXXX-fake-GUID-9bZZ-XXXXxxxxXXXX/' does not exist in tenant 'XYZ Publisher' and cannot access the application 'YYYYYYYY-fake0-GUID-YYYYyyyyYYYY' in that tenant. The account needs to be added as an external user in the tenant first. Sign out and sign in again with a different Azure Active Directory user account.
Thanks in advance !
Your question has multiple layers, trying to address most of them:
AppSource is about trial experiences for new users: this mean that any corporate account around the globe can potentially be an user of your SaaS application - or at least to the trial experience of your application, therefore the first thing you need to think when integrating with AppSource is how easy it has to be for a potential user to experience your app for the first time.
With that in mind, AppSource recommends that the trial of application is build on such a way that allows any user from any organization to sign-in, and therefore a multi-tenant approach for your application is the recommended setup for any application.
The single-tenant approach requires more control on your side, and for a trial experience - it means that the user cannot try your application right away because the operation you have to do on your single-tenant application is to add the user to an Azure Active Directory tenant as a guest user. This guest account will need then to wait receiving an email to accept the invitation to join to this tenant you are adding the user to then sign-in to your application.
Therefore your scenario 1 is the best scenario thinking on a trial experience in general, and also in general require less management (as you'd not need to create/ manage each individual account that needs to access your application as guest users of your Azure AD instance).
However some concerns you listed - that this scenario bringing are valid: Because you are accepting the common endpoint, you are saying basically that any user can sign-in to any tenant to your application, and this may not be desirable. In addition, the scenario you listed that a user can generate a token for any application is also valid, however, you can add additional checks to make this more secure and that any token generated by another authentication is blocked:
You can validate the 'audience' claim to guarantee that the token was issued to your application
You can eventually check the 'tid'/'iss' claims against of a list of tenant Ids in your database to see if that the user's organization is a valid organization in your application -- this would be valid for non-trial users/ organizations.
More information in this article.
About scenario '2' and broker accounts:
This scenario could be interpreted in two different ways:
Broker accounts are guest accounts of a customers' Azure AD tenant
Broker accounts are third party accounts but are not actually added as a user of anotherAADInstance or contoso AD
If your case is '1' then you're right: if your application needs to authenticate guest users that belong to another Azure AD tenant, then common endpoint could not be used directly.
If your case is '2' then what you'd need to do is to continue using the common endpoint and somewhat after the user is authenticated ask them to choose the company. I am describing this on generic terms without fully understanding this scenario. Perhaps this is not simple as you want the remote company to control this and not the user - so some additional complexities may need to be handled here.
A note is that if your scenario is scenario 1. - in theory - you can still use an hybrid approach, where you'd ask user to type the username inside the application and the company that they want to sign-in, then check if you need to validate the user against common or tenant-id endpoint, preparing the request and then sending a login_hint argument when authenticating. More information here

Resources