I have two APIs as registered applications on Azure with authentication.
I want one of the APIs to call the other as part of a workflow, without a user.
My understanding of this is to use a client secret, and I successfully get a bearer token with the following call:
_httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/x-www-form-urlencoded"));
_httpClient.DefaultRequestHeaders.Add("Accept", "*/*");
var Parameters = new List<KeyValuePair<string, string>>
{
new KeyValuePair<string, string>("grant_type", "client_credentials"),
new KeyValuePair<string, string>("scope", "api://APPID/.default"),
new KeyValuePair<string, string>("client_secret", APPSECRET),
new KeyValuePair<string, string>("client_id", "APPID
};
var Request = new HttpRequestMessage(HttpMethod.Get, "https://login.microsoftonline.com/TENANTID/oauth2/v2.0/token")
{
Content = new FormUrlEncodedContent(Parameters)
};
var result = await _httpClient.SendAsync(Request).Result.Content.ReadAsStringAsync();
But if I make an API call with the returned bearer token I get 403 Forbidden.
The app on Azure has SPA redirect URLs which makes it "public", is this the cause?
Would passing the bearer token from the front-end from the original API call to be used for the API-to-API call be bad practice?
Related
I am doing an HttpPost to the FedEx tracking API endpoint from the backend of a web app that I am hosting in Azure.
The app works fine on my development machine and can post to the endpoint just fine. But after deploying it to my App service it is no longer able to call the FedEx api to get the required oauth token.
AccessTokenInfo responseObj = new();
using (var client = new HttpClient()) {
client.BaseAddress = new Uri(_fedexConfig.BaseUri);
client.DefaultRequestHeaders.Accept.Add(new
MediaTypeWithQualityHeaderValue("application/json"));
var response = new HttpResponseMessage();
var allIputParams = new List<KeyValuePair<string, string>>() {
new KeyValuePair<string, string>("grant_type","client_credentials"),
new KeyValuePair<string, string>("client_id",_fedexConfig.ClientId),
new KeyValuePair<string, string>("client_secret", _fedexConfig.ClientSecret)
};
HttpContent requestParams = new FormUrlEncodedContent(allIputParams);
response = await client.PostAsync(_fedexConfig.TokenUri,requestParams).ConfigureAwait(false);
}
When the call is made the response only returns a 403 error "forbidden".
Is there some additional settings that the AppService needs to have to communicate with an api endpoint outside the Azure environment?
We have a stand alone process which needs to get call record details of completed calls via the Graph API.
We have obtained record IDs via the Azure Dashboard so that we can use them with the following endpoint
GET https://graph.microsoft.com/v1.0/communications/callRecords/{id}
as shown in
https://learn.microsoft.com/en-us/graph/api/callrecords-callrecord-get?view=graph-rest-1.0
The Azure App has been configured for access and has readAll permissions set.
The following code generates a token for access, but when actually trying to read back a call record specified by id, it always returns 404 Not found.
var scopes = new string[] { "https://graph.microsoft.com/.default" };
IConfidentialClientApplication app;
app = ConfidentialClientApplicationBuilder.Create(clientID)
.WithClientSecret(clientSecret)
.WithAuthority(new Uri(authority))
.Build();
var result = await app.AcquireTokenForClient(scopes).ExecuteAsync();
HttpClient httpClient = new HttpClient();
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", result.AccessToken);
string id = "YYYYYYYY-F571-45D9-ABC6-XXXXXXXXXXXX"; // Real ID, but obfuscated for this example
HttpResponseMessage response = await httpClient.GetAsync(new Uri("https://graph.microsoft.com/v1.0/communications/callRecords/" + id));
Can anyone advise what I am missing.
Thanks
I am using Azure AD V2 app for authentication into my app based on Angular. I am using ASP.NET Web APIs for backend.
I am using auth code flow to get the auth code and using which I am fetching the user access token and refresh token as mentioned in the article: https://learn.microsoft.com/en-us/azure/active-directory/develop/v2-oauth2-auth-code-flow
This flow works fine for me with Azure AD accounts. When I login using outlook account it provides me the code but when I try to get access token using code it doesn't work and I get Bad Request error in the response.
Following is the I am using in the Web API:
public async Task<string> GetAccessToken(string code)
{
string token = "", refresh_token = "";
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
var keyValuePairs = new List<KeyValuePair<string, string>>();
keyValuePairs.Add(new KeyValuePair<string, string>("scope", AppsConfiguration.idaScope));
keyValuePairs.Add(new KeyValuePair<string, string>("client_id", AppsConfiguration.idaClient_id));
keyValuePairs.Add(new KeyValuePair<string, string>("client_secret", AppsConfiguration.idaClient_secret));
keyValuePairs.Add(new KeyValuePair<string, string>("grant_type", AppsConfiguration.grant_type_authorization_code));
keyValuePairs.Add(new KeyValuePair<string, string>("code", code));
keyValuePairs.Add(new KeyValuePair<string, string>("redirect_uri", AppsConfiguration.idaRedirect_uri));
var client1 = new HttpClient();
var req = new HttpRequestMessage(HttpMethod.Post, AppsConfiguration.idaTokenEndpoint) { Content = new FormUrlEncodedContent(keyValuePairs) };
using (var res = client1.SendAsync(req).Result)
{
if (res.IsSuccessStatusCode)
{
var jsonresult = JObject.Parse(await res.Content.ReadAsStringAsync());
token = (string)jsonresult["access_token"];
refresh_token = (string)jsonresult["refresh_token"];
}
}
return token;
}
Do I need to do any specific configuration in the Azure AD V2 app?
Thank you!
I was using login URL for v2 endpoint as : https://login.microsoftonline.com/common/oauth2/v2.0/authorize?client_id={client id}....
Though this worked for Active Directory accounts and onmicrosoft accounts, it didn't worked for Microsoft Personal accounts like hotmail/outlook.
For me it worked when I changed the above login URL to :
https://login.microsoftonline.com/{domain name/tenand id}/oauth2/v2.0/authorize?client_id={client id}....
Thank you!
I am working on an ASP.NET MVC5 Web App that uses Azure ADAL libraries to authenticate users, it works fine, however, when I manually send requests to graph, ex: GET https://graph.microsoft.com/v1.0/me or GET https://graph.microsoft.com/v1.0/groups?$filter=from/displayName eq 'whatever'.
I have tried updating the App Registration in Azure as to add the required Graph permissions, and I have also tried creating new app registrations, no matter what I do my requests will always respond 401 Unauthorized, is there anything I am missing?
EDIT: Example response from Postman
{
"error": {
"code": "InvalidAuthenticationToken",
"message": "Access token validation failure.",
"innerError": {
"request-id": "a142576b-acce-4e59-8a8d-adede61aaf59",
"date": "2017-04-05T13:27:36"
}
}
}
EDIT: C# Request Example
public async Task<GroupGraph> GetGroupIdByDisplayName(string displayName)
{
var accessToken = await authenticationService.GetTokenUserOnly();
GroupGraph groupGraphResponse = null;
using (var client = new HttpClient())
{
using (var request = new HttpRequestMessage(HttpMethod.Get, $"https://graph.microsoft.com/v1.0/groups?$filter=from/displayName eq '{displayName}'"))
{
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
using (var response = client.SendAsync(request).Result)
{
if (response.IsSuccessStatusCode)
{
using (var content = response.Content)
{
var result = await content.ReadAsStringAsync();
groupGraphResponse = JsonConvert.DeserializeObject<GroupGraph>(result);
}
}
}
}
}
return groupGraphResponse;
}
EDIT: The way I obtain the token
public async Task<string> GetTokenUserOnly()
{
string signedInUserID = ClaimsPrincipal.Current.FindFirst(ClaimTypes.NameIdentifier).Value;
string tenantID = ClaimsPrincipal.Current.FindFirst("http://schemas.microsoft.com/identity/claims/tenantid").Value;
string userObjectID = ClaimsPrincipal.Current.FindFirst("http://schemas.microsoft.com/identity/claims/objectidentifier").Value;
// get a token for the Graph without triggering any user interaction (from the cache, via multi-resource refresh token, etc)
ClientCredential clientcred = new ClientCredential(clientId, appKey);
// initialize AuthenticationContext with the token cache of the currently signed in user, as kept in the app's database
AuthenticationContext authenticationContext = new AuthenticationContext(aadInstance + tenantID, new TableTokenCache(signedInUserID));
//AuthenticationResult authenticationResult = await authenticationContext.AcquireTokenSilentAsync(graphResourceID, clientcred, new UserIdentifier(userObjectID, UserIdentifierType.UniqueId));
AuthenticationResult authenticationResult = authenticationContext.AcquireToken(graphResourceID, clientcred);
return authenticationResult.AccessToken;
}
You can't use ADAL to get tokens for graph.microsoft.com. ADAL is for graph.windows.net.
In order to get tokens for the Graph library (graph.windows.com) look into the Nuget Package Microsoft.Graph. Microsoft also has some documentation on how to pull user info using Graph.
Be forewarned though, using Graph Libraries and ADAL libraries side by side can lead to some weird side effects, such as the credential cache being cleared.
It seems you are using the client credential grant flow to acquire the access token for graph api(graphResourceID is https://graph.microsoft.com ?) :
AuthenticationResult authenticationResult = authenticationContext.AcquireToken(graphResourceID, clientcred);
So you need to grant app permission in azure ad portal :
For error "Access token validation failure" , you could use online tool like http://jwt.calebb.net/ to decode your access token , check the audience or lifetime of the access token .
To obtain a valid token for Microsoft Graph API you can use Azure.Identity.
To use any implementation of TokenCredential we need to build our own IAuthenticationProvider.
public class TokenCredentialAuthenticationProvider : IAuthenticationProvider
{
private readonly TokenCredential _tokenCredential;
public TokenCredentialAuthenticationProvider(TokenCredential tokenCredential)
{
_tokenCredential = tokenCredential;
}
public async Task AuthenticateRequestAsync(HttpRequestMessage request)
{
var accessToken = await _tokenCredential.GetTokenAsync(new TokenRequestContext(new[] { "https://graph.microsoft.com" }), CancellationToken.None);
request.Headers.Authorization = new AuthenticationHeaderValue("bearer", accessToken.Token);
}
}
Now we can for instance use AzureCliCredential to acquire an access token.
Open Powershell and type in az login in order to login with your Azure AD account.
In Azure you could also use Managed Identity to get a token based on a Azure resource e.g. Azure App Service. Here need to use ManagedIdentityToken.
Usage:
var client = new GraphServiceClient(new TokenCredentialAuthenticationProvider(new AzureCliCredential()));
var user = await client.Me.Request().GetAsync();
I have download a MVC application from Git for AAD graph API. I ran this application but each time i am not getting expected result.
To find the error i run same api using postman and generated token below was the response.
{
"odata.error": {
"code": "Request_ResourceNotFound",
"message": {
"lang": "en",
"value": "Resource not found for the segment 'me'."
}
}
}
I am using below Get URL-
https://graph.windows.net/XXXXX/me?api-version=1.6
Also, to verify is it working with AAD Grapg api explorer. After log in everything is working fine.
Below is my code to call Grapg API-
// Get the access token from the cache
string userObjectID =
ClaimsPrincipal.Current.FindFirst("http://schemas.microsoft.com/identity/claims/objectidentifier")
.Value;
string authority = String.Format(CultureInfo.InvariantCulture, aadInstance, tenant);
//AuthenticationContext authContext = new AuthenticationContext(authority,
// new NaiveSessionCache(userObjectID));
ClientCredential credential = new ClientCredential(clientId, appKey);
AuthenticationContext authContext = new AuthenticationContext(authority, true);
result = await authContext.AcquireTokenAsync(graphResourceId.ToString(), credential);
var Token = result.AccessToken;
//// AcquireTokenSilentAsync
//result = await authContext.AcquireTokenSilentAsync(graphResourceId, credential,
// new UserIdentifier(userObjectID, UserIdentifierType.UniqueId));
// Call the Graph API manually and retrieve the user's profile.
string requestUrl = String.Format(
CultureInfo.InvariantCulture,
graphUserUrl,
HttpUtility.UrlEncode(tenantId));
HttpClient client = new HttpClient();
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, requestUrl);
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", result.AccessToken);
HttpResponseMessage response = await client.SendAsync(request);
// Return the user's profile in the view.
if (response.IsSuccessStatusCode)
{
string responseString = await response.Content.ReadAsStringAsync();
profile = JsonConvert.DeserializeObject<UserProfile>(responseString);
}
Could you guys please tell me what is the problem with my code. Why it is working on AAD explorer not with Localhost.
To request the me endpoint of Azure AD Graph REST, we need to use the delegate token which represents the sign-in user.
The code above you acquire token using the Client Credential flow is request the access token which represents for the application which doesn't contain the info of sign-in user.
To achieve this in the MVC application, we need to acquire the token after you get the authorization code when users login. Next time, we can acquire the token from the token cache based on the login user.
Here is the code for your reference( code sample from here):
result = await authContext.AcquireTokenSilentAsync(graphResourceId, credential,
new UserIdentifier(userObjectID, UserIdentifierType.UniqueId));