I want to embed some PowerBI reports in my web application. I have a working code for a previous project. Now, I have a new project with a new Active Directory and new PowerBI. I created a new app in Active Directory and I have the TenantId. When I run AcquireTokenAsync, I receive an error.
public async Task<bool> CreatePowerBIClient()
{
bool rtn = false;
if (client == null)
{
var credential = new UserPasswordCredential(SettingsModels.Username, SettingsModels.Password);
var authenticationContext = new AuthenticationContext(SettingsModels.AuthorityUrl);
var authenticationResult = await authenticationContext.AcquireTokenAsync(SettingsModels.ResourceUrl,
SettingsModels.ClientId, credential);
if (authenticationResult != null)
{
var tokenCredentials = new TokenCredentials(authenticationResult.AccessToken, "Bearer");
client = new PowerBIClient(new Uri(SettingsModels.ApiUrl), tokenCredentials);
rtn = true;
}
}
else
rtn = true;
return rtn;
}
{"error":"interaction_required","error_description":"AADSTS50076: Due
to a configuration change made by your administrator, or because you
moved to a new location, you must use multi-factor authentication to
access '00000009-0000-0000-c000-000000000000'.\r\nTrace ID:
4d6fa156-0435-4c92-9746-b0e3d6bcdb00\r\nCorrelation ID:
0febdcc8-cd86-46e2-a7a5-0ec0705732bb\r\nTimestamp: 2020-09-17
12:20:40Z","error_codes":[50076],"timestamp":"2020-09-17
12:20:40Z","trace_id":"4d6fa156-0435-4c92-9746-b0e3d6bcdb00","correlation_id":"0febdcc8-cd86-46e2-a7a5-0ec0705732bb","error_uri":"https://login.microsoftonline.com/error?code=50076","suberror":"basic_action","claims":"{"access_token":{"capolids":{"essential":true,"values":["8abf28b1-2a8a-440a-821c-9874593bec9c","9f5f13cb-276e-49fe-ad14-829ce71aef09"]}}}"}:
Unknown error
I checked the permission on the application settings in Active Directory but I can't find a place to disable multi-factor authentication. I'm not the admin of this domain though.
What can I do?
Update
I'm using the latest version of PowerBI packages and I replaced the code with the suggested code:
public async Task<bool> CreatePowerBIClient()
{
bool rtn = false;
if (client == null)
{
var authenticationContext = new AuthenticationContext(SettingsModels.AuthorityUrl);
var credential = new ClientCredential(SettingsModels.ClientId, SettingsModels.ClientSecret);
var authenticationResult = await authenticationContext.AcquireTokenAsync(SettingsModels.ResourceUrl, credential);
if (authenticationResult != null)
{
var tokenCredentials = new TokenCredentials(authenticationResult.AccessToken, "Bearer");
client = new PowerBIClient(new Uri(SettingsModels.ApiUrl), tokenCredentials);
rtn = true;
}
}
else
rtn = true;
return rtn;
}
with those values:
authorityUrl: https://login.windows.net/common/oauth2/authorize/
resourceUrl: https://analysis.windows.net/powerbi/api
clientId and clientSecret from when I registered the app from PowerBI (also, I checked the ApplicationId in the Azure portal and it is the same)
Now, I got an error:
Response status code does not indicate success: 400 (BadRequest).
[AdalServiceException: AADSTS90002: Tenant 'authorize' not found. This may happen if there are no active subscriptions for the tenant. Check to make sure you have the correct tenant ID. Check with your subscription administrator.
I don't know what the problem is. I found useful this post.
From your code, I understand that you are making use of a specific account to get the token to connect to the PowerBI report.
This error you are encountering indicates that you are passing credential of the account for which MFA is enabled. MFA is enabled at a user account level and not at the app level. To overcome this error you could use one of the below options :
Option 1 :
You could try seek & exemption for MFA for the account that you re using to connect to the report. Alternatively, in a lot of organization as best practice use service accounts with least perms without MFA enabled to perform automated task. You could make use of one of these accounts to connect to reports by granting them access.
This will not require any change in your code.
Option 2 :
You could generate a App Only Token. You are making a App to get authenticated against Azure AD and consuming the report. MFA will be completely out of the picture.
The App will need to be given permission to the workspace in which the report resides.
The below snippet of the code to get App only token
var credential = new ClientCredential(ApplicationId, ApplicationSecret);
authenticationResult = await authenticationContext.AcquireTokenAsync(ResourceUrl, credential);
For detailed steps on how to create and grant permissions for an app, you could refer this article.
Note :
This needs a setting to be enabled at PowerBI service by the PowerBI service Admin to consume reports by this method.
Related
I tried to call a test connection using C#. At the beginning it worked, a few days later, I have deleted the token and tried again => the user authentication / Microsoft login window does not open anymore.
No matter if the settings were wrong or right, the window always opened.
(it did not work even on a completely rebuilt PC)
Problem:
When executing the method "await app.AcquireTokenInteractive(scopes).ExecuteAsync();" It looks like the app is waiting for input in the microsoft login window, but no window opens. Unfortunately there is no response.
My Azure App Configuration:
I registered my app in the Azure portal as "Accounts in any organizational directory (Any Azure AD directory - Multitenant) and personal Microsoft account".
1
RedirectUri
For.NET Desktop i used: https://login.microsoftonline.com/common/oauth2/nativeclient
2
In the API permissions i added following permission scopes:
offline_access
email
IMAP.AccessAsUser.All
POP.AccessAsUser.All
SMTP.Send
3
In the line var result = await app.AcquireTokenInteractive(scopes).ExecuteAsync(); it should open the window.
My C# Code:
var app = PublicClientApplicationBuilder
.Create(accessParameters.ClientId)
.WithAuthority(
AadAuthorityAudience.AzureAdAndPersonalMicrosoftAccount
)
.WithDefaultRedirectUri()
.Build();
TokenCacheHelper.EnableSerialization(app.UserTokenCache);
var scopes = new string[]
{
"offline_access",
"email",
"https://outlook.office.com/IMAP.AccessAsUser.All",
"https://outlook.office.com/POP.AccessAsUser.All",
"https://outlook.office.com/SMTP.Send",
};
string userName;
string accessToken;
var account = (await app.GetAccountsAsync()).FirstOrDefault();
try
{
AuthenticationResult refresh = await app
.AcquireTokenSilent(scopes, account)
.ExecuteAsync();
userName = refresh.Account.Username;
accessToken = refresh.AccessToken;
}
catch (MsalUiRequiredException e)
{
var result = await app.AcquireTokenInteractive(scopes)
.ExecuteAsync();
userName = result.Account.Username;
accessToken = result.AccessToken;
}
string[] acc = { userName, accessToken };
return acc;
It should look like this
I am trying to use Windows Authentication credentials to connect with my native (Winforms, console app) client to Identity Server hosted on IIS. The point is for user to be authenticated by AD and with those credentials get the right claims and roles from the Identity Server (which is run through commercial https://commercial.abp.io/ platform).
EDIT:
I found out it is not client related issue since i cannot use my External login (Windows credentials) even directly on hosted site.
The thing worked locally while hosted by IISExpress, then i published it to IIS and enabled the Anonymous and Windows Authentication in the IIS settings and here is where problems began.
When i run it and click the External Login (Windows Credentials) button i usually get a redirect to https://myserver/Error?httpStatusCode=401
and i get prompt for my windows credentials (which even if i insert correctly, just repeat prompt again).
From time to time i get logged in with my Windows credentials (which is the goal). Login with username and password works fine.
I saw the similar issue mentioned by someone here:
https://github.com/IdentityServer/IdentityServer4/issues/4937 without any solution\answer.
My client is basically the sample NativeConsolePKCEClient from this https://github.com/damienbod/AspNetCoreWindowsAuth
static string _authority = "https://myserver/";
string redirectUri = "https://127.0.0.1:45656";
var options = new OidcClientOptions
{
Authority = _authority,
ClientId = "native.code",
ClientSecret = "secret",
RedirectUri = redirectUri,
Scope = "openid profile",
FilterClaims = false,
Browser = browser,
Flow = OidcClientOptions.AuthenticationFlow.AuthorizationCode,
ResponseMode = OidcClientOptions.AuthorizeResponseMode.Redirect,
LoadProfile = true
};
_oidcClient = new OidcClient(options);
var result = await _oidcClient.LoginAsync();
and on server side the startup configuration services:
private void ConfigureAuthentication(ServiceConfigurationContext context, IConfiguration configuration)
{
context.Services.Configure<IISOptions>(iis => // IISOptions
{
iis.AuthenticationDisplayName = "Windows";
iis.AutomaticAuthentication = false;
});
context.Services.AddAuthentication()
.AddJwtBearer(options =>
{
options.Authority = configuration["AuthServer:Authority"];
options.RequireHttpsMetadata = Convert.ToBoolean(configuration["AuthServer:RequireHttpsMetadata"]); ;
options.Audience = "ABPIdentityServer";
});
}
Here is the ProcessWindowsLoginAsync challenge method:
private async Task<IActionResult> ProcessWindowsLoginAsync(string returnUrl)
{
// see if windows auth has already been requested and succeeded
var result = await HttpContext.AuthenticateAsync(Microsoft.AspNetCore.Server.IISIntegration.IISDefaults.AuthenticationScheme);
if (result?.Principal is WindowsPrincipal wp)
{
// we will issue the external cookie and then redirect the
// user back to the external callback, in essence, tresting windows
// auth the same as any other external authentication mechanism
var props = new AuthenticationProperties()
{
RedirectUri = "./ExternalLoginCallback",
Items =
{
{ "returnUrl", returnUrl },
{ "scheme", Microsoft.AspNetCore.Server.IISIntegration.IISDefaults.AuthenticationScheme },
}
};
var id = new ClaimsIdentity(Microsoft.AspNetCore.Server.IISIntegration.IISDefaults.AuthenticationScheme);
id.AddClaim(new Claim(JwtClaimTypes.Subject, wp.Identity.Name));
id.AddClaim(new Claim(JwtClaimTypes.Name, wp.Identity.Name));
// add the groups as claims -- be careful if the number of groups is too large
{
var wi = (WindowsIdentity)wp.Identity;
var groups = wi.Groups.Translate(typeof(NTAccount));
var roles = groups.Select(x => new Claim(JwtClaimTypes.Role, x.Value));
id.AddClaims(roles);
}
await HttpContext.SignInAsync(IdentityConstants.ExternalScheme, new ClaimsPrincipal(id), props);
return Redirect(props.RedirectUri);
}
else
{
// trigger windows auth
// since windows auth don't support the redirect uri,
// this URL is re-triggered when we call challenge
return Challenge("Windows");
}
}
I am suspecting that this piece of code when calling Challenge somehow returns up redirecting to error page, but i am not sure and i do now why.
So what am i missing? Is it even possible to run both Windows and Anonymous authentication on IIS?
Here i also found similar issue:
identity server 4 windows authentication
but the presented answers did not help me.
I strongly suspect that it's not the client issue it's the token provider's issue (Not the ID4 library but one where you have installed the ID4 library).
I believe that you have added the below code in the AccountController->Login action but make sure that you have added a success check in it, if you miss that then your app will go infinite loop.
[HttpGet] public async Task<IActionResult> Login(string returnUrl)
{
if(loginViewModel.ExternalLoginScheme == "Windows")
{
var authenticationResult = await HttpContext.AuthenticateAsync("Windows").ConfigureAwait(false);
if (authenticationResult.Succeeded && authenticationResult?.Principal is WindowsPrincipal windowsPrinciple)
{
// Add your custom code here
var authProps = new AuthenticationProperties()
{
RedirectUri = Url.Action("Callback"),
Items =
{
{ "returnUrl", returnUrl },
{ "scheme", "Windows"},
}
};
await HttpContext.SignInAsync();
return Redirect(RedirectUri);
}
else
{
return Challenge("Windows");
}
}
}
I hope this will help you to fix your issue.
Happy Coding!!
Just for anybody who might be interested. I found out what was causing the redirect error.
It is somehow connected with the ABP Suite i used for generating the base application.
there in the ApplicationInitialization there was a middleware called
app.UseErrorPage();
Which when the Windows credentials were challenged took it as an Error and redirected to https://myserver/Error?httpStatusCode=401.
I am not sure how this middleware works and why sometimes login worked, but removing this part solved my issue.
I hope this helps somebody, somehow, sometime..
I am attempting to programmatically authorise an Azure application from an Azure AD joined machine.
If I go to the application URL in Internet Explorer it is able to verify the logged on user account.
My current code looks something like this:
using Microsoft.IdentityModel.Clients.ActiveDirectory;
AuthenticationContext context = new AuthenticationContext("https://login.microsoftonline.com/TENANTGUID");
Uri uri = new Uri("urn:ietf:wg:oauth:2.0:oob");
var pparams = new PlatformParameters(PromptBehavior.Auto, null);
AuthenticationResult result = await context.AcquireTokenAsync("https://graph.windows.net", "1950a258-227b-4e31-a9cf-717495945fc2", uri, pparams);
This call is successful but I want to acquire a token for the currently logged on user.
The first two parameters to the AcquireTokenAsync call are resource and clientid.
I can get the Homepage url and application id for the application I want to access but cannot find a combination of the two that works.
What parameters should I pass to this function to silently validate the logged on user and obtain an authorisation header that can be used in subsequent calls to the application?
I'd advise you now MSAL.NET Integrated Windows Authentication for domain or AAD joined machines:
the code would be something like :
static async Task GetATokenForGraph()
{
string tenant = "contoso.com" // can also be a GUID or organizations for multi-tenant
string authority = $"https://login.microsoftonline.com/{tenant}";
string[] scopes = new string[] { "user.read" };
PublicClientApplication app = new PublicClientApplication(clientId, authority);
var accounts = await app.GetAccountsAsync();
AuthenticationResult result=null;
if (accounts.Any())
{
result = await app.AcquireTokenSilentAsync(scopes, accounts.FirstOrDefault());
}
else
{
try
{
result = await app.AcquireTokenByIntegratedWindowsAuthAsync(scopes);
}
catch (MsalUiRequiredException ex)
{
// For details see the article
I have an Azure Account, now I'm trying to get token in an console application to manage resources (i.e. create a resource group etc):
string userName = "xyz#gmail.com";
string password = "XXXXXXXXX";
string directoryName = "xyzgmail.onmicrosoft.com";
string clientId = "guid-of-registered-application-xxx";
var credentials = new UserPasswordCredential(userName, password);
var authenticationContext = new AuthenticationContext("https://login.windows.net/" + directoryName);
var result = await authenticationContext.AcquireTokenAsync("https://management.core.windows.net/", clientId, credentials);
On AcquireTokenAsync call I have
Microsoft.IdentityModel.Clients.ActiveDirectory.AdalServiceException:
'accessing_ws_metadata_exchange_failed: Accessing WS metadata exchange
failed'
Can anybody help, please?
Update: how I tried to create a resource group under newly created user
var jwtToken = result.AccessToken;
string subscriptionId = "XX-XX-XX-YY-YY-YY";
var tokenCredentials = new TokenCredentials(jwtToken);
var client = new ResourceManagementClient(tokenCredentials);
client.SubscriptionId = subscriptionId;
var rgResponse = await client.ResourceGroups.CreateOrUpdateWithHttpMessagesAsync("myresgroup77777",
new ResourceGroup("East US"));
Here I got another exception
'The client 'newaduser#xyzgmail.onmicrosoft.com' with object id
'aaa-aaa-aaa-aaa' does not have authorization to perform action
'Microsoft.Resources/subscriptions/resourcegroups/write' over scope
'/subscriptions/XX-XX-XX-YY-YY-YY/resourcegroups/myresgroup77777'.'
Not sure why you're getting the first error, but the second error is because the signed in user does not have permission to perform the operation (as mentioned in the error message).
When you assign the permission to execute Windows Azure Service Management API, it is actually assigned to the application which assumes the identity of the signed in user.
In order to perform Create Resource Group operation in Azure Subscription, that user must be in a role that allows this operation to be performed. You can try by assigning built-in Contributor role at the Azure Subscription level to this user.
Also, regarding using login.windows.net v/s login.microsoftonline.com, it is recommended that you use latter. When you use login.windows.net, it gets automatically redirected to login.microsoftonline.com. Using login.microsoftonline.com will save you one redirection.
I have created a universal app and an azure mobile services. I followed the tutorial on adding microsoft account authentication as is provided at the azure website. I am using the newest Live SDK for this purpose.
Client side, i am using this code, which is more or less straigt out of the tutorial:
const string ReferralUrl = "https://my-url-here.azure-mobile.net/";
static MobileServiceClient MobileService = new MobileServiceClient(
"https://my-url-here.azure-mobile.net/",
"my key");
MobileServiceUser _user;
private LiveConnectSession _session;
private async Task Authenticate()
{
var authClient = new LiveAuthClient(ReferralUrl);
while (_session == null)
{
var result = await authClient.LoginAsync(new[] { "wl.basic" });
if (result.Status == LiveConnectSessionStatus.Connected)
{
_session = result.Session;
var client = new LiveConnectClient(result.Session);
var meResult = await client.GetAsync("me");
return;
}
else
{
break;
}
}
_session = null;
}
public async Task<bool> Login()
{
try
{
await Authenticate();
_user = await MobileService
.LoginWithMicrosoftAccountAsync(_session.AuthenticationToken);
}
catch (Exception ex)
{
//Debug.WriteLine(ex.Message);
//return false;
}
return true;
}
The following facts have been verified for this application:
The store entry has been created
The store entry has been associated with the universal app (this has been performed for both, win8.1 and wp8.1 projects)
The store app has the following settings:
mobile or desktop: yes
jwt: yes
security: activated
redirection url: is set to https://my-url-here.azure-mobile.net/login/microsoftaccount
At the azure portal, in the identity tab, the client id and secret have been entered and saved. However, there is no package-SID (this is also not mentioned in the tutorial).
I downloaded the service project that is created with the mobile service and added it to my solution. Also, I have added the SetIsHosted(true) flag to the WebApiConfig class. Finally i added the AuthorizeLevel attribute to the TodoItemController and set it to User Level. The service has been successfully published to azure.
When i run my application and call Login, it will result in a 401 exception. It doesnt matter if i run it local or hosted.
Any ideas?
This feature - authentication based on information from a client-side SDK for Microsoft accounts (live SDK) or Facebook - is currently not supported in the .NET backend for mobile services. What the .NET backend supports is either the authentication via the web interface for Microsoft/Facebook/Google/Twitter, or client-side authentication for Azure Active Directory. The support for the feature you want should be implemented in a coming release. You can check out the Azure Mobile team blog for announcements on when it is live.