How do I Call Microsoft Teams OnlineMeeting endpoints via Microsoft Graph API using a console app? - azure

I have followed the code example given in the following link by Microsoft and was successfully able to get the list of users.
My registered app in the Azure Active Directory also have the "OnlineMeeting.ReadWrite.All" application permission.
But when I am trying to call the create meeting call by posting the request in the endpoint "https://graph.microsoft.com/v1.0/me/onlineMeetings". I am getting a 403 forbidden error. Any idea why I am getting this?

For the graph api create online meetings https://graph.microsoft.com/v1.0/me/onlineMeetings, we can see the tutorial shows it doesn't support "Application permission" to call it. It just support "Delegated permission", so we can just request it by password grant flow but not client credential flow.
Update:
For your requirement to request the graph api of creating online meeting, we can just use password grant flow or auth code flow. Here provide a sample of password grant flow(username and password) for your reference, use this sample to get the token and request the graph api by this token. You can also find this sample in this tutorial.
static async Task GetATokenForGraph()
{
string authority = "https://login.microsoftonline.com/contoso.com";
string[] scopes = new string[] { "user.read" };
IPublicClientApplication app;
app = PublicClientApplicationBuilder.Create(clientId)
.WithAuthority(authority)
.Build();
var accounts = await app.GetAccountsAsync();
AuthenticationResult result = null;
if (accounts.Any())
{
result = await app.AcquireTokenSilent(scopes, accounts.FirstOrDefault())
.ExecuteAsync();
}
else
{
try
{
var securePassword = new SecureString();
foreach (char c in "dummy") // you should fetch the password
securePassword.AppendChar(c); // keystroke by keystroke
result = await app.AcquireTokenByUsernamePassword(scopes,
"joe#contoso.com",
securePassword)
.ExecuteAsync();
}
catch(MsalException)
{
// See details below
}
}
Console.WriteLine(result.Account.Username);
}

Related

Permissions from Graph API seem to be empty

Another Microsoft Graph API question this time I'm curious about the result.
Why does this return a 200 and with nothing in the value object.
What I've tried:
Add different permissions in the Modify permissions tab
Test different accounts and other SharePoint environments ( I am global admin on those accounts and its no personal account but work account)
I've tested before with the query params such as select, filter and expand. So ive tried things like ?expand=all, expand=items and expand=children and a few more.
Use name or id in the sites/{site name or site id}
Usually I've solved all of my problems with repeating step 1 or 3 but now it seem to give me nothing. Since it's part of the docs im curious what I'm missing here
https://learn.microsoft.com/en-us/graph/api/site-list-permissions?view=graph-rest-1.0&tabs=http
What could be the missing piece here? :)
Edit:
I've tried to solve this issue in a c# mvc 5 app by doing the following code but it still returns the exact same result:
IConfidentialClientApplication app = MsalAppBuilder.BuildConfidentialClientApplication();
var account = await app.GetAccountAsync(ClaimsPrincipal.Current.GetAccountId());
string[] scopes = { "Sites.FullControl.All" };
AuthenticationResult result = null;
HttpClient client = new HttpClient();
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, "https://graph.microsoft.com/v1.0/sites/{site_id_or_name}/permissions");
try
{
//Get acccess token before sending request
result = await app.AcquireTokenSilent(scopes, account).ExecuteAsync().ConfigureAwait(false);
if (result != null)
{
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", result.AccessToken);
//Request to get groups
HttpResponseMessage response = await client.SendAsync(request);
if (response.IsSuccessStatusCode)
{
ViewBag.Permissions = response.Content.ReadAsStringAsync().Result;
}
}
}
catch (Exception ex)
{
//Something went wrong
}
Any idea what is wrong here?
The GitHub project im using: https://github.com/Azure-Samples/ms-identity-aspnet-webapp-openidconnect just add a client id and secret from your app reg and you can copy my method above :)
The reason is very simple, because it does not support delegated permissions, so don't try to have a user login Graph Explorer for testing, because it uses delegated permissions by default.
You need to grant Sites.FullControl.All application permissions to the application in the Azure portal, and then use the client credential flow to obtain an access token. Then you can use postman to call that api.

Resource not found for the segment 'me'

i'm using Graph API to retrieve profile information of user who's currently logged in from the Azure AD, unfortunately i'm receiving the following error message :
{"odata.error":{"code":"Request_ResourceNotFound","message":{"lang":"en","value":"Resource not found for the segment 'me'."}}}
Below is my code :
Uri serviceRoot = new Uri(serviceRootURL);
ActiveDirectoryClient adClient = new ActiveDirectoryClient(
serviceRoot,
async () => await GetAppTokenAsync());
var user = (User)await adClient.Me
.Expand(x => x.Manager)
.ExecuteAsync();
And below is my code for GetAppTokenAsync() :
private static async Task<string> GetAppTokenAsync()
{
// Instantiate an AuthenticationContext for my directory (see authString above).
AuthenticationContext authenticationContext = new AuthenticationContext(authString, false);
// Create a ClientCredential that will be used for authentication.
// This is where the Client ID and Key/Secret from the Azure Management Portal is used.
ClientCredential clientCred = new ClientCredential(clientID, clientSecret);
// Acquire an access token from Azure AD to access the Azure AD Graph (the resource)
// using the Client ID and Key/Secret as credentials.
AuthenticationResult authenticationResult = await authenticationContext.AcquireTokenAsync(resAzureGraphAPI, clientCred);
// Return the access token.
return authenticationResult.AccessToken;
}
From your code "await GetAppTokenAsync()" , you are getting an app-only token , which using application identity, instead of as a user's identity .
The "(User)await adClient.Me" won't work if that token is not associated with a user .
To use app token to get user manager information ,you need to specify the user you want to query , code below is for your reference :
try
{
User manager = (User)await adClient.Users.GetByObjectId("5eba8883-c258-45d0-8add-a286a1ec1e91").Manager.ExecuteAsync();
}
catch (Exception ex)
{
throw;
}
Update
You could use authorization code flow for delegated permissions(user's identity) . If you want a client library code sample , you could refer to this code sample . After user sign in , you could use below code to get manager of current login user :
ActiveDirectoryClient client = AuthenticationHelper.GetActiveDirectoryClient();
User manager = (User)await client.Me.Manager.ExecuteAsync();
I used an application identity with the legacy Azure Active Directory api and the 'Application.ReadWrite.OwnedBy' permission to work around the Resource not found for the segment 'me' error. The same permission exists in the Microsoft Graph api, but the behavior is not identical. More information here.

Application access to SharePoint Online using Azure AD Token

How can I get an application token to query SharePoint with application credentials (= without user impersonation) using Azure AD?
The following code works perfectly for querying data as a user but we need to fetch information without impersonation like listing all sites in the collection regardless of user permissions etc.
Exception thrown:
An exception of type
'Microsoft.IdentityModel.Clients.ActiveDirectory.AdalServiceException'
occurred in mscorlib.dll but was not handled in user code
Additional information: AADSTS70001: Application with identifier 'xxx'
was not found in the directory sharepoint.com
Code to get token:
internal static async Task<string> GetSharePointAccessToken(string url, string userAccessTokenForImpersonation)
{
string clientID = #"<not posted on stack overflow>";
string clientSecret = #"<not posted on stack overflow>";
var appCred = new ClientCredential(clientID, clientSecret);
var authContext = new Microsoft.IdentityModel.Clients.ActiveDirectory.AuthenticationContext("https://login.windows.net/common");
// Use user assetion if provided, otherwise use principal account
AuthenticationResult authResult = null;
if (string.IsNullOrEmpty(userAccessTokenForImpersonation))
{
authResult = await authContext.AcquireTokenAsync(new Uri(url).GetLeftPart(UriPartial.Authority), appCred);
}
else
{
authResult = await authContext.AcquireTokenAsync(new Uri(url).GetLeftPart(UriPartial.Authority), appCred, new UserAssertion(userAccessTokenForImpersonation));
}
return authResult.AccessToken;
}
Test code:
// Auth token from Bearer https://xxx.azurewebsites.net/.auth/me
string authHeader = #"<valid jwt bearer token from azure auth>";
var sharePointUrl = #"https://xxx.sharepoint.com/sites/testsite/";
string sharePrincipalToken = await GetSharePointAccessToken(sharePointUrl, null); // <-- doesn't work
string sharePointUserToken = await GetSharePointAccessToken(sharePointUrl, authHeader); // <-- works
Permissions in Azure AD:
The error message you are getting implies that you are signing in with a user that is pointing our token service to get a token in the context of "sharepoint.com"
This is because you are using the "common" endpoint. Read more about that here.
Instead try using a fixed endpoint, where the tenant is the same as where the application is registered and see if that solves your issue.
If your plan is to make this application accessible by multiple tenants, make sure that you have explicitly set your application to be multi-tenant, and then make sure you have a user from the external tenant try and sign into the application before you try doing service to service calls.
Let me know if this helps.

Is it possible to operate OneNote with azure daemon App?

In order to operate OneNote with azure's daemon app,
I created a new ClientID, acquired the Access Token by user authentication with that ClientID, and realized access to the OneNote API using it.
However, instead of user authentication, Access token is acquired by ClientID and certificate, and access to OneNote API using it is refused.(401 Unauthorized)
How can I operate OneNote from azure dameon App?
The way I tried
The AccessToken creation by the certificate was implemented with reference to the following.
https://azure.microsoft.com/ja-jp/resources/samples/active-directory-dotnet-daemon-certificate-credential/
Specific AccessToken acquisition codes are as follows,
public async Task AuthWithCertAsync(string tenant, string clientID, string certName)
{
var authority = $"{aadInstance}{tenant}";
var authContext = new AuthenticationContext(authority);
//refer: above URL
ClientAssertionCertificate certCred = GetCertificate(clientID, certName);
if (certCred == null) {return false;}
//"https://graph.microsoft.com/";
var graphResult = await authContext.AcquireTokenAsync(graphResourceID, certCred);
graphToken = graphResult.AccessToken;
//"https://www.onenote.com/";
var onenoteResult = await authContext.AcquireTokenAsync(onenoteResourceID, certCred);
onenoteToken = onenoteResult.AccessToken;
}
With this graphToken, access to the Graph API succeeds.
using (var client = new HttpClient())
{
client.DefaultRequestHeaders.Add("Authorization", $"Bearer {graphToken}");
//e.g. "https://graph.microsoft.com/v1.0/groups", "https://graph.microsoft.com/v1.0/users"
var response = await client.GetStringAsync(url);
...
}
However, if the target URL is an API on onenote, it fails.
using (var client = new HttpClient())
{
client.DefaultRequestHeaders.Add("Authorization", $"Bearer {graphToken}");
//e.g:"https://graph.microsoft.com/beta/users/{userID}/notes/notebooks"
// Occured HttpRequestException(401 Unauthorized)
var response = await client.GetStringAsync(url);
...
}
This request returns HTTP 401 Unauthorized status.
Also when accessing OneNote API on onenoteToken failed.
using (var client = new HttpClient())
{
client.DefaultRequestHeaders.Add("Authorization", $"Bearer {onenoteToken}");
//e.g.:"https://www.onenote.com/api/v1.0/users/{userID}/notes/notebooks"
var response = await client.GetStringAsync(url);
return response;
}
This request also returns HTTP 401 Unauthorized status.
The application setting in Azure Active Directory:
Type:
WEB APPLICATION AND/OR WEB API
Multi Tenant:
ON
permissions to other applications:
Graph, OneNote, Active Directory, SharePoint :Application Permissions all checked.
In the admin account of the target tenant, the following admin consent URL is accessed and accepted.
https://login.microsoftonline.com/common/adminconsent?client_id={clientID}&state={state}&redirect_uri={redirectUrl}
Update
According to the answer of https://stackoverflow.com/a/41890179/1411521,
I understood that there is no way to access OneNote by daemon App with the current Graph API. (at 2017-1-31)
However, Application Permission of OneNote API can set as follows.
View and modify notes for all users
View notes for all users
Despite the fact that they are valid, what causes the authentication error (401 Unauthorized) with the following code?
using (var client = new HttpClient())
{
client.DefaultRequestHeaders.Add("Authorization", $"Bearer {onenoteToken}");
//e.g.:"https://www.onenote.com/api/v1.0/users/{userID}/notes/notebooks"
var response = await client.GetStringAsync(url); // Occured HttpRequestException(401 Unauthorized)
...
}
You were mixing the Microsoft Graph and OneNote API.
The token you were acquire is for the Microsoft Graph REST, and you can manipulate the OnenNote through Microsoft Graph REST which in beta version by following the document here(beta reference->OneNote).
And if you want to use the OneNoe API, you can refer the document here for the authentication.
Update
To list the notebooks, we need permissions like Notes.Read, Notes.ReadWrite.CreatedByApp, Notes.ReadWrite, Notes.Read.All, or Notes.ReadWrite.All. However there is no such kinds of permission for the Client Credential flow for Microsoft Graph.
If you want the Microsoft Graph to support the Client Credential flow to manipulate the OneNote, you can submit the feedback from here.
This problem was solved today(2017-2-10).
The OneNote REST API now supports application-level permissions
OneNote authentication and Azure AD application permissions

How to enable App Service Mobile App SSO for UWP

I am building a Universal Windows Platform (UWP) app that uses the Azure App Service Mobile App backend as well as the user's OneDrive account. I have 2 requirements for authentication:
If the user is logged in to their UWP device with a Microsoft account (e.g. Windows 10) then I don't want them to be presented with a login prompt (i.e. Single Sign On, re-using their Microsoft account credentials).
I want to have a single authentication event across Azure & OneDrive, i.e. the user authorises once and I re-use that token for both services.
I did this in Windows Phone 8 with an Azure Mobile Service by logging in with the Live SDK and then passing the returned token to the MobileServiceClient.LoginAsync() method, however I can't get this to work in UWP with an Azure Mobile App. When I call that same method I receive a 401 Unauthorised response.
I have associated my UWP app with the store and set up the
application at the Microsoft Account Developer Centre, including
adding the redirect URI from the Azure Mobile App.
I have set up the Azure App Service Mobile App, including adding the
Client ID & Secret from the Microsoft Account Developer Centre.
I have tried numerous ways to retrieve the token, including the
OnlineIdAuthenticator, WebAuthenticationCoreManager and
WebAuthenticationBroker. None has worked so far.
I currently use the following code in a class LiveAuthenticationService to retrieve an access token:
public async Task<bool> LoginAsync()
{
AccessToken = null;
bool success = false;
OnlineIdAuthenticator onlineIdAuthenticator = new OnlineIdAuthenticator();
EventWaitHandle waithandle = new ManualResetEvent(false);
OnlineIdServiceTicketRequest serviceTicketRequest = new OnlineIdServiceTicketRequest(scopes, "DELEGATION");
UserIdentity result = await onlineIdAuthenticator.AuthenticateUserAsync(serviceTicketRequest);
if (!string.IsNullOrWhiteSpace(result?.Tickets[0]?.Value))
{
currentUserId = result.SafeCustomerId;
AccessToken = result.Tickets[0].Value;
success = true;
waithandle.Set();
}
else
{
await logger.LogErrorAsync("Error signing in to Microsoft Live",
new Dictionary<string, string> { { "errorCode", result?.Tickets[0]?.ErrorCode.ToString() } });
}
waithandle.WaitOne(10000); //10 second timeout
return success;
}
And then this to attempt to login to my Azure Mobile App with that token, which uses LiveAuthenticationService from above:
private async Task RefreshUserIdAndAccessToken()
{
try
{
var tcs = new TaskCompletionSource<MobileServiceUser>();
var authService = new LiveAuthenticationService();
await UiDispatcher.RunAsync(CoreDispatcherPriority.Normal,
async () =>
{
try
{
await authService.LoginAsync();
var jsonAuthenticationToken = JObject.Parse(#"{""authenticationToken"": """ + authService.AccessToken + #"""}");
tcs.SetResult(await mobileService.LoginAsync(MobileServiceAuthenticationProvider.MicrosoftAccount, jsonAuthenticationToken));
}
catch (Exception ex)
{
tcs.SetException(ex);
}
});
var user = await tcs.Task;
currentUserId = user.UserId;
AccessToken = user.MobileServiceAuthenticationToken;
}
catch (Exception ex)
{
await logger.LogExceptionAsync(ex,
Constants.LOGGING_DATAKEY_REFRESHACCESSTOKENFAILURE,
currentUserId);
currentUserId = null;
AccessToken = null;
}
}
As stated this results in a 401 Unauthorised response from Azure. I have run Fiddler and the request seems to be correct, the expected authentication token is included in a JSON payload with the request.
UPDATE
One thing I can see is that the token issued by the code above is almost 900 characters long, all in the form YnElFkAAcK8bRSQab/FK+PT5n/wA4CPU..., while the token issued if I let Azure Mobile App handle the authentication, i.e. call MobileServiceClient.LoginAsync() without passing a token, is only about 350 characters long and in the form hbGciOi.eyJmdWWxsIiwiRGJn... (notice the period towards the beginning).
This issue is really causing me problems now. I can't release the app without the authentication working and I can't figure out how to fix it. Any help will be appreciated.
This was a tough one for me to solve as I was facing this problem too.
The most important part is the OnlineIdServiceTicketRequest the request should look like this:
var mobileServicesTicket = new OnlineIdServiceTicketRequest("https://yourmobileservice.azure-mobile.net/", "JWT");
Note that we are specifying your endpoint and also requesting a JWT token instead of delegation. This will get the 350ish character token you were looking for.
Here is a full code sample of what I'm doing:
public async Task<bool> LoginAsync()
{
var authenticator = new Windows.Security.Authentication.OnlineId.OnlineIdAuthenticator();
var mobileServicesTicket = new Windows.Security.Authentication.OnlineId.OnlineIdServiceTicketRequest("https://yourendpoint.azure-mobile.net/", "JWT");
var ticketRequests = new List<OnlineIdServiceTicketRequest>() { mobileServicesTicket };
var authResult = await authenticator.AuthenticateUserAsync(ticketRequests, CredentialPromptType.PromptIfNeeded);
if ((authResult.Tickets.Count == 1) && (authResult.Tickets[0].ErrorCode == 0))
{
var accessToken = authResult.Tickets[0];
var res = await _mobileServiceClient.LoginWithMicrosoftAccountAsync(accessToken.Value);
return true;
}
else
{
return false;
}
}
_mobileServiceClient is injected into the class and is a reference to Microsoft.WindowsAzure.MobileServices.MobileServiceClient object within the WindowsAzure.MobileServices library.
I actually ended up writing a blog article about this problem here http://jshapland.com/single-sign-on-with-azure-mobile-services-in-a-uwp-app/

Resources