How to GET application by appId using Microsoft Graph API via Java using HttpURLConnection - azure

I want to authorize an OAuth JSON Web Token granted by Azure Active Directory, and one thing I need to do is get more information about the application in the token's appId using the Microsoft Graph API.
Microsoft Graph API allows me to GET an app by its id via
https://graph.microsoft.com/beta/applications/{id}
, but NOT by its appId via
https://graph.microsoft.com/beta/applications/{appId}
The best way I see to use Microsoft Graph API to GET an app using its AppId is via a filter like so:
https://graph.microsoft.com/beta/applications?filter=appId eq '{appId}'
The above filter works just fine in the Microsoft Graph Explorer, but when calling the Graph API using a GET request using HttpUrlConnection, my request fails with HTTP Code 400 and message "Bad Request".
It's weird because using the exact same HttpUrlConnection to GET the full range of applications via
https://graph.microsoft.com/beta/applications
works just fine.
Is there something about the filter functionality that I can't use it in a Microsoft Graph API GET request? How should I get info on an app by its AppId?
Here is a snippet of the Java code I am using for my HttpURLConnection:
url = new URL(String.format("https://graph.microsoft.com/beta/applications?filter=appId eq '%s'", appId));
final HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");
conn.setRequestProperty("Authorization", "Bearer " + result.getAccessToken());
conn.setRequestProperty("Accept", "application/json");
conn.setRequestProperty("Content-Type", "application/json");
final int httpResponseCode = conn.getResponseCode();
if (httpResponseCode == 200 || httpResponseCode == 201) {
BufferedReader in = null;
final StringBuilder response;
try {
in = new BufferedReader(
new InputStreamReader(conn.getInputStream()));
String inputLine;
response = new StringBuilder();
while ((inputLine = in.readLine()) != null) {
response.append(inputLine);
}
} finally {
in.close();
}
final JSONObject json = new JSONObject(response.toString());
return json.toString(4);
} else {
return String.format("Connection returned HTTP code: %s with message: %s",
httpResponseCode, conn.getResponseMessage());
}

In case someone else comes looking for this, if you are using the GraphServiceClient you can do this:
var appId = "some app id";
var response = await _graphClient.Applications
.Request()
.Filter($"appId eq '{appId}'")
.GetAsync();
var azureAddApplication = response.FirstOrDefault() ?? throw new ArgumentException($"Couldn't find App registration with app id {appId}");

In case someone is searching this... the API call should be done with the app's Object ID, not the app ID.

You should URLEncode the query parameters.
String url2=URLEncoder.encode("$filter=appId eq '{applicationId}'");
URL url = new URL("https://graph.microsoft.com/beta/applications?"+url2);

Related

Postman not connecting to Dynamics365 API using Oauth client credentials, console app working using same details

I am trying to connect with Postman to Dynamics365 CRM REST API.
Although I obtain a token successfully without login using Grant Type client credentials I then get a 401 error when doing a sample GET to the API.
My console application is working successfully however and is not prompting the user for login (I don't want a login prompt to appear).
I have:
1. Registered the app in Azure AD,
2. Created the client secret
3. Created the application user in Dynamics and linked via the Application ID to App from step 1
I've done this with two different apps and two different application users and get the same result in Postman i.e. Token retrieved but 401 error on GET.
The console app below is working with the same credentials, would appreciate any input on what is missing in the Postman config
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.IdentityModel.Clients.ActiveDirectory;
using System.Net.Http.Headers;
using System.Net.Http;
using Newtonsoft.Json.Linq;
namespace ConsoleApp8
{
class Program
{
static void Main(string[] args)
{
const string ResourceId = "https://myurldev.crm4.dynamics.com/api/data/v9.1";
Task<AuthenticationResult> t = GetUserOAuthToken();
t.Wait();
string accessToken = t.Result.AccessToken;
Console.WriteLine("ACCESS TOKEN \n\n" + accessToken);
Console.WriteLine("\n\n Please any key to display content of the blob");
//Console.ReadKey();
using (HttpClient httpClient = new HttpClient())
{
httpClient.BaseAddress = new Uri(ResourceId);
httpClient.Timeout = new TimeSpan(0, 2, 0); // 2 minutes
httpClient.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("Bearer", accessToken);
//Send the Get Incident request to the Web API using a GET request.
var response = httpClient.GetAsync("/incidents",
HttpCompletionOption.ResponseHeadersRead).Result;
if (response.IsSuccessStatusCode)
{
//Get the response content and parse it.
JObject body = JObject.Parse(response.Content.ReadAsStringAsync().Result);
string title = (string)body["title"];
Console.WriteLine("Incident title is : {0}", title);
}
else
{
Console.WriteLine("The request failed with a status of '{0}'",
response.ReasonPhrase);
}
}
/*
// Use the access token to create the storage credentials.
TokenCredential tokenCredential = new TokenCredential(accessToken);
StorageCredentials storageCredentials = new StorageCredentials(tokenCredential);
// Create a block blob using those credentials
CloudBlockBlob blob = new CloudBlockBlob(new Uri("https://placeholderURL/SalesOrder.json"), storageCredentials);
using (var stream = blob.OpenRead())
{
using (StreamReader reader = new StreamReader(stream))
{
while (!reader.EndOfStream)
{
Console.WriteLine(reader.ReadLine());
}
}
}
Console.WriteLine("\n\n Please any key to terminate the program");
Console.ReadKey();*/
}
static async Task<AuthenticationResult> GetUserOAuthToken()
{
const string ResourceId = "https://myurldev.crm4.dynamics.com/";
const string AuthInstance = "https://login.microsoftonline.com/{0}/";
const string TenantId = "XXXX-DYNAMICS_TENANT_ID-XXXXX"; // Tenant or directory ID
// Construct the authority string from the Azure AD OAuth endpoint and the tenant ID.
string authority = string.Format(System.Globalization.CultureInfo.InvariantCulture, AuthInstance, TenantId);
AuthenticationContext authContext = new AuthenticationContext(authority);
ClientCredential cc = new ClientCredential("XXXX_APPLICATION_ID_XXXX", "XXXXX_CLIENT_SECRET_XXXX");
// Acquire an access token from Azure AD.
AuthenticationResult result = await authContext.AcquireTokenAsync(ResourceId, cc);
return result;
}
}
}
POSTMAN TOKEN VARIABLES
POSTMAN REQUEST AND RESPONSE
I had already added the following permissions to my APP
This is the response when I analyse the token in JWT.io
It seems that Dynamics CRM only support delegated permission which allow the application to access Common Data Service acting as users in the organization. This means client credentials is not appropriate here.
However, you said that you can use client credentials in console app. You can try with below request to get the access token.

Can not get token for TextToSpeechAPI

Here is the code:
private AccessTokenInfo GetToken()
{
WebRequest webRequest = WebRequest.Create("https://oxford-speech.cloudapp.net/token/issueToken");
webRequest.ContentType = "application/x-www-form-urlencoded";
webRequest.Method = "POST";
byte[] bytes = Encoding.ASCII.GetBytes(_requestDetails);
webRequest.ContentLength = bytes.Length;
try
{
using (Stream outputStream = webRequest.GetRequestStream())
{
outputStream.Write(bytes, 0, bytes.Length);
}
// ...
I have got the exception:
the underlying connection was closed could not establish trust relationship
How can I fit it ?
I hope I'm not missing something here...
The URL you're using isn't the one that generates tokens for the Text-to-Speech API as documented here. (The "Oxford" that's referenced in your URL refers to the Project Oxford which Cognitive Services was formerly known as.)
Also, WebRequest is deprecated. Use the System.Net.Http package instead.
The code to invoke the new REST endpoint then would look something like:
using (var client = new HttpClient())
using (var request = new HttpRequestMessage(HttpMethod.Post, "https://api.cognitive.microsoft.com/sts/v1.0/issueToken"))
{
request.Headers.Add("Ocp-Apim-Subscription-Key", "YOUR-KEY-HERE");
var response = await client.SendAsync(req);
var token = await response.Content.ReadAsStringAsync();
}
Finally, there are several client libraries that may get you around from writing any code to hit the REST services at all.

C# CSOM Sharepoint Bearer request from azure active directory

I am using the following approach as the basis of this (https://learn.microsoft.com/en-us/azure/active-directory/develop/active-directory-devquickstarts-webapi-dotnet).
I got all this example working after setting up azure. But now we need to port it to an actual existing mobile app and web api app. The mobile app can get the Bearer token, but when we pass it to the web api, we pass this in a CSOM request as follows, but we still get a 401 Unauthroised response.
public static ClientContext GetSharepointBearerClientContext(this JwtTokenDetails tokenDetails)
{
var context = new ClientContext(tokenDetails.SiteUrl);
//context.AuthenticationMode = ClientAuthenticationMode.Anonymous;
context.ExecutingWebRequest += new EventHandler<WebRequestEventArgs>((s, e) =>
{
e.WebRequestExecutor.RequestHeaders["Authorization"] = "Bearer " + tokenDetails.BearerToken;
});
return context;
}
Our web api doesn't use any of the tech as in the example above, as I presume that we should just be able to pass the token through the CSOM request in the header, but this is not working, what else could I look at?
I have assigned the Office 365 Sharepoint Online (Microsoft.Sharepoint) permission and set the following
I have also done the same for the app registration, which we don't really use! Still not sure how the app registration comes into it)...
So this was possible, it was just microsoft telling us to put in an incorrect value. All the documentation says put the APP ID URI in the Resource. But in our case it needed to be the sharepoint url.
So we have the tenant name which on azure id the domain name e.g. srmukdev.onmicrosoft.com
Tenant: srmukdev.onmicrosoft.com
Application Id: This is the guid for the app registered in azure active directory.
RedirectUri: This can be any url(URI), its not actually used as a url for a mobile app as far as I can see.
ResourceUrl: srmukdev.sharepoint.com
The code I am using to get a token is as follows for a WPF example. The aadInstance is https://login.microsoftonline.com/{0}
private static string authority = String.Format(CultureInfo.InvariantCulture, aadInstance, tenant);
public async void CheckForCachedToken(PromptBehavior propmptBehavior)
{
//
// As the application starts, try to get an access token without prompting the user. If one exists, populate the To Do list. If not, continue.
//
AuthenticationResult result = null;
try
{
result = await authContext.AcquireTokenAsync(resourceUrl, applicationId, redirectUri, new PlatformParameters(propmptBehavior));
TokenTextBox.Text = result.AccessToken;
// A valid token is in the cache - get the To Do list.
GetTokenButton.Content = "Clear Cache";
}
catch (AdalException ex)
{
if (ex.ErrorCode == "user_interaction_required")
{
// There are no tokens in the cache. Proceed without calling the To Do list service.
}
else
{
// An unexpected error occurred.
string message = ex.Message;
if (ex.InnerException != null)
{
message += "Inner Exception : " + ex.InnerException.Message;
}
MessageBox.Show(message);
}
return;
}
}

Access token validation failure when creating Microsoft Graph webhook using the "Web API on-behalf-of flow"

What I am trying to do is to use the "Web API on-behalf-of flow" scenario Microsoft described in this article to create a web hook.
So I started with the Microsoft github example and made sure that I can successfully get the users profile via the Graph API.
Then I modified the code where it gets the users profile to create the web hook, so the code looks like this:
// Authentication and get the access token on behalf of a WPF desktop app.
// This part is unmodified from the sample project except for readability.
const string authority = "https://login.microsoftonline.com/mycompany.com";
const string resource = "https://graph.windows.net";
const string clientId = "my_client_id";
const string clientSecret = "my_client_secret";
const string assertionType = "urn:ietf:params:oauth:grant-type:jwt-bearer";
var user = ClaimsPrincipal.Current.FindFirst(ClaimTypes.NameIdentifier).Value;
var authenticationContext = new AuthenticationContext(authority,new DbTokenCache(user));
var assertion = ((BootstrapContext) ClaimsPrincipal.Current.Identities.First().BootstrapContext).Token;
var userName = ClaimsPrincipal.Current.FindFirst(ClaimTypes.Upn) != null
? ClaimsPrincipal.Current.FindFirst(ClaimTypes.Upn).Value
: ClaimsPrincipal.Current.FindFirst(ClaimTypes.Email).Value;
var result = await authenticationContext.AcquireTokenAsync(resource,new ClientCredential(clientId,clientSecret),new UserAssertion(assertion,assertionType,userName));
var accessToken = result.AccessToken;
// After getting the access token on behalf of the desktop WPF app,
// subscribes to get notifications when the user receives an email.
// This is the part that I put in.
var subscription = new Subscription
{
Resource = "me/mailFolders('Inbox')/messages",
ChangeType = "created",
NotificationUrl = "https://mycompany.com/subscription/listen",
ClientState = Guid.NewGuid().ToString(),
ExpirationDateTime = DateTime.UtcNow + new TimeSpan(0, 0, 4230, 0)
};
const string subscriptionsEndpoint = "https://graph.microsoft.com/v1.0/subscriptions/";
var request = new HttpRequestMessage(HttpMethod.Post, subscriptionsEndpoint);
var contentString = JsonConvert.SerializeObject(subscription, new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore });
request.Content = new StringContent(contentString, System.Text.Encoding.UTF8, "application/json");
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
var response = await new HttpClient().SendAsync(request);
if (response.IsSuccessStatusCode)
{
// Parse the JSON response.
var stringResult = await response.Content.ReadAsStringAsync();
subscription = JsonConvert.DeserializeObject<Subscription>(stringResult);
}
The error I get from the response is:
{
"error":
{
"code": "InvalidAuthenticationToken",
"message": "Access token validation failure.",
"innerError":
{
"request-id": "f64537e7-6663-49e1-8256-6e054b5a3fc2",
"date": "2017-03-27T02:36:04"
}
}
}
The webhook creation code was taken straight from the ASP.NET webhook github sample project, which, I have also made sure that I can run successfully.
The same access token code works with the original user profile reading code:
// Call the Graph API and retrieve the user's profile.
const string requestUrl = "https://graph.windows.net/mycompany.com/me?api-version=2013-11-08";
request = new HttpRequestMessage(HttpMethod.Get, requestUrl);
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
var response = await new HttpClient().SendAsync(request);
So I want to find out:
Is creating a webhook via the graph API using the on-behalf-of flow even supported? Not sure if this SO question is what I'm looking for here.
If it is supported, what am I missing here?
If it is not supported, is there an alternative to achieve it? E.g. is there anything from the existing Office 365 API that I can use?
"message": "Access token validation failure.",
The error means you got incorrect access token for the resource . According to your code ,you get the access token for resource :https://graph.windows.net( Azure AD Graph API) , But then you used that access token to access Microsoft Graph API(https://graph.microsoft.com) ,so access token validation failed .

Create custom extension through Graph API with Client Credentials auth

I have a .NET Web API that I am using to do some interaction with Microsoft Graph and Azure AD. However, when I attempt to create an extension on the user, it comes back with Access Denied.
I know it is possible from the documentation here however, it doesnt seem to work for me.
For the API, I am using client credentials. So my web app authenticates to the API using user credentials, and then from the API to the graph it uses the client.
My app on Azure AD has the Application Permission Read and Write Directory Data set to true as it states it needs to be in the documentation for a user extension.
I know my token is valid as I can retrieve data with it.
Here is my code for retrieving it:
private const string _createApprovalUrl = "https://graph.microsoft.com/beta/users/{0}/extensions";
public static async Task<bool> CreateApprovalSystemSchema(string userId)
{
using(var client = new HttpClient())
{
using(var req = new HttpRequestMessage(HttpMethod.Post, _createApprovalUrl))
{
var token = await GetToken();
req.Headers.Add("Authorization", string.Format("Bearer {0}", token));
req.Headers.TryAddWithoutValidation("Content-Type", "application/json");
var requestContent = JsonConvert.SerializeObject(new { extensionName = "<name>", id = "<id>", approvalLimit = "0" });
req.Content = new StringContent(requestContent, Encoding.UTF8, "application/json");
using(var response = await client.SendAsync(req))
{
var content = await response.Content.ReadAsStringAsync();
ApprovalSystemSchema schema = JsonConvert.DeserializeObject<ApprovalSystemSchema>(content);
if(schema.Id == null)
{
return false;
}
return true;
}
}
}
}
Is there anyone who may have a workaround on this, or information as to when this will be doable?
Thanks,
We took a look and it looks like you have a bug/line of code missing. You appear to be making this exact request:
POST https://graph.microsoft.com/beta/users/{0}/extensions
Looks like you are missing the code to replace the {0} with an actual user id. Please make the fix and let us know if you are now able to create an extension on the user.

Resources