microsoft-identity-web
microsoft-identity-web copied to clipboard
Trying to add a second MicrosoftIdentityWebAppAuthentication to log in PbiEmbed after Active Directory
Hi ! I am trying to add a second schema to succesfully log in power bi with the embed project: UserOwnsData https://docs.microsoft.com/en-us/power-bi/developer/embedded/embed-sample-for-your-organization?tabs=net-core
The project works perfectly after cloned, no problem at all, but when I try to merge with the current app of my organization it occurs that I get : System.InvalidOperationException: 'Scheme already exists: Cookies'
The app was previously working with Active Directory, and now I need to apply a new AD loggin to embed dashboards inside this app...
My Configuration method inside StartUp:
` public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
// Graph api base url
var graphBaseUrl = "https://graph.microsoft.com/v1.0";
// Graph scope for reading logged in user's info
var userReadScope = "user.read";
// List of scopes required
string[] initialScopes = new string[] { userReadScope };
services.AddMicrosoftIdentityWebAppAuthentication(configuration: Configuration,
configSectionName: "AzureAd");
services.AddMicrosoftIdentityWebAppAuthentication(configuration: Configuration,configSectionName: "PBIEMBED")
.EnableTokenAcquisitionToCallDownstreamApi(initialScopes)
.AddMicrosoftGraph(graphBaseUrl, userReadScope)
.AddSessionTokenCaches();
services.AddControllersWithViews(options =>
{
var policy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build();
options.Filters.Add(new AuthorizeFilter(policy));
});
services.AddRazorPages().AddMvcOptions(options =>
{
var policy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build();
options.Filters.Add(new AuthorizeFilter(policy));
}).AddMicrosoftIdentityUI();
//Dependency Injection
services.ResolveThisContext(Configuration);
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
services.Configure<FormOptions>(options => options.ValueCountLimit = 5000);
services.AddMvc(config =>
{
config.Filters.Add(new CustomActionFilter());
});
services.AddMvc()
.AddMvcOptions(options =>
{
options.MaxModelBindingCollectionSize = int.MaxValue;
options.MaxModelValidationErrors = 999999;
});
services.AddDistributedMemoryCache();
services.AddSession(options =>
{
options.IdleTimeout = TimeSpan.FromDays(1); // It depends on user requirements.
});
var config = new ConfigurationBuilder() //newForAD
.SetBasePath(System.IO.Directory.GetCurrentDirectory())//newForAD
.AddJsonFile("appsettings.json", false)//newForAD
.Build();//newForAD
}
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
public class RequestFormSizeLimitAttribute : Attribute, IAuthorizationFilter, IOrderedFilter
{
private readonly FormOptions _formOptions;
public RequestFormSizeLimitAttribute(int valueCountLimit)
{
_formOptions = new FormOptions()
{
ValueCountLimit = valueCountLimit
};
}
public int Order { get; set; }
public void OnAuthorization(AuthorizationFilterContext context)
{
var features = context.HttpContext.Features;
var formFeature = features.Get<IFormFeature>();
if (formFeature == null || formFeature.Form == null)
{
// Request form has not been read yet, so set the limits
features.Set<IFormFeature>(new FormFeature(context.HttpContext.Request, _formOptions));
}
}
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Home/Error");
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseCookiePolicy();
app.UseAuthentication();
app.UseAuthorization();
app.UseSession(); //cambio
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
//endpoints.MapRazorPages();
});
}
}
} `
MY app settings with the double json, one for each log in config:
` //Active Directory "AzureAd": { "Instance": "https://login.microsoftonline.com", "Domain": "xxxxxxxxxx", "TenantId": "xxxxxxxxxxx", "ClientId": "xxxxxxxxxxx", "CallbackPath": "/signin-oidc", "SignedOutCallbackPath": "/signout-oidc" },
//PBI EMBED USER OWNS DATA "PBIEMBED": { "Instance": "https://login.microsoftonline.com/", "Domain": "https://api.powerbi.com", "TenantId": "xxxxxxxxxxxxxx", "ClientId": "xxxxxxxxxxxxxx", "ClientSecret": "xxxxxxxxxxxxxxxxx", //"CallbackPath": "/signin-oidc2" //"SignedOutCallbackPath": "/signout-oidc" }, "PowerBiHostname": "https://app.powerbi.com"`
I tried to do with all of different parameters...but nothing worked well :(
@matiasdellea27 have you seen our documentation on using multiple-auth schemes? You'll need to define a separate cookie scheme for one of the auth schemes.
I am trying all that I can! but not success :(
services.AddAuthentication() .AddMicrosoftIdentityWebApp(Configuration.GetSection("AzureAd"), Microsoft.Identity.Web.Constants.AzureAd, null);
and:
services.AddAuthentication() .AddMicrosoftIdentityWebApp(Configuration.GetSection("PBIEMBED"), Microsoft.Identity.Web.Constants.AzureAdB2C, null) .EnableTokenAcquisitionToCallDownstreamApi(initialScopes) .AddMicrosoftGraph(graphBaseUrl, userReadScope) .AddSessionTokenCaches();
But this config gives me the not null name error... but trying to add a name is worthless
InvalidOperationException: No authenticationScheme was specified, and there was no DefaultChallengeScheme found. The default schemes can be set using either AddAuthentication(string defaultScheme) or AddAuthentication(Action<AuthenticationOptions> configureOptions).
This is a new error after updating both dependencies to the last version: 1.15.2 ( Identity.Web.UI and Identity.Web.MicrosoftGraph )
My new config in start up is this:
services.AddAuthentication()
.AddMicrosoftIdentityWebApp(Configuration.GetSection("AzureAd"), Microsoft.Identity.Web.Constants.AzureAd, null);
services.AddAuthentication()
.AddMicrosoftIdentityWebApp(Configuration.GetSection("PBIEMBED"), Microsoft.Identity.Web.Constants.AzureAdB2C, null)
.EnableTokenAcquisitionToCallDownstreamApi(initialScopes)
.AddMicrosoftGraph(graphBaseUrl, userReadScope)
.AddSessionTokenCaches();
I take this opportunity to ask you, and I take advantage of your generosity jennyf19, is it possible to generate a general authentication for the two schemes when entering the application, and that when I reach the PBI request, the user is ALREADY logged in? since the client_id is the same! and both apps run on the same azure, even though they are different applications.
@matiasdellea27
The first services.AddAuthentication(Microsoft.Identity.Web.Constants.AzureAd)
needs to define the default scheme.
A second issue is you cannot call graph w/B2C, so you would need to move this:
.EnableTokenAcquisitionToCallDownstreamApi(initialScopes)
.AddMicrosoftGraph(graphBaseUrl, userReadScope)
.AddSessionTokenCaches();
to under the AzureAd section.
These 3 lines are coming in the microsoft, power bi embed project... that's why I have them !
I will try to fix it! thank you very much!!!
Thank you @jennyf19 ! this is now working for the first authentication in active directory, but failing causing the token to be null at the second instance when I try to log in power bi...
` // Graph api base url var graphBaseUrl = "https://graph.microsoft.com/v1.0";
// Graph scope for reading logged in user's info
var userReadScope = "user.read";
// List of scopes required
string[] initialScopes = new string[] { userReadScope };
//original AD
//services.AddMicrosoftIdentityWebAppAuthentication(configuration: Configuration,
// configSectionName: "AzureAd", Microsoft.Identity.Web.Constants.AzureAd);
services.AddAuthentication(Microsoft.Identity.Web.Constants.AzureAd)
.AddMicrosoftIdentityWebApp(Configuration.GetSection("AzureAd"), Microsoft.Identity.Web.Constants.AzureAd, "cookiesAd");
services.AddAuthentication()
.AddMicrosoftIdentityWebApp(Configuration.GetSection("PBIEMBED"), Microsoft.Identity.Web.Constants.Scope, "cookiesPBI")
.EnableTokenAcquisitionToCallDownstreamApi(initialScopes)
.AddMicrosoftGraph(graphBaseUrl, userReadScope)
.AddSessionTokenCaches();`
I did something wrong by using ".scope" at the second authentication ?
I am having this error now! The first log in into the app is now working succesfully !
I know that you are saying that I am not supposed to use Graph,,, but the code uses it! and it is working in the cloned project: https://github.com/microsoft/PowerBI-Developer-Samples
Or maybe there is a way to keep logged to AD and then go into power bi with the same token ( ClientID-TenantId-Secret) are the same in both loggings !!!
@matiasdellea27 you don't need two auth schemes. just use the one and use the OpenIdConnect default. I would just use our basic web app calls web api template and go from there.
mkdir webapp-calls-api
cd webapp-calls-api
dotnet new webapp2--auth SingleOrg --called-api-url "https://app.powerbi.com" --called-api-scopes "PowerBiScopes.ReadDashBoard"
You can also include MicrosoftGraph, but you don't need a separate section and separate auth scheme.
Hi Again @jennyf19 ! I have been told to use this method: https://docs.microsoft.com/en-us/power-bi/developer/embedded/embedded-faq#my-application-already-uses-aad-for-user-authentication--how-can-we-use-this-identity-when-authenticating-to-power-bi-in-a--user-owns-data--scenario-
"Once you have a user token to your app, you simply call to ADAL API AcquireTokenAsync using the user access token and specify the Power BI resource URL as the resource ID"
In this steps I am wondering where to obtain the token ? because after the succesfull log in I am not able to retrieve the token in a variable to send to the PBIEmbed controller... can you help me with this ?
@matiasdellea27 ADAL is on the deprecation path, so that doc needs to be updated. Who told you to use this?
Some guy in stackoverflow :(
so, that's why there is no way to get the token as a variable ?
This is the controller in charge of reveiving the token authenticated from power bi ( or azure ad )
`// ---------------------------------------------------------------------------- // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. // ----------------------------------------------------------------------------
namespace namespace { using NFP.Domain.PbiEmbedUOD; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.Identity.Web; using Microsoft.Graph; using System.Threading.Tasks;
[Authorize]
public class ReportsController : Controller
{
private readonly GraphServiceClient m_graphServiceClient;
private readonly ITokenAcquisition m_tokenAcquisition;
public ReportsController(ITokenAcquisition tokenAcquisition,
GraphServiceClient graphServiceClient)
{
this.m_tokenAcquisition = tokenAcquisition;
this.m_graphServiceClient = graphServiceClient;
}
public IActionResult Index()
{
return View();
}
// Redirects to login page to request increment consent
[AuthorizeForScopes(Scopes = new string[] { PowerBiScopes.ReadDashboard, PowerBiScopes.ReadReport, PowerBiScopes.ReadWorkspace } )]
public async Task<IActionResult> Embed()
{
// Generate token for the signed in user
var accessToken = await m_tokenAcquisition.GetAccessTokenForUserAsync(new string[] { PowerBiScopes.ReadDashboard, PowerBiScopes.ReadReport, PowerBiScopes.ReadWorkspace });
// Get username of logged in user
var userInfo = await m_graphServiceClient.Me.Request().GetAsync();
var userName = userInfo.DisplayName;
AuthDetails authDetails = new AuthDetails
{
UserName = userName,
AccessToken = accessToken
};
return View(authDetails);
}
}
} `
It is exactly the same controller provided by UserOwnsData project downloaded from here: https://github.com/microsoft/PowerBI-Developer-Samples/tree/master/.NET%20Core/Embed%20for%20your%20organization
Hi ! @jennyf19 , can you confirm to me if this method is working ?
https://docs.microsoft.com/en-us/dotnet/api/microsoft.identitymodel.clients.activedirectory.authenticationcontext.acquiretokenasync?view=azure-dotnet
Maybe I can retrieve the token in this method
@matiasdellea27 : you should not use ADAL.NET
In your ReportsController
code, why do you acquire a token for PowerBI, if you want to call Graph?