Trying to authenticate with Azure for using Power BI Embedded - azure

Below is the output from an error which is being generated whilst trying to authenticate credentials with Azure for allowing public access to a Power BI Embedded report:
It has initially displayed a pop to confirm that I want to give but after I have given permission the above error is displayed.
I have checked the credentials I have entered in the appsettings.json file and have confirmed that they are correct.
In the Startup.cs file I have added this section:
services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
.AddMicrosoftIdentityWebApp(Configuration.GetSection("AzureAd"), "AzureAd")
.EnableTokenAcquisitionToCallDownstreamApi(Configuration.GetValue<string>("DownstreamApi:Scopes")?.Split(' '))
.AddMicrosoftGraph(Configuration.GetSection("DownstreamApi"))
.AddInMemoryTokenCaches();
services.AddScoped(typeof(PowerBiServiceApi));
services.AddRazorPages()
.AddMicrosoftIdentityUI();
The error itself is being generated whilst acquiring the access token in the PowerBiServiceApi the code for which is provided below:
using System;
using System.Threading.Tasks;
using Microsoft.Extensions.Configuration;
using Microsoft.Identity.Web;
using Microsoft.PowerBI.Api;
using Microsoft.PowerBI.Api.Models;
using Microsoft.Rest;
namespace NDTSM2.Services.Implementations.PowerBI
{
// A view model class to pass the data needed to embed a single report.
public class EmbeddedReportViewModel
{
public string Id;
public string Name;
public string EmbedUrl;
public string Token;
}
public class PowerBiServiceApi
{
private ITokenAcquisition tokenAcquisition { get; }
private string urlPowerBiServiceApiRoot { get; }
public PowerBiServiceApi(IConfiguration configuration, ITokenAcquisition tokenAcquisition)
{
this.urlPowerBiServiceApiRoot = configuration["PowerBi:ServiceRootUrl"];
this.tokenAcquisition = tokenAcquisition;
}
public const string powerbiApiDefaultScope = "https://analysis.windows.net/powerbi/api/.default";
// A method to get the Azure AD token (also known as 'access token')
public string GetAccessToken()
{
return this.tokenAcquisition.GetAccessTokenForAppAsync(powerbiApiDefaultScope).Result;
}
public PowerBIClient GetPowerBiClient()
{
var tokenCredentials = new TokenCredentials(GetAccessToken(), "Bearer");
return new PowerBIClient(new Uri(urlPowerBiServiceApiRoot), tokenCredentials);
}
public async Task<EmbeddedReportViewModel> GetReport(Guid WorkspaceId, Guid ReportId)
{
PowerBIClient pbiClient = GetPowerBiClient();
// Call the Power BI service API to get the embedding data
var report = await pbiClient.Reports.GetReportInGroupAsync(WorkspaceId, ReportId);
// Generate a read-only embed token for the report
var datasetId = report.DatasetId;
var tokenRequest = new GenerateTokenRequest(TokenAccessLevel.View, datasetId);
var embedTokenResponse = await pbiClient.Reports.GenerateTokenAsync(WorkspaceId, ReportId, tokenRequest);
var embedToken = embedTokenResponse.Token;
// Return the report embedded data to caller
return new EmbeddedReportViewModel
{
Id = report.Id.ToString(),
EmbedUrl = report.EmbedUrl,
Name = report.Name,
Token = embedToken
};
}
}
}
Does anyone have any ideas why the error is being generated (have looked for guidance but so far none of the advice has rectified the issue)?
Any help would be very much appreciated.
Further to original question:
Error Details:
Operation returned an invalid status code 'Unauthorized'
at Microsoft.PowerBI.Api.ReportsOperations.GetReportInGroupWithHttpMessagesAsync(Guid groupId, Guid reportId, Dictionary`2 customHeaders, CancellationToken cancellationToken)
at Microsoft.PowerBI.Api.ReportsOperationsExtensions.GetReportInGroupAsync(IReportsOperations operations, Guid groupId, Guid reportId, CancellationToken cancellationToken)
at Microsoft.PowerBI.Api.ReportsOperationsExtensions.GetReportInGroup(IReportsOperations operations, Guid groupId, Guid reportId)
at NDTSM2.Services.Implementations.PowerBI.PbiEmbedService.GetEmbedParams(Guid workspaceId, Guid reportId, Guid additionalDatasetId) in C:\Users\cryof\Desktop\NDTMS4\Service\NDTSM2.SERVICES\Implementations\PowerBI\PbiEmbedService.cs:line 41
at NDTMS2.Web.Controllers.EmbedInfoController.GetEmbedInfo() in C:\Users\cryof\Desktop\NDTMS4\NDTMS2.WEB\Controllers\EmbedInfoController.cs:line 40
The line that is generating the error in the PbiEmbedService is this:
var pbiReport = pbiClient.Reports.GetReportInGroup(workspaceId, reportId);

Follow the sample here
// For app only authentication, we need the specific tenant id in the authority url
var tenantSpecificUrl = azureAd.Value.AuthorityUrl.Replace("organizations", azureAd.Value.TenantId);
// Create a confidential client to authorize the app with the AAD app
IConfidentialClientApplication clientApp = ConfidentialClientApplicationBuilder
.Create(azureAd.Value.ClientId)
.WithClientSecret(azureAd.Value.ClientSecret)
.WithAuthority(tenantSpecificUrl)
.Build();
// Make a client call if Access token is not available in cache
authenticationResult = clientApp.AcquireTokenForClient(azureAd.Value.ScopeBase).ExecuteAsync().Result;

Related

Need help demystifying the new feature introduced on Microsoft.Graph 4.0.0

Question:
I am not sure if this falls under question or code review because the code works where I do not know if it is implemented correctly. But, do we need to acquire the access token from Microsoft.Graph using either silent or interactive modes? From what I can tell the answer is, No. (see Context below)
The new implementation seems to be drastically scaled down with the whole idea of silent and interactive token retrieval being removed. Is this correct?
using Azure.Identity;
using Microsoft.Graph;
using System;
namespace ConsoleApp1
{
class Program
{
static void Main(string[] args)
{
var scopes = new[] { "User.Read" };
// Multi-tenant apps can use "common",
// single-tenant apps must use the tenant ID from the Azure portal
var tenantId = "SomeGuid";
// Value from app registration
var clientId = "SomeGuid";
var options = new InteractiveBrowserCredentialOptions
{
TenantId = tenantId,
ClientId = clientId,
AuthorityHost = AzureAuthorityHosts.AzurePublicCloud,
// MUST be http://localhost or http://localhost:PORT
// See https://github.com/AzureAD/microsoft-authentication-library-for-dotnet/wiki/System-Browser-on-.Net-Core
RedirectUri = new Uri("http://localhost:1234"),
};
// https://learn.microsoft.com/dotnet/api/azure.identity.interactivebrowsercredential
var interactiveCredential = new InteractiveBrowserCredential(options);
var graphClient = new GraphServiceClient(interactiveCredential, scopes);
// Interactive browser login occurs here.
var me = graphClient.Me.Request().GetAsync().Result;
// Printing the results
Console.WriteLine("-------- Data from call to MS Graph --------");
Console.Write(Environment.NewLine);
Console.WriteLine($"Id: {me.Id}");
Console.WriteLine($"Display Name: {me.DisplayName}");
Console.WriteLine($"Email: {me.Mail}");
//Console.ReadLine();
}
}
}
Context:
As part of our routine maintenance, I was tasked with upgrading our NuGet packages on a Winforms desktop application that is running in Azure and whose users are in Azure Active Directory Services (AADS). One of the packages, Microsoft.Graph, had a major version change. https://www.nuget.org/packages/Microsoft.Graph/4.0.0
The documentation on it indicated a new feature for handling the TokenCredentialClass. https://github.com/microsoftgraph/msgraph-sdk-dotnet/blob/4.0.0/docs/upgrade-to-v4.md#new-capabilities
From what I can tell, there is a separate and distinct break on how the token is retrieved. Previously, we followed the method provided here: https://learn.microsoft.com/en-us/azure/active-directory/develop/tutorial-v2-windows-desktop#add-the-code-to-initialize-msal
Old way:
using Microsoft.Graph;
using Microsoft.Graph.Auth;
using Microsoft.Identity.Client;
using System;
using System.Linq;
using System.Threading.Tasks;
namespace ConsoleApp1
{
class Program
{
static void Main(string[] args)
{
_PublicClientApp = PublicClientApplicationBuilder.Create(ClientId)
.WithRedirectUri("http://localhost:1234")
.WithAuthority(AzureCloudInstance.AzurePublic, TenantId)
.Build();
// We sign the user in here
bolIsAutorizeSSO = CallMicrosoftSSO().GetAwaiter().GetResult();
InteractiveAuthenticationProvider = new InteractiveAuthenticationProvider(PublicClientApp, Scopes);
GraphServiceClient = new Microsoft.Graph.GraphServiceClient(InteractiveAuthenticationProvider);
if (bolIsAutorizeSSO)
{
// We also signt the user in here.
var User = GraphServiceClient.Me.Request().GetAsync().Result;
// Printing the results
Console.WriteLine("-------- Data from call to MS Graph --------");
Console.Write(Environment.NewLine);
Console.WriteLine($"Id: {User.Id}");
Console.WriteLine($"Display Name: {User.DisplayName}");
Console.WriteLine($"Email: {User.Mail}");
}
else
{
// signout
Console.ReadLine();
}
}
public static async Task<bool> CallMicrosoftSSO()
{
AuthenticationResult authResult = null;
var app = PublicClientApp;
var accounts = await app.GetAccountsAsync();
try
{
authResult = await app.AcquireTokenInteractive(Scopes)
.WithAccount(accounts.FirstOrDefault())
.WithPrompt(Microsoft.Identity.Client.Prompt.ForceLogin)
.ExecuteAsync();
}
catch (MsalUiRequiredException _Exception)
{
// A MsalUiRequiredException happened on AcquireTokenSilent.
// This indicates you need to call AcquireTokenInteractive to acquire a token.
Console.WriteLine(_Exception.Message);
}
catch (MsalException msalex)
{
if (msalex.ErrorCode != "authentication_canceled")
{
Console.WriteLine(msalex.Message);
}
}
catch (Exception _Exception)
{
Console.WriteLine(_Exception.Message);
}
if (authResult != null)
{
return true;
}
return false;
}
private static string ClientId = "SomeGuid";
private static string TenantId = "SomeGuid";
private static string[] Scopes = new string[] { "User.Read" };
private static Microsoft.Graph.GraphServiceClient GraphServiceClient;
private static bool bolIsAutorizeSSO = false;
private static InteractiveAuthenticationProvider InteractiveAuthenticationProvider;
private static IPublicClientApplication _PublicClientApp;
public static IPublicClientApplication PublicClientApp { get { return _PublicClientApp; } }
}
}
I am struggling to make sense of it. Partly because the feature is brand new and there are very few code samples up on the internet that say do it this way. What I have found seems to point me back to what we already are using (more on that in a bit). So, the examples may not yet be fully updated.

How to call an Azure Function App API with Easy-Auth Enables using Active Directory from a C# Client

I have an Azure Function App with Azure Active Directory configured but when I call if from my client I keep getting an Unauthorized response.
I have tried a couple different scenarios but nothing worked. Below is a snippet of the last bit of code that I tried.
///
var #params2 = new NameValueCollection
{
{"grant_type", "client_credentials"},
{"client_id", $"{ClientId}"},
{"client_secret", $"{ClientSecret}"},
{"username", userId},
{"resource", "https://management.azure.com/"}
};
var queryString2 = HttpUtility.ParseQueryString(string.Empty);
queryString2.Add(#params2);
var content = new FormUrlEncodedContent(new Dictionary<string, string>
{
{"grant_type", "client_credentials"},
{"client_id", ClientId},
{"client_secret", ClientSecret},
{"username", userId}
});
var authorityUri2 = $"{string.Format(CultureInfo.InvariantCulture, AadInstance, Tenant).TrimEnd('/')}/oauth2/token";
//var authorityUri2 = $"https://login.microsoftonline.com/{Tenant}/v2.0/.well-known/openid-configuration";
var authUri2 = String.Format("{0}?{1}", authorityUri2, queryString2);
var client2 = new HttpClient();
var message = client2.PostAsync(authorityUri2, content).Result;
//var message = client2.GetAsync(authorityUri2).Result;
var response = message.Content.ReadAsStringAsync().Result;
dynamic values=null;
try
{
values = JsonConvert.DeserializeObject<Dictionary<string, string>>(response);
}
catch
{
values = response;
}
var AuthToken2 = values["access_token"];
client2.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", AuthToken2);
HttpResponseMessage response2 = await client2.GetAsync(AppBaseAddress.TrimEnd('/') + "/api/AADIntegration");
if (response.IsSuccessStatusCode)
{
// Read the response and data-bind to the GridView to display To Do items.
string s = await response.Content.ReadAsStringAsync();
log.LogInformation($"Success while getting / api / AADIntegration : {s}");
return (ActionResult)new OkObjectResult(s);
}
else
{
string failureDescription = await response.Content.ReadAsStringAsync();
log.LogInformation($"An error occurred while getting / api / AADIntegration : {response.ReasonPhrase}\n {failureDescription}");
return (ActionResult)new OkObjectResult(failureDescription);
}
Data should returned from the Function App.
For client_credentials grant flow your code seems little different. Here I am giving you exact sample for azure function. Just plug and play :))
Example contains:
How would you get token using client_credentials flow
Getting user list From Azure Active Directory tenant using above
token
Access Token Class:
public class AccessTokenClass
{
public string token_type { get; set; }
public string expires_in { get; set; }
public string resource { get; set; }
public string scope { get; set; }
public string access_token { get; set; }
}
Reference To Add:
using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.Http;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using System.Net.Http;
using System.Collections.Generic;
using System.Net.Http.Headers;
Azure Function Body:
public static class FunctionGetUserList
{
[FunctionName("FunctionGetUserList")]
public static async Task<IActionResult> Run(
[HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = null)] HttpRequest req,
ILogger log)
{
try
{
log.LogInformation("C# HTTP trigger function processed a request.");
//Token Request endpoint Just replace yourTennantId/Name
string tokenUrl = $"https://login.microsoftonline.com/yourTennantId/Name.onmicrosoft.com/oauth2/token";
var tokenRequest = new HttpRequestMessage(HttpMethod.Post, tokenUrl);
tokenRequest.Content = new FormUrlEncodedContent(new Dictionary<string, string>
{
["grant_type"] = "client_credentials",
["client_id"] = "b603c7bead87-Your_client_id-e6921e61f925",
["client_secret"] = "Vxf1SluKbgu4P-Your_client_Secret-F0Nf3wE5oGl/2XDSeZ=",
["resource"] = "https://graph.microsoft.com"
});
dynamic json;
AccessTokenClass results = new AccessTokenClass();
HttpClient client = new HttpClient();
var tokenResponse = await client.SendAsync(tokenRequest);
json = await tokenResponse.Content.ReadAsStringAsync();
results = JsonConvert.DeserializeObject<AccessTokenClass>(json);
var accessToken = results.access_token;
//Create Request To Server
using (HttpClient clientNew = new HttpClient())
{
//Pass Token on header
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
//Get Data from API
var requestToAzureEndpoint = await client.GetAsync("https://graph.microsoft.com/v1.0/users");
if (requestToAzureEndpoint.IsSuccessStatusCode)
{
var result_string = await requestToAzureEndpoint.Content.ReadAsStringAsync();
dynamic responseResults = JsonConvert.DeserializeObject<dynamic>(result_string);
return new OkObjectResult(responseResults);
}
else
{
var result_string = await requestToAzureEndpoint.Content.ReadAsStringAsync();
return new OkObjectResult(result_string);
}
}
}
catch (Exception ex)
{
return new OkObjectResult(ex.Message);
}
}
}
Point To Remember
For Azure Active Directory List users access make sure you have following permission:
User.Read.All
Permission Type: Application
You can check here. See the screen shot for better understanding; make sure you have clicked "Grant admin consent for yourTenant" after adding permission.
Note: This is how you can access Azure Active Directory Token using Azure Function after that how to access resource using that token to a specific API endpoint efficiently.
Are you sure you have properly implemented this properly? It looks like a few of your parameters are wrong for the client credential flow. Please double check that you are properly following the client credential flow.
The client credential grant flow is documented here : https://learn.microsoft.com/en-us/azure/active-directory/develop/v2-oauth2-client-creds-grant-flow
But for more information on getting this properly working in your function app, please refer to the blog below for more information/help on implementing this.
https://blogs.msdn.microsoft.com/ben/2018/11/07/client-app-calling-azure-function-with-aad/
The value of resource is not correct.
Replace {"resource", "https://management.azure.com/"} with {"resource", $"{ClientId}"}

Using OAuthCard to call Azure Function

I have a V3 Bot, and from a Dialog I want to call an Azure Function which is locked down by Azure AD V1. I want to get a token from Azure AD and use this token to access the Azure Function.
I'm trying to use OAuthCard with an Azure AD provider, and setting the Resource URL to my Azure Function https://my-function-app.azurewebsites.net
When I sign in to the OAuthCard I get the error "The application named https://my-function-app.azurewebsites.net was not found in the tenant named 880fb54d-f717-4364-9a22-df9ac5c77f6d"
The Function App does live in that tenant. Below are the OAuth Connection Settings configured in the Bot Channel Registration.
Is it possible to use OAuthCard to call an Azure Function locked down by Azure AD?
I have a V3 Bot, and from a Dialog I want to call an Azure Function which is locked down by Azure AD V1. I want to get a token from Azure AD and use this token to access the Azure Function.
If possible, you can directly make request to acquire an access token for the https://my-function-app.azurewebsites.net from you bot application, like below:
//Acquire token
var client = new RestClient($"https://login.microsoftonline.com/{tenantId}/oauth2/token");
var request = new RestRequest(Method.POST);
request.AddHeader("content-type", "application/x-www-form-urlencoded");
request.AddParameter("application/x-www-form-urlencoded", $"grant_type=client_credentials&client_id={client_id}&client_secret={client_secret}&resource={resource}", ParameterType.RequestBody);
IRestResponse response = client.Execute(request);
var tokenResponse = JsonConvert.DeserializeObject<TokenResponse>(response.Content);
var access_token = tokenResponse.access_token;
TokenResponse class:
public class TokenResponse
{
public string token_type { get; set; }
public string expires_in { get; set; }
public string ext_expires_in { get; set; }
public string expires_on { get; set; }
public string not_before { get; set; }
public string resource { get; set; }
public string access_token { get; set; }
}
and then you can call/access your function app endpoint using that access token.
//Call Azure function using access token
var client2 = new RestClient($"https://xxxxfunction.azurewebsites.net/api/HttpTriggerFunc?code=CR9X9VsIattzWybmvasvpjAXfQU2feRuV3jXC6p/0B2AlFgl4LwPMw==");
var request2 = new RestRequest(Method.POST);
request2.AddHeader("Authorization", $"Bearer {access_token}");
request2.AddHeader("Content-Type", "application/json");
request2.RequestFormat = DataFormat.Json;
request2.AddBody(new { name = "Fei Han" });
IRestResponse response2 = client2.Execute(request2);
var funcResponse = JsonConvert.DeserializeObject<string>(response2.Content);
await context.PostAsync($"Response returned from Azure function: {funcResponse}.");
Test result:
Note:
For detailed information, please check "Service to service calls using client credentials".

Token in permissions dissapears using documentdb and xamarin forms

I am trying to send permissions for documentdb for a specific user from my azure server to my client app, which are xamarin forms.
On server side everything looks good and I can see users specific permissions and token.
But when permissions are received in the client, the token is stripped away, why?
I am new with documentdb so hopefully it is just me.
I am using an Azure Mobile App service as backend.
My backend controller returns an object holding properties for documentdb database including a list of permissions for the user.
public class DbConfig
{
public string DatabaseName { get; set; }
public string CollectionId { get; set; }
public string EndpointUri { get; set; }
public IList<Permission> Permissions { get; set; }
}
I create a permission for a user for the entire collection if not already created.
public async Task<Permission> CreatePermissionAsync(string resourceLink, string userLink, PermissionMode mode, string resourcePartitionKey = null)
{
try
{
Permission permission = new Permission
{
Id = Guid.NewGuid().ToString("N"),
PermissionMode = mode,
ResourceLink = resourceLink
};
if (resourcePartitionKey != null)
{
permission.ResourcePartitionKey = new PartitionKey(resourcePartitionKey);
}
var result = await client.CreatePermissionAsync(userLink, permission);
DbConfig.Permissions.Add(result);
return result;
}
catch (Exception e)
{
Trace.WriteLine($"##### Exception: {e}");
throw;
}
}
I retrieve permissions for a user with this method.
public List<Permission> GetPermissionsForUserPermissionLink(User user)
{
var permFeed = client.CreatePermissionQuery(user.PermissionsLink);
List<Permission> permList = new List<Permission>();
foreach (Permission perm in permFeed)
{
permList.Add(perm);
DbConfig.Permissions.Add(perm);
}
return permList;
}
On the client side in my Xamarin forms app i use this call to my custom controller in the backend.
var parameters = new Dictionary<string, string> { { "userid", Settings.AzureUserId } };
dbConfig = await client.InvokeApiAsync<DbConfig>("Settings", HttpMethod.Get, parameters);
When i look at the permissionlist in the dbConfig object the token for a permission is null. My thought was that I could instantiate a documentdb client based on the permissionslist but it fails.
public void CreateDocumentDbClient(DbConfig config)
{
client = new DocumentClient(new Uri(config.EndPointUri), config.Permissions);
collectionLink = UriFactory.CreateDocumentCollectionUri(config.DatabaseName, config.CollectionId);
IsInitialized = true;
}
EDITS MADE FROM ANSWER
Just for finish up upon question.
I created a custom class holding both Permission and Token
public class PermissionCustom
{
public Permission Permission { get; set; }
public string Token { get; set; }
}
This makes it possible to create a documentdb client like this:
client = new DocumentClient(new Uri(config.EndPointUri), config.Permissions[0].Token);
So far so good :-) but it doesn't makes it easier to secure your database considering users could have many permissions for different resources. Even though it is properly to make it more secure, the token is readonly in the first place.
According to your code, I have checked this issue and found I could encounter the same issue. When you invoke client.InvokeApiAsync<DbConfig>("Settings", HttpMethod.Get, parameters);, you would send request with the following link:
https://{your-app-name}.azurewebsites.net/api/settings?userid={Settings.AzureUserId}
By using fiddler you could find that the token has been sent to your mobile client as follows:
But when deserialize it to Permission, the token has not been initialized correctly. I found that the token property is read only as follows:
In summary, I recommend that you need to define your custom Permission class and refer to the Permission class provided by DocumentDB client SDK for defining the properties you need within your custom permission class in your mobile client.

NullReferenceException in Microsoft.IdentityModel.Clients.ActiveDirectory.AcquireToken

I'm trying to get some MSFT Power BI SDK samples working. Unfortunately, the Microsoft.IdentityModel.Clients.ActiveDirectory library is giving me a lot of trouble with the initial external authentication step.
I'm using Microsoft.IdentityModel.Clients.ActiveDirectory, Version=2.28.3.860, from NuGet; this is the last version of the library before AcquireToken was removed, and I haven't figured out how to use its replacement (AcquireTokenAsync) in a way that's equivalent to what I see in the samples.
When I take the following code and modify the TODO lines to specify my actual Azure Client ID and authentication redirect page, I get as far as the AcquireToken line.
using System;
using Microsoft.IdentityModel.Clients.ActiveDirectory;
public class Application
{
public static void Main(string[] args)
{
try
{
string clientID = "abcdef01-1234-1234-abcd-abcdabcd1234"; // TODO: actual Azure client ID
string redirectUri = "https://acmecorporation.okta.com/login/do-login"; // TODO: actual redirect
string resourceUri = "https://analysis.windows.net/powerbi/api";
string authorityUri = "https://login.windows.net/common/oauth2/authorize";
AuthenticationContext authContext = new AuthenticationContext(authorityUri);
AuthenticationResult ar = authContext.AcquireToken(
resourceUri,
clientID,
new Uri(redirectUri),
PromptBehavior.RefreshSession);
string token = ar.AccessToken;
Console.WriteLine("Success: " + token);
}
catch (Exception ex)
{
Console.WriteLine(ex.ToString());
}
}
}
At this point:
A "Sign in to your account" window pops up with the name of the app I've associated in Azure with the clientID GUID
I'm redirected to my organization's ("acmecorporation") sign-on page
I sign in using my AD credentials
The AcquireToken method throws the following NullReferenceExpection:
System.NullReferenceException: Object reference not set to an instance of an object.
at Microsoft.IdentityModel.Clients.ActiveDirectory.AuthenticationContext.RunAsyncTask[T](Task`1 task)
at Microsoft.IdentityModel.Clients.ActiveDirectory.AuthenticationContext.AcquireToken(String resource, String clientId, Uri redirectUri, PromptBehavior promptBehavior)
at PowerBISample.Application.Main(String[] args) in \\noxfile\users\ehirst\documents\visual studio 2015\Projects\PowerBISample\PowerBISample\Program.cs:line 18
Can anyone provide guidance on how to get past this? My goal is to get a POC working to determine whether we can integrate Power BI into a larger application, but so far it feels like I'm beta testing a pretty unstable system.
The NullReferenceException is a bug in the 2.x version of the ADAL library; it's fixed in current versions. It was triggered by an incorrect value of redirectUri; unfortunately the documentation was sparse on this one. A working code sample, adapted (thanks Kanishk!) to use the current 3.13.7 version of ADAL, is posted below.
namespace PowerBISample
{
using System;
using Microsoft.IdentityModel.Clients.ActiveDirectory;
using System.Threading.Tasks;
public class Application
{
public static void Main(string[] args)
{
Run();
Console.ReadLine();
}
static async void Run()
{
try
{
string clientID = "abcdef01-1234-1234-abcd-abcdabcd1234"; // TODO: actual Azure client ID
/** THE REAL PROBLEM WAS HERE **/
string redirectUri = "https://login.live.com/oauth20_desktop.srf";
string resourceUri = "https://analysis.windows.net/powerbi/api";
string authorityUri = "https://login.windows.net/common/oauth2/authorize";
AuthenticationContext authContext = new AuthenticationContext(authorityUri);
AuthenticationResult ar = await authContext.AcquireTokenAsync(resourceUri, clientID, new Uri(redirectUri), new PlatformParameters(PromptBehavior.RefreshSession));
string token = ar.AccessToken;
Console.WriteLine("Success: " + token);
}
catch (Exception ex)
{
string error = ex.ToString();
Console.WriteLine(error);
}
}
}
}

Resources