Get PowerBI access token using UserAssertion - azure

I have an Azure Function App with Easy Auth enabled. I want to get an access token to access to power bi with the permissions of the logged in user, so that user can directly access power bi from frontend.
I have registered an application on Azure AD with permissions to power bi and I created a HTTP triggered endpoint to return the access token to frontend.
I don't have any username or password in the function app, only the ID token of the user. Is there any way I can require an access token to access power bi on behalf of the user using the ID token or UserAssertion?
My implementation is here:
var context = new AuthenticationContext("https://login.windows.net/common/oauth2/authorize");
var res = await context.AcquireTokenAsync("https://analysis.windows.net/powerbi/api",
clientId, new Microsoft.IdentityModel.Clients.ActiveDirectory.UserAssertion(idToken)).ConfigureAwait(false);
return new OkObjectResult(new { Token = res.AccessToken });
But when I try to run this code, it throws me this exception
System.Private.CoreLib: Exception while executing function: GetPowerBiToken. Microsoft.IdentityModel.Clients.ActiveDirectory: AADSTS50027: JWT token is invalid or malformed.
Is this the correct way to get the token?

Per my understanding, you have a public client to log in users. This client will call APIs on the Azure function app and your function app will call PowerBi to do some tasks on behalf of users who logged in on your public client.
In this whole process, you should register two Azure AD apps :
1.An App for your public client, you should grant it delegated permission to access your Azure function App so that users could login and get an access token to call your function app .
2.An app for your Azure function app,you should grant it delegated permission related to powerbi, so that it could call powerbi apis on behalf of users.
So pls follow the steps below to finish this process :
1.On your client side , login users and get an access token which requested resources is your function app.
2.Call your function app with that access token so that your function app could use on-behalf-of flow to get an access token to call powerbi resources.
I have tested this process in an simple console app and it works perfectly for me, and the code is what you are looking for I think :
using Microsoft.IdentityModel.Clients.ActiveDirectory;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace AzureADUserAssertion
{
class Program
{
static void Main(string[] args)
{
var functionClientId = "<function azure ad app id>";
var functionSecret = "<function azure ad app secret>";
var functionCred = new ClientCredential(functionClientId, functionSecret);
var access_token_from_client_side = "<access token value>";
var context = new AuthenticationContext("https://login.windows.net/common/");
var res = context.AcquireTokenAsync("https://analysis.windows.net/powerbi/api",
functionCred, new Microsoft.IdentityModel.Clients.ActiveDirectory.UserAssertion(access_token_from_client_side, "urn:ietf:params:oauth:grant-type:jwt-bearer")).ConfigureAwait(false).GetAwaiter().GetResult();
Console.WriteLine( res.AccessToken );
Console.ReadKey();
}
}
}
Result :
Check this token :
Hope it helps .

Related

Authenticating sharepoint rest api with SSL cert

I am trying to access the sharepoint rest apis from Azure api management service. I need to send an access token for the request, But I am not sure how we can get the access token.
I am getting the access token in a console application using the following code. I used Microsoft.Identity.Client library in it. Anyone have any idea, how we can translate this code to APIM.
using Microsoft.Identity.Client;
using System;
using System.IO;
using System.Security.Cryptography.X509Certificates;
using System.Threading.Tasks;
public class TokenProvider
{
public static async Task<string> GetAccessTokenAsync(string endpoint)
{
var clientId = "<<AAD_APP_CLIENT_ID>>";
var tenantId = "<<AAD_TENANT_ID>>";
using var certificate = GetCertificate(
Path.Combine(Environment.CurrentDirectory, "MyAppCertificate.pfx"),
"<<CERTIFICATE_PASSWORD>>");
var confidentialClient = ConfidentialClientApplicationBuilder
.Create(clientId)
.WithTenantId(tenantId)
.WithCertificate(certificate)
.Build();
var token = await confidentialClient
.AcquireTokenForClient(new[] { $"{endpoint.TrimEnd('/')}/.default" })
.ExecuteAsync();
return token.AccessToken;
}
private static X509Certificate2 GetCertificate(string path, string password)
{
return new X509Certificate2(path, password, X509KeyStorageFlags.MachineKeySet);
}
}
Please check if the below steps that helps to workaround:
APIM has a Send Request policy that you can use in your inbound policy, along with the C# expression, to initiate any request before calling your backend services.
Here is an article gives information about how to implement Access Token Acquisition, Caching, and Renewal within your policy. To create the SharePoint online token, refer to this MS Q&A on what URL endpoint call you need to initiate and update your policy accordingly.
Refer this MSFT Documentation for more information on SharePoint REST services.
In the send-request policy, use the client certificate to authenticate. Authenticate policy is used to authenticate with a backend service using the client certificate, but authentication-certificate policy can be used at the end of your send-request. The certificate, which is identified by its thumbprint, must first be installed in API Management. Refer here for more information.

azure oauth 2.0 how to protect web api from external organization web api?

I am new to Azure and trying to protect/web api hosted in azure using oauth 2.0.
This web api will be called from other web api/deamon which is in control of other organization.
I am aware of client credential flow, but in this scenario external api is hosted outside azure ad. We have no idea of where it is hosted and how this third external web api/deamon is hosted? How should we do authentication/authorization for our web api, so that any external service can use it?
You know about client credential flow, then you should know that this kind of flow doesn't need a user to sign in to generate access token, but only need an azure ad application with the client secret. This azure ad application can come from your tenant, so it doesn't require the web api/deamon which is in control of other organization to have an application, you can create the app in your tenant then provide it to the external web api. What you need to make sure is that the external is really a daemon application.
Let's assume that the external app that need to call your api which is protected by azure ad is a daemon application, then client credential flow is suitable here.
Code for external api to generate access token
//you can see it when you add api permission
var scopes = new[] { "api://exposed_apis_app_id/.default" };
var tenantId = "your_tenant_name.onmicrosoft.com";
var clientId = "YOUR_CLIENT_ID";
var clientSecret = "YOUR_CLIENT_SECRET";
// using Azure.Identity;
var options = new TokenCredentialOptions{AuthorityHost = AzureAuthorityHosts.AzurePublicCloud};
var clientSecretCredential = new ClientSecretCredential(tenantId, clientId, clientSecret, options);
var tokenRequestContext = new TokenRequestContext(scopes);
var token = clientSecretCredential.GetTokenAsync(tokenRequestContext).Result.Token;
Code for your api to add authentication for azure ad, you still have some more configurations, you can refer to my this answer, some related document: authorize the token with role and jwt token configuration.
[Authorize]
public class HelloController : Controller
{
public IActionResult Index()
{
HttpContext.ValidateAppRole("User.Read");//You set it when adding app role
Student stu = new Student();
stu.age = 18;
return Json(stu) ;
}
}
appsettings:
"AzureAd": {
"Instance": "https://login.microsoftonline.com/",
"ClientId": "2c0xxxxxxx57",
"Domain": "tenantname.onmicrosoft.com", // for instance contoso.onmicrosoft.com. Not used in the ASP.NET core template
"TenantId": "common",
"Audience": "8fxxxx78"
}
startup.cs, don't forget "app.UseAuthentication();app.UseAuthorization();" in Configure method
public void ConfigureServices(IServiceCollection services)
{
services.AddMicrosoftIdentityWebApiAuthentication(Configuration);
services.AddControllersWithViews();
}
create an azure ad application in your tenant and expose an api with a role.
you can create another azure ad application, add client secret and add the application permission created before in the API permissions blade.
provide the application id and client secret to those external app and let them use these to generate access token, then they can use the access token to call your api.
modify your api to authorize the token if has the correct role.

What is the best practice to authenticate an Azure AD registered app?

I am developing an application which is based on GCP (Specifically it runs on the Google Cloud Composer (a managed version of Airflow)). From there I would like to connect to the Microsoft Graph API of another organization. So I want the application to be running in the background and every x minutes retrieve new emails from the inbox of an external organization, then do some processing and based on that perform some actions in said mailbox through the Graph API.
I am trying to figure out the best practice on how to secure this connection. I believe I could use the client secret of the registered application and then store that in an azure keyvault. Now I am not sure how I should authenticate my registered app such that it can retrieve this client secret which in turn can be used to access the Graph API?
As you can probably tell I'm not quite sure whether this makes sense or what a better approach would be? Almost all the information I am finding is concerning managed identities, however if I understood correctly these can only be used when the application is running natively on Azure, which for me is not the case.
Would really appreciate any help!
If I don't misunderstand, you can refer to my sample code, pls note you need to add key vault access policy first. And you may refer to this doc to know about the default azure credential.
using Azure.Identity;
using Azure.Security.KeyVault.Secrets;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Identity.Client;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace test0430callingapi.Controllers
{
public class HelloController : Controller
{
public async Task<string> IndexAsync()
{
const string secretName = "clientsecret";
var kvUri = "https://key_vault_name.vault.azure.net/";
var a = new DefaultAzureCredential();
var client = new SecretClient(new Uri(kvUri), a);
var secret = await client.GetSecretAsync(secretName);
string secretVaule = secret.Value.Value;
//azure ad client credential flow to generate access token
IConfidentialClientApplication app;
app = ConfidentialClientApplicationBuilder.Create("azure_ad_app_clientid")
.WithClientSecret(secretVaule)
.WithAuthority(new Uri("https://login.microsoftonline.com/your_tanent_name.onmicrosoft.com"))
.Build();
AuthenticationResult result = null;
string[] scopes = new string[] { "https://graph.microsoft.com/.default" };
result = await app.AcquireTokenForClient(scopes)
.ExecuteAsync();
string accesstoken = result.AccessToken;
// this token can be used to call https://graph.microsoft.com/v1.0/users/user_id/mailFolders/{id}/messages
return accesstoken;
}
}
}
I used client credential flow here which don't need to make users sign in, and this api support application permission. If you use an api which only supports delegated permission, it can't work. Here's the api calling response.

Accessing Google Drive through Azure Function

The task is to download google sheet in excel format and store it in Azure blob storage on timely basics using the Azure time trigger function.
Access Method to users google drive - OAuth Client ID.
I have created an Azure function locally and it works fine as expected and performs the task but when I deploy azure function I get this error.
Code for DriveService where the error occurs according to stack trace when deployed
public string[] Scopes = { DriveService.Scope.Drive, DriveService.Scope.DriveReadonly };
public DriveService GetService()
{
UserCredential _credential;
//Error Occurs at line below
Google.Apis.Auth.OAuth2.Flows.GoogleAuthorizationCodeFlow googleAuthFlow = new GoogleAuthorizationCodeFlow(new GoogleAuthorizationCodeFlow.Initializer()
{
ClientSecrets = new ClientSecrets
{
ClientId = _config[Constant.ClientId],
ClientSecret = _config[Constant.ClientSecret],
}
});
string FilePath = Path.GetDirectoryName(_driveCredentialsPath);
_credential = GoogleWebAuthorizationBroker.AuthorizeAsync(
googleAuthFlow.ClientSecrets,
Scopes,
"user",
CancellationToken.None,
new FileDataStore(FilePath, true)).Result;
DriveService service = new DriveService(new BaseClientService.Initializer()
{
HttpClientInitializer = _credential,
ApplicationName = Constant.ApplicationName,
});
return service;
}
I think there are two situations where it can go wrong but I am not sure about it.
When I am running the application locally a consent screen appears and gives permission to access the drive.
When this same function is running on azure who and how it will grant permission to access the drive.
I have provided my Azure App URL on Google OAuth Consent Screen as mentioned below to overcome this situation.
When I am running locally after giving permission to access drive it creates a TOKENRESPONSE-USER file a which consists of the access token, expiry date refresh token, and scope.
Is this possible that when the function is deployed it is unable to create a TOKENRESPONSE-USER file on azure function?
Please let me know why I am getting this error or do I need to change something in my process.
You can configure your function app to use Google login for authentication purposes when running on Azure. To achieve this you have to generate client id and client secret using the Google sign-in for server-side apps, using this connection you can store the tokens obtained in the token store. Please refer to this document to configure your function app to use Google Login, refer to this document regarding the token store and how to retrieve and refresh the token obtained.

Service to service authentication in Azure without ADAL

I configured azure application proxy for our on-premise hosted web service and turned on Azure AD authentication. I am able to authenticate using ADAL but must find a way to get the token and call web service without ADAL now (we are going to use this from Dynamics 365 online and in sandbox mode I can't use ADAL). I followed some examples regarding service to service scenario and I successfully retrieve the token using client credentials grant flow. But when I try to call the app proxy with Authorization header and access token, I receive an error "This corporate app can't be accessed right now. Please try again later". Status code is 500 Internal server error.
Please note the following:
I don't see any error in app proxy connectors event log.
I added tracing on our on-premise server and it seems like the call never comes there.
If I generate token with ADAL for a NATIVE app (can't have client_secret so I can't use client credentials grant flow), I can call the service.
I created an appRole in manifest for service being called and added application permission to the client app.
This is the way I get the token:
public async static System.Threading.Tasks.Task<AzureAccessToken> CreateOAuthAuthorizationToken(string clientId, string clientSecret, string resourceId, string tenantId)
{
AzureAccessToken token = null;
string oauthUrl = string.Format("https://login.microsoftonline.com/{0}/oauth2/token", tenantId);
string reqBody = string.Format("grant_type=client_credentials&client_id={0}&client_secret={1}&resource={2}", Uri.EscapeDataString(clientId), Uri.EscapeDataString(clientSecret), Uri.EscapeDataString(resourceId));
HttpClient client = new HttpClient();
HttpContent content = new StringContent(reqBody);
content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/x-www-form-urlencoded");
using (HttpResponseMessage response = await client.PostAsync(oauthUrl, content))
{
if (response.IsSuccessStatusCode)
{
DataContractJsonSerializer serializer = new DataContractJsonSerializer(typeof(AzureAccessToken));
Stream json = await response.Content.ReadAsStreamAsync();
token = (AzureAccessToken)serializer.ReadObject(json);
}
}
return token;
}
AzureAccessToken is my simple class marked for serialization.
I assume it must be something I haven't configured properly. Am I missing some permissions that are required for this scenario?
Any help is appriciated.

Resources