Azure AD B2C multitenants applications - azure-active-directory

I have been working in a solution to integrate a multi-tenant api backend system with multiple B2C directory, the idea is that each tenant own and manage their own directory, so our api backend system will need to be added to each tenant b2C directory.
I was thinking in extending the owin openID middleware as described here.
Active Directory B2C and OpenIdConnectAuthenticationMiddleware - Multitenant systems
Another alternative is to setup our own B2C directory to integrated our tenant's B2C directory.
Is this possible?

The client ID being referenced by the OWIN OpenIDConnect middleware in the context of Azure Active Directory is used to identify the application itself, regardless of tenancy.
For multi-tenant support, if you go into the "Configure" portion of the application, under the AD being used for application development, you should notice an option labeled "APPLICATION IS MULTI-TENANT" as shown in the screenshot.Multi-Tenant Option
Make sure Multi-Tenant support is enabled. There are other requirements for enabling multi-tenant support that will be obvious when attempting to enable that option.
This option will allow AADs of other tenants to consent to using your application. In action, once a AAD global admin for a tenant consents, this actually adds a reference to the application you have registered in your AAD to their AAD, allowing them to control access as desired without any change on your part.
Speaking to code, you must change the OWIN middleware to disable auto validation of the issuer, and implement your own mechanism for validating the issuer (such as storing all that information upon the initial registration of a tenant and checking all future tenant logins against the initially stored information). As follows:
app.UseOpenIdConnectAuthentication(
new OpenIdConnectAuthenticationOptions
{
ClientId = ClientId,
Authority = Authority,
TokenValidationParameters = new System.IdentityModel.Tokens.TokenValidationParameters
{
// instead of using the default validation (validating against a single issuer value, as we do in line of business apps),
// we inject our own multitenant validation logic
ValidateIssuer = false,
},
Notifications = new OpenIdConnectAuthenticationNotifications()
{
// we use this notification for injecting our custom logic
SecurityTokenValidated = (context) =>
{
// retriever caller data from the incoming principal
string issuer = context.AuthenticationTicket.Identity.FindFirst("iss").Value;
string UPN = context.AuthenticationTicket.Identity.FindFirst(ClaimTypes.Name).Value;
string tenantID = context.AuthenticationTicket.Identity.FindFirst("http://schemas.microsoft.com/identity/claims/tenantid").Value;
if (
// the caller comes from an admin-consented, recorded issuer
(db.Tenants.FirstOrDefault(a => ((a.IssValue == issuer) && (a.AdminConsented))) == null)
// the caller is recorded in the db of users who went through the individual onboardoing
&& (db.Users.FirstOrDefault(b =>((b.UPN == UPN) && (b.TenantID == tenantID))) == null)
)
// the caller was neither from a trusted issuer or a registered user - throw to block the authentication flow
throw new SecurityTokenValidationException();
return Task.FromResult(0);
}
}
});
Source: https://github.com/Azure-Samples/active-directory-dotnet-webapp-multitenant-openidconnect/blob/master/TodoListWebApp/App_Start/Startup.Auth.cs
The reason for disabling issuer validation is due to the common gateway being used in multi-tenant applications for AAD, so the issuer changes based on that tenant that's authenticating. There must be some store of appropriate issuers beforehand to compare with.

Related

Azure AD: How to get both security groups and application roles into access token

I am facing a scenario where my .NET Core app (Azure Web API) will be accessed in two ways:
client_credentials flow
delegated permissions (user) flow
I am using Microsoft Identity Web to authorize and authenticate requests via AD on middleware level. Then I would like to do additional authorization inside my controller methods to check the following:
In case of application call (client credentials), check that the provided access_token contains a specific roles claim that matches with the application role defined in app registrations -> app roles.
In case of signed-in user call, check that the provided access_token contains specific AD Groups (security groups) assigned to that user in Azure AD.
Flow #1 works, but if I enable flow #2 by clicking on token configuration -> add groups claim -> Security Groups -> emit groups as role claims in access token, then the app roles are no longer available in the client credentials flow inside the access token (as in the below screenshot), presumably because it overwrites the roles claim with the security groups (which do not exist for applications).
What is the correct way to do this, or achieve an equivalent situation in a different way?
The requirement is to differentiate controller method access where application A can call e.g. a read-only endpoint 1, but cannot call write endpoint 2, whereas application B is able to call write endpoint 2. The same differentiation should be done also for users on AD-group basis.
You can include a groups claim in your token. You just need to modify the "groupMembershipClaims" field in application manifest:
"groupMembershipClaims": "SecurityGroup"
Then the token will contain the Ids of the groups that the use belongs to like below :
{
"groups": ["{group_id}"]
}
This method won't overwrite the roles claim. So for delegated permissions (user) flow, you need to check the groups claim instead now.

Does what you send in Scope Governs whether you can login with Microsoft Account using Azure AD V2 Endpoints

I have registered a application using the App Registration (Preview) Blade and added the Azure Service Management API as API Permissions I downloaded the MSAL based Sample from
https://github.com/azure-samples/active-directory-dotnet-webapp-openidconnect-v2
Now in startup.auth.cs if i change the Scope i.e keep openid and add https://management.azure.com and then run and try and Login with a Microsoft Account i get the following error
This Doesn't Look like a Work or School Email you cant Sign-in here with Personal Account use your work or School Account Instead.
if i remove the Scope for https://managment.azure.com and just keep Openid profile offline_access i get the Consent Screen and Login
new OpenIdConnectAuthenticationOptions
{
// The Authority represents the v2.0 endpoint - https://login.microsoftonline.com/common/v2.0
// The Scope describes the initial permissions that your app will need. See https://azure.microsoft.com/documentation/articles/active-directory-v2-scopes/
ClientId = clientId,
Authority = String.Format(CultureInfo.InvariantCulture, aadInstance, "common", "/v2.0"),
RedirectUri = redirectUri,
Scope = "openid https://management.azure.com/.default",
PostLogoutRedirectUri = redirectUri,
I am Expecting to have the user Login and Obtain a Token for management API , i am Looking for Reasons for getting the above Error is this Expected ? The Account that i am using exists in my directory as a Member . this works if i use a Managed user(user#tenant.onmicrosoft.com) to Login
Since personal MS accounts cannot be used to manage Azure subscriptions unless they are added to an Azure AD, you should use the organizations endpoint instead of common.
In v1 "common" meant any AAD tenant.
In v2 "common" means any AAD tenant + any personal MS account.
If you wanted only personal accounts, you can use "consumers".
https://learn.microsoft.com/en-us/azure/active-directory/develop/v2-protocols-oidc#fetch-the-openid-connect-metadata-document
You can use "organizations" to allow any AAD tenant but disallow MS accounts.
Of course a user can just edit the URL and login with a personal MS account anyway, so you'll want to check the tenant id of the logged in user.
The tenant id for MS accounts is always 9188040d-6c67-4c5b-b112-36a304b66dad, per the docs: https://learn.microsoft.com/en-us/azure/active-directory/develop/id-tokens#payload-claims.
So check the idp claim.

How to add a claim in the bearer send by httpinterceptor of adal-angular

In a angularjs application, i use adal and adal-angular libraries to authentify user on Azure AD. On backend I use OWIN middleware in my webAPI to add custom claims with :
app.UseWindowsAzureActiveDirectoryBearerAuthentication(new WindowsAzureActiveDirectoryBearerAuthenticationOptions
{
Audience = ConfigurationManager.AppSettings["ida:Audience"],
Tenant = ConfigurationManager.AppSettings["ida:Tenant"],
Provider = new OAuthBearerAuthenticationProvider()
{
OnValidateIdentity = async context =>
{
if(!context.IsValidated)
return;
var userManager = context.OwinContext.Get<UserManager>();
string email = context.Ticket.Identity.GetClaimValue(System.Security.Claims.ClaimTypes.Email);
User user = userManager.GetByEmail(email);
context.Ticket.Identity.AddClaim(new Claim(ClaimTypes.UserId, user.Id.ToString(CultureInfo.InvariantCulture));
}
}
});
It's work for the current request, but how to add the claim in the bearer send by httpinterceptor of adal-angular for the next requests?
To answer your direct question: you cannot modify the access token, period.
It has a digital signature which will no longer be valid if you change anything in the token.
It's a security measure that prevents tampering.
Your OWIN middleware downloads the public keys of the signing key pairs from Azure AD on startup, and uses those to validate tokens.
Now if you stored your internal ids as extension attributes,
it would actually be possible to tell Azure AD to include that in the tokens.
This feature is in preview though, and not recommended for production use: https://learn.microsoft.com/en-us/azure/active-directory/develop/active-directory-optional-claims#configuring-custom-claims-via-directory-extensions.
Here you would set the internal id as an extension on the User entity, and have it included for access tokens to your API.
A pragmatic solution to your issue might be an in-memory cache.
How often do emails change?

How do you authenticate AAD B2C using MSAL?

I have a working version of a Client/Server authentication using ADAL. However, it appears that the B2C AAD doesn't work well with ADAL when you want to use Local Accounts (that is, just a username or just an email address with no backing authenticator other than AAD). It appears the API we should be using for Local Accounts is the alpha release of MSAL. So far, so good. I'm able to create a local user using the Graph API and using the following code, I appear to be authenticating the local user 'joeconsumer#mycompany.com':
this.pca = new PublicClientApplication("a4828eaa-42f6-418a-8062-f857130b69ce");
AuthenticationResult result = await this.pca.AcquireTokenAsync(
new string[] { "a4828eaa-42f6-418a-8062-f857130b69ce" },
string.Empty,
UiOptions.ForceLogin,
null,
null,
"https://login.microsoftonline.com/" + "darkbondpublic.onmicrosoft.com",
"B2C_1_sign-in");
The problem is that I pass the security token from 'result.Token' back to the server using a custom security token mechanism in WCF. The code on the server, which used to work with ADAL, no longer seems to accept the security token from the above call:
JwtSecurityTokenHandler tokenHandler = new JwtSecurityTokenHandler();
Microsoft.IdentityModel.Tokens.SecurityToken securityToken = null;
ClaimsPrincipal claimsPrincipal = tokenHandler.ValidateToken(userName, this.GetTokenValidationParameters(MetadataAddress), out securityToken);
Thread.CurrentPrincipal = claimsPrincipal;
The error message is:
Can anyone tell me what is going on here? Do I need a different method of authenticating on the server?
The metadata endpoint you config for Azure AD B2C tenant is incorrect. Here is the correct one for your reference:
https://login.microsoftonline.com/{tenantId}/v2.0/.well-known/openid-configuration?p=B2C_1_Sign_In
We can find the metadata for the specific policy from the new Azure portal like figure below.
And in the metadata should able to see the keys endpoint like below:
https://login.microsoftonline.com/{tenant}/discovery/v2.0/keys?p={policy}
We can find the key with kid gfIKIH-yZ3phRHRyjnsHIqZMaePLGAELzPat0CNY4sA like below figure:
I think the problem is: you are sending request to V1 endpoint but AAD B2C uses V2 endpoint with the authority: https://login.microsoftonline.com/tfp/{tenantId}/{policyName}/v2.0/
Metadata for v2 endpoint is available at https://login.microsoftonline.com/tfp/{tenantId}/{policyName}/.well-known/openid-configuration
Can you update your Urls and make one more attempt?
To see an authority in Azure Portal select your policy, then:
Locate your Policy
Click "Edit"
Click "Token, session & SSO config"
Expand "Issuer (iss) claim"
Azure (uses V1 endpoint) and Azure AD B2C (uses V2 endpoint) use different set of keys to sign tokens, therefore it is important to download public keys from the right location - originally you downloaded it from V1 but instead need to use V2.
For me this endpoint worked:
https://{Azure domain}/{Azure tenant}/v2.0/.well-known/openid-configuration?p={Azure policy}

Implementing Office 365 single sign-on using custom authentication/claims provider in ADFS 3.0 (RE: AADSTS90019)

I have a new Claims Provider Trust successfully configured in ADFS 3.0 that allows us to use a separate SAML IdP and let ADFS 3.0 be the SP. We now see "You are signed in" when we go through our SAML IdP. So this part works fine (SSO into ADFS 3.0).
However, attempting to access Office 365 apps now returns the following error by at https://login.microsoftonline.com/login.srf:
AADSTS90019: No tenant-identifying information found in either the
request or implied by any provided credentials.
Any ideas? Do I need to add a Relying Party Trust to get Office 365 to also trust our SAML-based Claims Provider?
Trying to complete this last leg in getting fully SSO-ed into Outlook Web App, for example, and feel like I'm just a couple parameters away.
So to properly execute this (SSO into Office 365 via custom authentication/claims provider in ADFS), we have to:
Allow the incoming Name ID claim. Add a Claim Rule to the new Claims Provider in ADFS to pass the Name ID through.
Map the Name ID to the corresponding AD user. Add a Claim Rule to the existing Microsoft Office 365 Identity Platform relying trust party to query AD using the Name ID (format as needed) and return the fields required: UPN and ImmutableID.
Vanilla example of claim rule for the new Claims Provider:
c:[Type == "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier", Properties["http://schemas.xmlsoap.org/ws/2005/05/identity/claimproperties/format"] == "urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"]
=> issue(claim = c);
Example of using the Name ID to query AD and return the UPN and ImmutableID:
c:[Type == "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier", Properties["http://schemas.xmlsoap.org/ws/2005/05/identity/claimproperties/format"] == "urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"]
=> issue(store = "Active Directory", types = ("http://schemas.xmlsoap.org/claims/UPN", "http://schemas.microsoft.com/LiveID/Federation/2008/05/ImmutableID"), query = "samAccountName={0};userPrincipalName,objectGUID;DOMAIN\{1}", param = regexreplace(c.Value, "(?<domain>[^\\]+)\\(?<user>.+)", "${user}"), param = c.Value);
Finally, you should add some filtering on the incoming claims in the Claims Provider for added security (i.e. only allow incoming claims matching your domain's email suffix, etc.)

Resources