I have a Web API and a UI application that used ADAL library to call the Web API.
I already gave DELEGATED PERMISSIONS (Read directory data) for both Web API and UI application while registering the apps to Azure AD.
I have below code in Web API to save Token for log-In user,
private void ConfigureAuthentication(IAppBuilder app)
{
app.UseWindowsAzureActiveDirectoryBearerAuthentication(
new WindowsAzureActiveDirectoryBearerAuthenticationOptions
{
Tenant = ConfigurationManager.AppSettings["ida:Tenant"],
TokenValidationParameters = new TokenValidationParameters { SaveSigninToken = true, ValidAudience = ConfigurationManager.AppSettings["ida:Audience"] }
});
}
Now in Web API controllers, I am trying to get token to access Microsoft AD Graph API using below code,
var bootstrapContext = ClaimsPrincipal.Current.Identities.First().BootstrapContext as System.IdentityModel.Tokens.BootstrapContext;
string userName = "test#onmicrosoft.com";
string userAccessToken = bootstrapContext.Token;
UserAssertion userAssertion = new UserAssertion(bootstrapContext.Token, "urn:ietf:params:oauth:grant-type:jwt-bearer", userName);
var authContext = new AuthenticationContext(string.Format(CultureInfo.InvariantCulture, aadInstance, tenant));
var clientCred = new ClientCredential(clientId, appKey);
var result = await authContext.AcquireTokenAsync("https://graph.windows.net", clientCred, userAssertion);
accessToken = result.AccessToken;
The above code giving me token back but the scope values is below,
`scp: "User.Read"`
Question - Why the token not giving directory access (Directory.Read.All) as I already set Directory access?
`scp: "Directory.Read.All User.Read"`
Update:
I am missing Grant Permission for Read Directory Data under DELEGATED PERMISSIONS. After giving Grant Permission I am able to get token with scope scp: "Directory.Read.All User.Read"
If I understand correctly, you want to use Microsoft Graph API ,not Azure AD Graph API.
However, based on that the screenshot you post in this question is a v1 enpoint AAD Application, it does nothing about Microsoft Graph API which you're trying to approaching. So, whatever you changed on this application, the result shouldn be same. I suggest you register v2 enpoint Application in https://apps.dev.microsoft.com/
Here is a document which shows how to get auth tokens for using Microsoft Graph.
Hope this helps!
Related
We are planning to move from Organization Service to Common Data Service Web API so we could utilize OAuth 2.0 authentication instead of a service account which customer has some security concerns.
Once we did some prototype, we discovered that the Web API authentication is a little different from typical Graph API authentication. It only supports Delegated Permission. Thus a user credential must be presented for acquiring the access token.
Here is the Azure AD Graph API permission for CRM Web API:
Here is the code in acquiring the access token for the sample code at Web API Global Discovery Service Sample (C#)
string GlobalDiscoUrl = "https://globaldisco.crm.dynamics.com/";
AuthenticationContext authContext = new AuthenticationContext("https://login.microsoftonline.com", false);
UserCredential cred = new UserCredential(username, password);
AuthenticationResult authResult = authContext.AcquireToken(GlobalDiscoUrl, clientId, cred);
Here is another similar post Connect to Dynamics 365 Customer Engagement web services using OAuth although it is more than one year old.
Do you know when MS would support Application permission to completely eliminate the user from authentication? Or there is any particular reason to keep the user here. Thanks for any insights.
[Update 1]
With below answer from James, I did the modification for the code, here is my code
string clientId = "3f4b24d8-61b4-47df-8efc-1232a72c8817";
string secret = "xxxxx";
ClientCredential cred = new ClientCredential(clientId, secret);
string GlobalDiscoUrl = "https://globaldisco.crm.dynamics.com/";
AuthenticationContext authContext = new AuthenticationContext("https://login.microsoftonline.com/common", false);
AuthenticationResult authResult = authContext.AcquireToken(GlobalDiscoUrl, cred);
HttpClient client = new HttpClient();
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", authResult.AccessToken);
client.Timeout = new TimeSpan(0, 2, 0);
client.BaseAddress = new Uri(GlobalDiscoUrl);
HttpResponseMessage response = client.GetAsync("api/discovery/v1.0/Instances", HttpCompletionOption.ResponseHeadersRead).Result;
if (response.IsSuccessStatusCode)
{
//Get the response content and parse it.
string result = response.Content.ReadAsStringAsync().Result;
JObject body = JObject.Parse(result);
JArray values = (JArray)body.GetValue("value");
if (!values.HasValues)
{
return new List<Instance>();
}
return JsonConvert.DeserializeObject<List<Instance>>(values.ToString());
}
else
{
throw new Exception(response.ReasonPhrase);
}
}
so I am able to acquire the access token, but it still could not access the global discovery services.
Here is what the access token looks like:
{
"aud": "https://globaldisco.crm.dynamics.com/",
"iss": "https://sts.windows.net/f8cdef31-a31e-4b4a-93e4-5f571e91255a/",
"iat": 1565802457,
"nbf": 1565802457,
"exp": 1565806357,
"aio": "42FgYEj59uDNtwvxTLnprU0NYt49AA==",
"appid": "3f4b24d8-61b4-47df-8efc-1232a72c8817",
"appidacr": "1",
"idp": "https://sts.windows.net/f8cdef31-a31e-4b4a-93e4-5f571e91255a/",
"tid": "f8cdef31-a31e-4b4a-93e4-5f571e91255a",
"uti": "w8uwKBSPM0y7tdsfXtAgAA",
"ver": "1.0"
}
By the way, we did already create the application user inside CRM by following the instruction.
Anything I am missing here?
[Update 2]
For WhoAmI request, there are different results. If I am using latest MSAL and with authority "https://login.microsoftonline.com/AzureADDirectoryID/oauth2/authorize", I would be able to get the correct result. If I am using MSAL with "https://login.microsoftonline.com/common/oauth2/authorize", it won't work, I would get unauthorized error. If I am using ADAL 2.29, it is not working for both authority. Here is the working code:
IConfidentialClientApplication app = ConfidentialClientApplicationBuilder.Create("3f4b24d8-61b4-47df-8efc-1232a72cxxxx")
.WithClientSecret("xxxxxx")
// .WithAuthority("https://login.microsoftonline.com/common/oauth2/authorize", false)
.WithAuthority("https://login.microsoftonline.com/3a984a19-7f55-4ea3-a422-2d8771067f87/oauth2/authorize", false)
.Build();
var authResult = app.AcquireTokenForClient(new String[] { "https://crmxxxxx.crm5.dynamics.com/.default" }).ExecuteAsync().Result;
HttpClient client = new HttpClient();
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", authResult.AccessToken);
client.Timeout = new TimeSpan(0, 2, 0);
client.BaseAddress = new Uri("https://crm525842.api.crm5.dynamics.com/");
HttpResponseMessage response = client.GetAsync("api/data/v9.1/WhoAmI()", HttpCompletionOption.ResponseHeadersRead).Result;
if (response.IsSuccessStatusCode)
{
//Get the response content.
string result = response.Content.ReadAsStringAsync().Result;
Console.WriteLine(result);
}
else
{
throw new Exception(response.ReasonPhrase);
}
The documentation isn't the easiest to follow, but from what I understand you should start with Use OAuth with Common Data Service.
You then have two subtle options when registering your app. The second does not require the Access Dynamics 365/Common Data Service as organization users permission
Giving access to Common Data Service
If your app will be a client which allows the authenticated user to
perform operations, you must configure the application to have the
Access Dynamics 365 as organization users delegated permission.
Or
If your app will use Server-to-Server (S2S) authentication, this step
is not required. That configuration requires a specific system user
and the operations will be performed by that user account rather than
any user that must be authenticated.
This is elaborated further.
Connect as an app
Some apps you will create are not intended to be run interactively by
a user. ... In these cases you can create a special application user
which is bound to an Azure Active Directory registered application and
use either a key secret configured for the app or upload a X.509
certificate. Another benefit of this approach is that it doesn't
consume a paid license.
Register your app
When registering an app you follow many of the same steps ... with the
following exceptions:
You do not need to grant the Access Dynamics 365 as organization users permission.
You will still have a system user record in Dynamics to represent the application registration. This supports a range of basic Dynamics behaviours and allows you to apply Dynamics security to you app.
As opposed to a username and password you can then use the secret to connect.
string serviceUrl = "https://yourorg.crm.dynamics.com";
string clientId = "<your app id>";
string secret = "<your app secret>";
AuthenticationContext authContext = new AuthenticationContext("https://login.microsoftonline.com/common", false);
ClientCredential credential = new ClientCredential(clientId, secret);
AuthenticationResult result = authContext.AcquireToken(serviceUrl, credential);
string accessToken = result.AccessToken;
Or a certificate.
string CertThumbPrintId = "DC6C689022C905EA5F812B51F1574ED10F256FF6";
string AppID = "545ce4df-95a6-4115-ac2f-e8e5546e79af";
string InstanceUri = "https://yourorg.crm.dynamics.com";
string ConnectionStr = $#"AuthType=Certificate;
SkipDiscovery=true;url={InstanceUri};
thumbprint={CertThumbPrintId};
ClientId={AppID};
RequireNewInstance=true";
using (CrmServiceClient svc = new CrmServiceClient(ConnectionStr))
{
if (svc.IsReady)
{
...
}
}
You may also want to check out Build web applications using Server-to-Server (S2S) authentication which appears to be a similar (but different).
Use server-to-server (S2S) authentication to securely and seamlessly
communicate with Common Data Service with your web applications and
services. S2S authentication is the common way that apps registered on
Microsoft AppSource use to access the Common Data Service data of
their subscribers. ... Rather than user credentials, the application is authenticated based on a service principal identified by an Azure AD Object ID value which is stored in the application user record.
Aside; if you are currently using the Organization Service .NET object, that is being migrated to using the Web API internally.
Microsoft Dynamics CRM 2011 endpoint
The Dynamics 365 SDK assemblies will be updated to use the Web API.
This update will be fully transparent to you and any code written
using the SDK itself will be supported.
I have tried to follow the simple example listed here: https://developers.google.com/admin-sdk/directory/v1/quickstart/dotnet
The difference is I generated a Service Account Credential, and assigned it as a Delegate with the Role Project Owner, so it has full access. I also assigned it the proper namespaces for scopes.
Here it has access to orgunits which is what I'm trying to list in the Directory API
Here is my service account defined
Here are my credentials
I downloaded the JSON for the credential and added it to my project. I can confirm that the code loades the ServiceAccountCredential and successfully authenticates and gets an access token by inspecting the debugger.
But then I pass the credential to the Service Initializer, and when I create and execute a request it fails with
{"Google.Apis.Requests.RequestError\r\nLogin Required [401]\r\nErrors [\r\n\tMessage[Login Required] Location[Authorization - header] Reason[required] Domain[global]\r\n]\r\n"}
Here's the code:
using Google.Apis.Auth.OAuth2;
using Google.Apis.Services;
using System;
using System.Collections.Generic;
using System.IO;
namespace DirectoryQuickstart
{
class Program
{
static string[] Scopes = { DirectoryService.Scope.AdminDirectoryUser, DirectoryService.Scope.AdminDirectoryOrgunit };
static string ApplicationName = "slea-crm";
static string Secret = "gsuite-secret.json";
static void Main(string[] args)
{
ServiceAccountCredential sac = GoogleCredential.FromFile(Secret).CreateScoped(Scopes).UnderlyingCredential as ServiceAccountCredential;
var token = sac.GetAccessTokenForRequestAsync().Result;
// Create Directory API service.
var service = new DirectoryService(new BaseClientService.Initializer()
{
HttpClientInitializer = sac,
ApplicationName = ApplicationName,
});
OrgunitsResource.ListRequest request = service.Orgunits.List(customerId: "REDACTED");
IList<OrgUnit> orgUnits = request.Execute().OrganizationUnits;
if (orgUnits != null && orgUnits.Count > 0)
{
foreach (var orgUnit in orgUnits)
{
Console.WriteLine("{0} ({1})", orgUnit.Name, orgUnit.OrgUnitPath);
}
}
else
{
Console.WriteLine("No orgunits found.");
}
Console.Read();
}
}
}
Here is the content of my JSON secret (with redactions)
What am I missing here?
EDIT: OK, I breakpoint the code while it generates the request, and I can see that no where does it set the Authorization token bearer in the headers. Why? I would expect this HttpClientInitializer class to take care of that, since the API docs say it knows how to handle that, and every example on the internet I've found shows it just passing the credential into the service initializer. But when I walked through it, even though the credential has already been granted an access token and one exists within it, nowhere does the request have the header updated.
The only thing I can see is there is some way to add an HTTP request interceptor where possibly I could do this myself, but wow, this seems really...bizarre -- after all this work they did on the dotnet client SDK, I honestly could have just written direct to the HTTP API and it would have been a lot simpler and easier to follow.
The missing piece of the puzzle is this line:
ServiceAccountCredential sac = GoogleCredential.FromFile(Secret)
.CreateScoped(Scopes)
.UnderlyingCredential as ServiceAccountCredential;
Needs to be modified to this:
static string userName = "admin#yourdomain.com" // valid user in your org
ServiceAccountCredential sac = GoogleCredential.FromFile(Secret)
.CreateScoped(Scopes)
.CreateWithUser(userName)
.UnderlyingCredential as ServiceAccountCredential;
Java/Python/Go sample of doing similar is here: https://developers.google.com/admin-sdk/directory/v1/guides/delegation#create_the_service_account_and_its_credentials
This has been answered but adding more details here. If anyone wants to impersonate user to upload file on google drive using Service account. Follow these steps
Create Service Account
Enable Site Wide delegation for service account
Get Service account client ID
Enable Client ID to use Google Drive API using Google Admin Console->Manage API
Use the below C# code to upload file
public static DriveService GetService()
{
string[] scopes = new string[] { DriveService.Scope.Drive };
//"SERVICE_ACCOUNT_EMAIL_HERE";
String serviceAccountEmail = "test-417#elated-graph-261115.iam.gserviceaccount.com";
// Scope and user email id which you want to impersonate
var initializer = new ServiceAccountCredential.Initializer(serviceAccountEmail)
{
Scopes = scopes,
User = "yourEmail#domain.com"
};
//get private key, from .JSON file
var credential = new ServiceAccountCredential(initializer.FromPrivateKey("-----BEGIN PRIVATE KEY-----\nMIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCkHeAicu6uFQn0\n7KUVTjgZ68nQui8+c8NmKW8aW8vhkBIKfdewXFECiUlTMPyI+HXbubsCK5Dl2xBS\nnphLq6YyE0xEQxNFLYHwfUKuzGQ2rV+qObcZ0mLZjCaf+pw3YiRVuU6OtslLJKJH\n-----END PRIVATE KEY-----\n"));
// Create the service.
var service = new DriveService(new BaseClientService.Initializer()
{
HttpClientInitializer = credential,
ApplicationName = "DriveAPI",
});
service.HttpClient.Timeout = TimeSpan.FromMinutes(100);
return service;
}
That's it, we are done above Code is using Impersonation/Delegation for uploading file on Google Drive using Service account
Reference : Upload file to Google Drive using Service Account in C# MVC (With Impersonation)
I'm trying to build a feature where a client application retrieves the graph resources via WebAPI layer. The scenario has following applications:
Angular5 Client application
ASP.Net Core Web API
The Angular5 client application uses MSAL to authenticate against application (resisted as Converged application via apps.dev.microsoft.com registration application; AAD v2 endpoint).
The authentication flow defines the Web API as scope while login or getting access token
constructor() {
var logger = new Msal.Logger((logLevel, message, piiEnabled) =>
{
console.log(message);
},
{ level: Msal.LogLevel.Verbose, correlationId: '12345' });
this.app = new Msal.UserAgentApplication(
CONFIGSETTINGS.clientId,
null,
this.authCallback,
{
redirectUri: window.location.origin,
cacheLocation: 'localStorage',
logger: logger
}
);
}
public getAPIAccessToken() {
return this.app.acquireTokenSilent(CONFIGSETTINGS.scopes).then(
accessToken => {
return accessToken;
},
error => {
return this.app.acquireTokenSilent(CONFIGSETTINGS.scopes).then(
accessToken => {
return accessToken;
},
err => {
console.error(err);
}
);
}
);
}
Here scope is defined as scopes: ['api://<<guid of application>>/readAccess']. This is the exact value which was generated when I've registered the Web API in registeration portal. Also, the client application id is added as Pre-authorized applications .
The Web API layer (built in dotnet core -- and uses JwtBearer to validate the authentication), defines the API which internally fetches the graph resources (using HttpClient). To get the access token, I've used following code
public async Task<string> GetAccesToken(string resourceName)
{
var userAssertion = this.GetUserAssertion();
string upn = GetLoggedInUpn();
var userTokenCache = new SessionTokenCache(upn, new Microsoft.Extensions.Caching.Memory.MemoryCache(new MemoryCacheOptions())).GetCacheInstance();
string msGraphScope = "https://graph.microsoft.com/User.Read";
string authority = string.Format("https://login.microsoftonline.com/{0}/v2.0", this.authConfig.TenantId);
ConfidentialClientApplication clientApplication = new ConfidentialClientApplication(this.authConfig.ClientId, authority, new ClientCredential(this.authConfig.AppKey), userTokenCache, null);
var result = await clientApplication.AcquireTokenOnBehalfOfAsync(new string[] { msGraphScope }, userAssertion);
return result != null ? result.AccessToken : null;
}
private UserAssertion GetUserAssertion()
{
string token = this.httpContextAccessor.HttpContext.Request.Headers["Authorization"];
string upn = GetLoggedInUpn();
if (token.StartsWith("Bearer", true, CultureInfo.InvariantCulture))
{
token = token.Trim().Substring("Bearer".Length).Trim();
return new UserAssertion(token, "urn:ietf:params:oauth:grant-type:jwt-bearer");
}
else
{
throw new Exception($"ApiAuthService.GetUserAssertion() failed: Invalid Authorization token");
}
}
Note here, The method AcquireTokenOnBehalfOfAsync is used to get the access token using graph scope. However it throws the following exception:
AADSTS65001: The user or administrator has not consented to use the application with ID '<>' named '<>'. Send an interactive authorization request for this user and resource.
I'm not sure why the of-behalf flow for AAD v2 is not working even when client application uses the Web API as scope while fetching access token and Web API registers the client application as the pre-authorized application.
Note - I've tried using the other methods of ConfidentialClientApplication but even those did not work.
Can someone please point out how the above flow can work without providing the admin consent on Web API?
I've been trying to figure this out for weeks! My solution isn't great (it requires the user to go through the consent process again for the Web API), but I'm not sure that's entirely unexpected. After all, either the Admin has to give consent for the Web API to access the graph for the user, or the user has to give consent.
Anyway, the key was getting consent from the user, which of course the Web API can't do since it has no UI. However, ConfidentialClientApplication will tell you the URL that the user has to visit with GetAuthorizationRequestUrlAsync.
Here's a snippet of the code that I used to get it working (I'm leaving out all the details of propagating the url back to the webapp, but you can check out https://github.com/rlittletht/msal-s2s-ref for a working example.)
async Task<string> GetAuthenticationUrlForConsent(ConfidentialClientApplication cca, string []graphScopes)
{
// if this throws, just let it throw
Uri uri = await cca.GetAuthorizationRequestUrlAsync(graphScopes, "", null);
return uri.AbsoluteUri;
}
async Task<string> GetAccessTokenForGraph()
{
// (be sure to use the redirectUri here that matches the Web platform
// that you added to your WebApi
ConfidentialClientApplication cca =
new ConfidentialClientApplication(Startup.clientId,
"http://localhost/webapisvc/auth.aspx",
new ClientCredential(Startup.appKey), null, null);
string[] graphScopes = {"https://graph.microsoft.com/.default"};
UserAssertion userAssertion = GetUserAssertion();
AuthenticationResult authResult = null;
try
{
authResult = await cca.AcquireTokenOnBehalfOfAsync(graphScopes, userAssertion);
}
catch (Exception exc)
{
if (exc is Microsoft.Identity.Client.MsalUiRequiredException
|| exc.InnerException is Microsoft.Identity.Client.MsalUiRequiredException)
{
// We failed because we don't have consent from the user -- even
// though they consented for the WebApp application to access
// the graph, they also need to consent to this WebApi to grant permission
string sUrl = await GetAuthenticationUrlForConsent(cca, graphScopes);
// you will need to implement this exception and handle it in the callers
throw new WebApiExceptionNeedConsent(sUrl, "WebApi does not have consent from the user to access the graph on behalf of the user", exc);
}
// otherwise, just rethrow
throw;
}
return authResult.AccessToken;
}
One of the things that I don't like about my solution is that it requires that I add a "Web" platform to my WebApi for the sole purpose of being able to give it a redirectUri when I create the ConfidentialClientApplication. I wish there was some way to just launch the consent workflow, get the user consent, and then just terminate the flow (since I don't need a token to be returned to me -- all I want is consent to be granted).
But, I'm willing to live with the extra clunky step since it actually gets consent granted and now the API can call the graph on behalf of the user.
If someone has a better, cleaner, solution, PLEASE let us know! This was incredibly frustrating to research.
I'm getting below error when I try to accessing YouTube Reporting Services API using Service Account.
Google.GoogleApiException HResult=0x80131500
Message=Google.Apis.Requests.RequestError The caller does not have
permission [403] Errors [ Message[The caller does not have
permission] Location[ - ] Reason[forbidden] Domain[global] ]
When I searched online, somewhere I saw we need to have GSuite subscription in order to use service account with API, is it correct?
Here is my code snippet:
String serviceAccountEmail = "xxxxxxxxxxxxx#youtubereportingapi.iam.gserviceaccount.com";
var certificate = new X509Certificate2(#"xxxxxxxx.p12", "notasecret", X509KeyStorageFlags.Exportable);
ServiceAccountCredential credential = new ServiceAccountCredential(
new ServiceAccountCredential.Initializer(serviceAccountEmail)
{
Scopes = new[] { YouTubeReportingService.Scope.YtAnalyticsReadonly, YouTubeReportingService.Scope.YtAnalyticsMonetaryReadonly }
}.FromCertificate(certificate));
var youtubeReportingService = new YouTubeReportingService(new BaseClientService.Initializer()
{
HttpClientInitializer = credential,
ApplicationName = Assembly.GetExecutingAssembly().GetName().Name
});
var jobsListResponse = youtubeReportingService.Jobs.List().Execute(); // Here its throwing error
In order to use a service account you must share data with it. I have never heard of using GSuite with a YouTube account you can have the admin try and set up domain wide delegation to the service account. However to my knowledge there is no way to share YouTube data with a service account.
Service accounts are not supported by the YouTube APIs. You will need to use Oauth2 and save your refresh token for access later. The docs only mention OAuth2.
I have developed a Azure API protected with AAD , which is working fine,
now I would like to call SharePoint online REST API from my Azure API ,my SharePoint online is using same active directory as my azure API.
For calling SP API i need access token to authenticate SharePoint
I assume the access token which authenticated the Azure API would be same to call SharePoint API
this is what I did :
My Azure API is registered in azure active directory (which is automatically done which I made authentication on )
I updated the Azure app's manifest.json to enable oauth2 implicit flow:
"oauth2AllowImplicitFlow": true
I granted the app access to "Read and write items and lists in all site collections" on behalf of the user (under delegated permissions) from the Azure AD app settings page ("permissions to other applications").
I tried to this code to get access token :
string clientId = "xxxxxxxxxx";
string appKey = xxxxxxxxxxx";
string aadInstance = "https://login.microsoftonline.com";
string tenant = "mydomain.onmicrosoft.com";
string domain = "mydomain.onmicrosoft.com";
string resource = "https://mydomain.sharepoint.com";
AuthenticationResult result = null;
ClientCredential clientCred = new ClientCredential(clientId, appKey);
string authHeader = HttpContext.Current.Request.Headers["Authorization"];
string userAccessToken = authHeader.Substring(authHeader.LastIndexOf(' ')).Trim();
UserAssertion userAssertion = new UserAssertion(userAccessToken);
string authority = aadInstance + domain;
AuthenticationContext authContext = new AuthenticationContext(authority);
//result = await authContext.AcquireTokenAsync(resource, clientCred); // auth without user assertion (fails, app only not allowed)
result = await authContext.AcquireTokenAsync(resource, clientCred, userAssertion); // clientCred and userAssertion params have swapped places since Kirk's blog
return result.AccessToken;
but authHeader is null ,
I came across this question which mentions in order to retrieve a user token uses ADAL.js using authenticationContext.acquireToken(clientId), then include the resulting token in the header of the AJAX request to the WebAPI
I am not sure how i need to include this in my azure API
anyway i appreciate any thoughts or idea to get access token to run SharePoint online APIs behalf of logged in user in azure API