Sharing single WebApp and API deployment with multiple B2C Tenants - asp.net-web-api

Current scenario:
Web App and Web API are authenticated using AAD B2C and working fine. Each customer has a unique tenant. OpenIdConnectAuthenticationOptions (web app) and OAuthBearerAuthenticationOptions (api) are set at the application Startup.
As described here: Token based authentication for both Web App and Web API using Azure AD B2C
Unfortunately both Web app and API have to be deployed for each and every customer to keep them separated.
Requirement:
Use same Web app and API for multiple customers rather than deploying them for every customer. Each customer will have a different URL for the web application.
Question 1 (Web App): How to redirect (for authentication) users to the correct tenant based on the request URL?
i.e. Set OpenIdConnectAuthenticationOptions (tenant, applicationId, signInPolicy etc.) from the database (or memory) on the fly based on the Request.Url rather than at application Startup.
Question 2 (API): How to validate the received token using the correct tenant?
i.e. Set OAuthBearerAuthenticationOptions configurations on the fly based on the received token clientId rather than at the application Startup

There are a couple of things within this that will not work based on what I think are assumptions.
Firstly, B2C has no concept of multiple "tenants". B2C is essentially
a Directory within your tenant and cannot be separated further. You
can have multiple B2C Directories within your tenant, say for each
customer, but you cannot have multiple segregated customers within a
single B2C Directory.
Secondly, if you did have your application connected to multiple B2C
directories, you would need to manage then connectors, App ID's, keys
etc for each one. It is then up to your application to work out which
one to use based on some data (URL accessed etc.).
There are also a couple of questions I would ask around how are you on-boarding users ? Can they register themselves ? Do they have their own user stores ?
If you really want to keep user accounts separate for each organisation (and they don't have a SAML / OIDC identity provider already) then I would do the following:
Create a B2C instance for each customer
Create a B2C instance for your application / web API
Use Custom Policies to connect your application B2C instance with your customer B2C instances (making sure to use the <Domain> element for the claims provider in the XML)
Use domain hints within your application to force the application to chose the correct B2C instance (see this: http://www.cloudidentity.com/blog/2014/11/17/skipping-the-home-realm-discovery-page-in-azure-ad/)
In this way your application only needs to trust and maintain the single B2C directory, and if you do have customers who have their own OIDC or SAML endpoints they become a claims provider within the directory, rather than a separate B2C instance.

Per #ManishJoisar's request this is how I resolved my issue long ago. This is an old .net framework 4.8 code sample.
Please note that B2C v2 policies provide better implementation with federation through custom policies these days.
public void ConfigureAuth(IAppBuilder app)
{
int index = 2000;
foreach (var record in SystemConfig.ApiToWebUrl)
{
app.MapWhen(
context => System.Web.HttpContext.Current.Request.Url.BaseURL() == record.Key,
config =>
{
var customer = SystemConfig.GetWebSettings(true)[record.Value];
config.UseOAuthBearerAuthentication(CreateBearerOptionsFromPolicy(index, customer.B2CSignInPolicyId,
customer.B2CAadInstance, customer.B2CTenant, customer.B2CApplicationId));
}
);
index++;
}
}
public OAuthBearerAuthenticationOptions CreateBearerOptionsFromPolicy(int index, string b2cPlicyName,
string b2cInstance, string b2cTenant, string b2cClientId)
{
TokenValidationParameters tvps = new TokenValidationParameters
{
// This is where you specify that your API only accepts tokens from its own clients
ValidAudience = b2cClientId,
AuthenticationType = string.Format("{0}_{1}", b2cPlicyName, index)
};
var aadInstance = string.Format("https://{0}{1}", b2cTenant.Split('.')[0], ".b2clogin.com/{0}/{1}/v2.0/.well-known/openid-configuration");
var config = String.Format(aadInstance, b2cTenant, b2cPlicyName);
return new OAuthBearerAuthenticationOptions
{
// This SecurityTokenProvider fetches the Azure AD B2C metadata & signing keys from the OpenIDConnect metadata endpoint
AccessTokenFormat = new JwtFormat(tvps, new OpenIdConnectCachingSecurityTokenProvider(config)),
Provider = new CustomOAuthBearerProvider()
};
}

Related

Is there a way to Get username & id of current user in Remote Bff Api Endpoint

Can someone guide me or put me in the right direction? I'm new with microservices and Identityserver. I googled it but it's mostly about doing in mvc app that's not my scenario.
I have 3 projects in my .sln file
FrontendHost //Bff frontend dotnet react app
IdentityService //Asp.net mvc app Duende IdentityServer 6 for protecting Apis. Added claims here are openid, profile, email
BackendApi1 //.net6 web api that is protected using Remote Bff Api Endpoint approach.
I've a string Created By field in a model inside BackendApi1 project and in its api controller I wanna assign it with current signed-in user's name.
I tried with
var user = User.Identity.GetUserId();
But didn't get the expected result.

Azure AD exchange access_token with id_token v.1.0 endpoint

I will draw a scenario, and need some suggestions:
I'm using Azure AD (v1.0 endpoint), a single-page app (SPA) and a web API. The SPA establishes the user's identity using OpenID Connect, authenticates the user and receives an id_token and access_token for the back-end web API.
Now, we don't want the SPA to do access control decision based on the id_token received within the SPA app.
Instead, the SPA sends the access_token to the back-end web API to access it, and now we want back-end web API to make an access control decision based on the roles claim found in the id_token, but which the back-end does not receive from the SPA.
The question is, is it possible for the back-end web API to send received access_token to Azure AD token endpoint and receive the relevant id_token for the user so that the back-end web API receives an id_token containing the roles claims for the user, in order to make an access control decision?
There are a couple issues with the approach as you describe it:
The app roles would be defined on the native client application (the SPA). Though you can technically define app roles in the manifest, you'll notice the Azure portal won't let you assign users or groups to a native client app. (Which sort of makes sense, because, as you've rightly said, you don't want to do any access control in the native client app.)
You can't do what you've described (exchange an access_token intended for one audience, for an id_token intended for a different audience). There are some variants of token exchange which you can do, but none of them would help you in this situation.
Instead, what you should do is define the app roles on the web API. Then, assign the users to the corresponding app role for the web API. When these users sign in to the SPA, and the SPA gets an access token on their behalf to the web API, you'll notice the access token will contain the roles claim, populated with the appropriate values.
Summarizing:
Under App registrations for the web API, define your appRoles in the app manifest (or on the Application object directly, using (for example) Azure AD PowerShell).
Under Enterprise apps for the web API, assign users and/or groups to their corresponding app roles, and choose whether or not app role assignment is required or not*. (Or do so directly on the ServicePrincipal object.)
Under App registrations for the SPA (the Application object), add the web API as a required permission.
*If you choose to require app role assignment for the web API (under Enterprise apps > Properties), the SPA will not be able to get an access token for users who are not assigned to an app role for the web API. If you choose not to require app role assignment , users who are not assigned an app role will be able to sign in to SPA and the SPA will be able to get an access token on their behalf for the web API, but the access token will not contain a roles claim.

Identity Server - multiple STS

I have two groups of api applications (group 1: api11, api12; group 2: api21, api22) that are used by two different groups od javascript applications (group 1 and group 2). Each api group has different domain and use different instance of Identity Server (also each identity server has different domain). Business requirement is that user logged in to any application from group 1 can use any app from this group but can't use app or api from group 2.
Under the hood both api1 and api2 often needs to talk to the same microservices. What I want to achive is to make microservices accessible with access tokens issued from any of the identity servers (used by group 1 or group 2 app). Is there any way to achieve this? For one identity server I can use UseIdentityServerBearerTokenAuthentication but as argument I can pass only one Authority there.
It is not directly possible (see this issue)
But you probably need to rethink your architecture. Assuming the business requirement (Business requirement is that user logged in to any application from group 1 can use any app from this group but can't use app or api from group 2.) is non negotiable, what you are trying to achieve may be dangerous if these microservices are handling something very particular to one or the other group (ie, I am talking about multitenancy here)
The right approach in your case would be to have separate instances of microservices, dedicated to different groups, ideally running in their own containers or VMs
So I think this should be possible although whether you really want to do it or not is another question! I haven't tried this but you'd need to do something along the lines of:
Use the same signing certificate from both IdentityServers
To validate your JWTs, use the Microsoft extension "UseJwtBearerAuthentication" instead of using the IdentityServer validator (which is set with the UseIdentityServerAuthentication extension not UseIdentityServerBearerTokenAuthentication - the latter is IdentityServer3 I think). IdentityServer uses the Microsoft validator under the hood anyway, code and docs:
Our authentication middleware serves the same purpose as the above middleware (in fact it uses the Microsoft JWT middleware internally)
The reason for using the Microsoft validator directly is that now you can avoid setting the authority when you set it up, this would normally be used to validate the "iss" (issuer) as part of validating the JWT. Instead you can configure multiple valid issuers (i.e. your two IdentityServers) and point the validator directly at the signing cert that you are using in step 1:
var tokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
ValidateIssuer = true,
ValidIssuers = new[]
{
"http://my_first_IdentityServer/",
"http://my_second_IdentityServer/"
},
IssuerSigningKey = new X509SecurityKey(new X509Certificate2(certLocation))
};
app.UseJwtBearerAuthentication(new JwtBearerOptions()
{
Audience = "http://localhost:5001/",
AutomaticAuthenticate = true,
TokenValidationParameters = tokenValidationParameters
});
The above is adapted from the article here. As it says in the article, you could equally (and preferably) use a certificate store rather than a cert file.
Btw, we're obviously talking authentication here, how you authorise users across the APIs will no doubt add extra complexity.

Using Facebook Ads API and Spring Social Facebook

I'm developing a SpringBoot application which needs to manage multiple Facebook Ad accounts. I've successfully used the Facebook Java Ads SDK:
https://github.com/facebook/facebook-java-ads-sdk
to connect to a single Facebook ads account, via a Facebook app within the same account (account A) and create a campaign and query campaigns, ads sets, etc:
public static final APIContext context = new APIContext(ACCESS_TOKEN, APP_SECRET);
public static void main(String[] args)
{
try
{
AdAccount account = new AdAccount(ACCOUNT_ID, context);
Campaign campaign = account.createCampaign().setName("Java SDK Test Campaign")
.setObjective(Campaign.EnumObjective.VALUE_LINK_CLICKS).setSpendCap(10000L)
.setStatus(Campaign.EnumStatus.VALUE_PAUSED).execute();
System.out.println(campaign.fetch());
} catch (APIException e)
{
e.printStackTrace();
}
}
Thew ACCESS_TOKEN, APP_SECRET and ACCOUNT_ID are all from the same Facebook account (account A), the one in which the Facebook app was created. The Facebook app has the ads_management permissions reviewed and approved.
Now I need to be able to access Facebook Ad Accounts from other Facebook accounts. I've used the spring-social-facebook Accessing Facebook Data tutorial:
https://spring.io/guides/gs/accessing-facebook/
To register a different Facebook account (account B) with the Facebook App, using the scopes "ads_read, ads_management" and the account ID for the new Facebook account (account B) was added to the Facebook App from the original account (account A).
However, when I used the second account's account ID in the code above I get:
{"error":{"message":"Unsupported get request. Object with ID 'act_105428933326149' does not exist, cannot be loaded due to missing permissions, or does not support this operation. Please read the Graph API documentation at https:\/\/developers.facebook.com\/docs\/graph-api","type":"GraphMethodException","code":100,"fbtrace_id":"Ee5exTqJdCp"}
Which suggests I have a permissions issue somewhere, but I'm stumped. I'm not even sure I'm following the correct approach.
I've tried googling for an example of how to use the Facebook Ads API with spring-social-facebook, but other than a few commits to the repository which don't help a great deal, I can't find one.
Facebook Ads API has tiered access. A newly registered app is in Development Tier and can only access the user's own ad accounts. You need to meet certain criteria to get promoted to Basic Tier and manage other people's ad account.

Azure Active Directory Consent Framework not kicking in for .NET client application

I have a Web API running in an Azure Web App. It is used from a .NET WinForms client application. In the same Azure Web App, there is also an ASP.NET MVC site.
Users authenticate to the site and the .NET client using Azure AD credentials. This should be multi-tenant. The MVC app works fine multi-tenant, but I have trouble getting the client to run multi-tenant.
My understanding from https://learn.microsoft.com/en-us/azure/active-directory/active-directory-integrating-applications is that the Consent Framework should kick in automatically if OAuth 2.0 is used. I am using code (below) that is very close to the sample at https://azure.microsoft.com/en-us/resources/samples/active-directory-dotnet-webapi-multitenant-windows-store/.
While I can successfully log in with a user in the tenant where the native app is defined and use the app, I cannot use the app with a user from another tenant. There is no consent asked and I get an AdalException:
AADSTS50001: The application named https://<myurl> was not found in the tenant named <sometenant>.onmicrosoft.com. This can happen if the application has not been installed by the administrator of the tenant or consented to by any user in the tenant. You might have sent your authentication request to the wrong tenant.
Trace ID: 3<snip>2
Correlation ID: 2<snip>a
Timestamp: 2017-01-05 01:01:10Z
I have added the ID of the native client app to the list of knownClientApplications in the web app's manifest. I am authenticating against the "common" tenant (https://login.microsoftonline.com/common). The third-party tenant user account signing in is a Global Admin in that tenant.
So, I am clearly overlooking something to enable the Consent Framework but I can't find what it is based on the sample...
Relevant native client code below
Uri RedirectUri = new Uri(sRedirectUri);
// AadInstance is the common tenant
AuthenticationContext authContext = new AuthenticationContext(AadInstance);
try
{
PlatformParameters pp = new PlatformParameters(PromptBehavior.Auto);
// Authenticate to Azure AD
Program.WebApiAuthenticationResult = await authContext.AcquireTokenAsync(WebAppIdUri, ClientID, RedirectUri, pp);
return true;
}
catch (AdalException ex)
{
MessageBox.Show(ex.Message, "Log In Not Successful");
}
Thanks for any insight!
I am trying to reproduce this issue however failed. The scenario that native client app consume the web API which both protect by Azure works well when I login the native app with the multiple tenant account. I tested the code sample from here.
And based on the error message, it seems that the resource is not correct, please ensure it is same as the APP ID URI for the web API app you register on Azure. I could get the same error message when I specify an incorrect resource.

Resources