Presence endpoint returns Forbidden with application permissions - c#

With help from a fellow stacker, I was able to make successful calls to thre MS graph api, at least users, but I have been wanting to get the teams status of a user using the Presence function. I have not been able to get around the 403 Forbidden error. I have read much of the prose MS has written on OAuth 2.0 and have tried sample apps, graph explorer, and tried Postman as well.
I have checked my app permissions in Azure portal and according to the display, Presence is application able, not just delegate:
As I can perform a User.Read.All it is confusing that I cannot call Presence without the error. This is the basic function I got help in writing and it fails on the presence call:
static async Task MainAsync()
{
var tenantId = "some giud";
var clientId = "more guid";
var clientSecret = "even more guid";
try
{
string[] scopes = { "https://graph.microsoft.com/.default" };
ClientSecretCredential clientSecretCredential = new ClientSecretCredential(tenantId, clientId, clientSecret);
GraphServiceClient graphClient = new GraphServiceClient(clientSecretCredential, scopes);
//var users = await graphClient.Users.Request().GetAsync();
//foreach (var user in users)
// Console.WriteLine(user.UserPrincipalName);
var userid = await graphClient.Users["my.name#company.com"].Request().GetAsync();
Console.WriteLine(userid.Id);
var presence = await graphClient.Users["{user id}"].Presence.Request().GetAsync();
// Console.WriteLine(presence)
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
I scoured google and MS looking for examples and I found one from MS and this is the link to github:
git clone https://github.com/Azure-Samples/active-directory-dotnetcore-devicecodeflow-v2.git
It will ask to perform a login via a browser and code. I got this code working and it does ask that I log in. I had to alter my registered app to this setting:
https://i.stack.imgur.com/Uz9IK.jpg
All this I did and I still get the 403 forbidden error. I am wondering if anyone has a working set of code that calls Presence and can share either what I am missing, or is this only something done in teams.
Why they have it so restricted is beyond reason given I can get more interesting user data just calling users/{emails}

I tried to reproduce the scenario and get the present status of the user .
I got the similar error forbidden when I gave a client secret which is expired and does not have Presence.Read.All delegated permissions .I only had application permissions
Then I added delegated permissions and user.read permissions (also granted admin consent).
I could get the status successfully with below code and query through graph where it uses access token (as authorization header is mandatory parameter to be passed ).
Note: Getting the presence requires users signed in.So while requesting On behalf of user, delegated permissions are must.
GraphServiceClient graphClient = new GraphServiceClient( authProvider );
var presence = await graphClient.Users["c4xx3cf2axxxxa6df-d2xxxx391"].Presence
.Request()
.GetAsync();
Presence of some user with Id
Current user’s presence:
Please make sure to go through all the required constraints to Resolve Microsoft Graph authorization errors - Microsoft Graph | Microsoft Learn
It requires Presence.Read or Presence.Read.All Delegated permissions to query the presence of the user .
Reference : Get presence - Microsoft Graph v1.0 | Microsoft Learn

Related

Get Microsoft accessToken silently

I'm trying to connect to Graph API and get user access token.
My problem is that I don't know how to connect to Graph API with credentials silently (without browser).
I currently use MSLogin() for get access token but it open a browser where you can authorize an AzureAD app to get some access to your account. A library in Java is litteraly what I want in c# https://github.com/Litarvan/OpenAuth
I need something like: MSGraph.ConnectAsync(email, pass).getAccessToken();
Here my current code (Through a browser)
private const string ClientId = "520f6e8e-xxxxxxxxxxxxxxxxxxxx";
private string[] scopes = { "https://graph.microsoft.com/user.read" };
private static AuthenticationResult authResult;
public static IPublicClientApplication PublicClientApp;
private async Task<AuthenticationResult> MSLogin()
{
PublicClientApp = PublicClientApplicationBuilder.Create(ClientId).WithRedirectUri("msal520f6e8e-xxxxxxxxxxxxxxxxxxxxxxxxxxxxx://auth").Build();
authResult = await PublicClientApp.AcquireTokenInteractive(scopes).ExecuteAsync();
return authResult;
}
If you are using Microsoft Graph .NET Client Library you can check documentation with example how to implement username/password authentication flow.
string[] scopes = {"User.Read"};
var usernamePasswordCredential = new UsernamePasswordCredential("username#domain.com", "password", tenantId, clientId);
var graphClient = new GraphServiceClient(usernamePasswordCredential, scopes);
var me = await graphClient.Me.Request().GetAsync();
You can use AcquireTokenByUsernamePassword() for that, see MSDN.
Note however that Microsoft discourages usage of this flow and depending on your AzureAD setup there might be restrictions (i.e. you can aquire tokens only within a certain IP range etc).
Well, you can get the access token silently but not at the first time, First a user must authorize your app by going through Microsoft's Login flow and for your subsequent calls to Microsoft, you can get the access token without the intervention of user.
I would just give a basic idea, without focusing on a specific SDK that you might be using. For which, you can decide which ever method suits your needs.
I assume, you already have your credentials and desired scopes with you, otherwise you need to obtain those.
Formulate a proper URL using the credentials you obtained, plus you need to add an extra scope in the URL which is offline_access. Then you need to redirect the user to Microsoft for the initial authorization.
If the user logs in successfully, Microsoft will redirect the user back to your website with an Authorization Code.
Grab that Authorization Code and exchange it for an Access Token using /oauth2/{version}/token api.
You will receive a response from above call which will contain an Access Token along with a Refresh Token. You need to store the refresh token somewhere for future use.
Now comes the interesting part.
Using the refresh token, you can renew the access token when it expires without user's intervention. You can use oauth2/v2.0/token api with parameters:
client_id={your_client_id}&scope={your_scopes}&refresh_token={refresh_token_obtained}&grant_type=refresh_token&client_secret={your_client_secret}
The resultant response would look something like this:
{
"access_token": "new access token",
"token_type": "Bearer",
"expires_in": 3599,
"scope": "your scopes",
"refresh_token": "refresh token",
}
REF: https://learn.microsoft.com/en-us/graph/auth-v2-user#authorization-request

Authentication failed exception with MailKit OAuth2.0

I'm using the following code to get an access token and connect to the mail folder:
var confidentialClientApplicationBuilder = ConfidentialClientApplicationBuilder.Create(clientId).WithClientSecret(clientSecret).WithTenantId(tenantId).Build();
var scopes = new string[] { ".default" };
var authToken = await confidentialClientApplicationBuilder.AcquireTokenForClient(scopes).ExecuteAsync();
var oauth2 = new SaslMechanismOAuth2(username, authToken.AccessToken);
using (ImapClient client = new ImapClient())
{
await client.ConnectAsync("outlook.office365.com", 993, SecureSocketOptions.SslOnConnect);
await client.AuthenticateAsync(oauth2);
//TODO
await client.DisconnectAsync(true);
}
Everything seems to work correctly here, the ImapClient is connected and I can see oauth2.Credentials.Password is populated with the access token. However, when I run it the AuthenticateAsync method throws the error:
MailKit.Security.AuthenticationException: 'Authentication failed.'
I have noticed that the authToken.Account is null and that's why I'm passing the account name in by the string username. Also it seems I have to use the .default scope as anything else causes an error on AcquireTokenForClient as per this question.
Any ideas what I'm doing wrong here?
It seems that what you want is not possible at this time. See this Github issue for details.
Basically, using ConfidentialClientApplicationBuilder can only use scopes defined as "API permissions" on your AppRegistration. If you have registered IMAP.AccessAsUser.All or Mail.Read Graph permissions, and requested them using the https://graph.microsoft.com/.default scope, you will get an access token, but it can only be used by the Graph API REST endpoints Microsoft has exposed.
MailKit does not support these Graph API endpoints (as the linked issue describes).
In order to use the IMAP support in MailKit it seems you must get an access token using PublicClientApplicationOptions as demonstrated in the MailKit example. This has the disadvantage of popping up the browser asking the user to authenticate themselves.
It is, however, uncertain how long this will work, as it seems Microsoft will deprecate their IMAP endpoints (as mentioned in the previously linked issue)

Authenticate Office 365 IMAP Account using Unattended C# Console

I am developing a .NET Core Console Application (C#) that needs to authenticate to an Office 365 IMAP account. The purpose is to retrieve mail and process CSV file attachments unattended.
The app has been registered on Azure as a Mobile/Desktop app with the RedirectUri set as http://localhost.
The following code causes a new tab to open in Chrome web browser and asks for the Outlook account to use for login. I need to stop the browser from opening and handle authentication completely from code.
Current Code:
using var client = new ImapClient(new ProtocolLogger("imap.log"));
var options = new PublicClientApplicationOptions
{
ClientId = _options.ClientId,
TenantId = _options.TenantId,
RedirectUri = "http://localhost"
};
var publicClientApplication = PublicClientApplicationBuilder
.CreateWithApplicationOptions(options)
.Build();
var scopes = new[]
{
"email",
"offline_access",
"https://outlook.office.com/IMAP.AccessAsUser.All" // Only needed for IMAP
//"https://outlook.office.com/POP.AccessAsUser.All", // Only needed for POP
//"https://outlook.office.com/SMTP.AccessAsUser.All", // Only needed for SMTP
};
var cancellationToken = new CancellationToken();
var authToken = await publicClientApplication
.AcquireTokenInteractive(scopes)
.ExecuteAsync(cancellationToken);
await publicClientApplication
.AcquireTokenSilent(scopes, authToken.Account)
.ExecuteAsync(cancellationToken);
SaslMechanism oauth2;
if (client.AuthenticationMechanisms.Contains("OAUTHBEARER"))
{
oauth2 = new SaslMechanismOAuthBearer(authToken.Account.Username, authToken.AccessToken);
}
else
{
oauth2 = new SaslMechanismOAuth2(authToken.Account.Username, authToken.AccessToken);
}
await client.AuthenticateAsync(oauth2);
await client.DisconnectAsync (true);
This line triggers a browser window to open https://login.microsoftonline.com/:
var authToken = await publicClientApplication
.AcquireTokenInteractive(scopes)
.ExecuteAsync(cancellationToken);
This console application will be run unattended. How do I obtain the token and authenticate without a web browser opening up?
This is an answear to your latest comment, as it's my final recommendation. So, first of all, you should decide if you want to acess the data on behalf of user, or as an app granted permissions by admin.
First step is to register your app.
Second step is getting the acess token. This is going to differ based on the method you chose. Tutorial for each: acting on behalf of the user or acting without the user, but granted permission from admin.
Once you have the acess token, you can call the Microsoft Graph API. The important thing is, you always have to call Microsoft Graph API. There is no other official way (as far as I know) of comunicating with Microsoft's services. You can try the requests with the Microsoft Graph Explorer, however it's VERY limited with it's defaul urls/parameters, so I suggest taking a look at the docs.
From what you've described, you first want to obtain UserID. The way of doing this is going to vary based on what type of auth you chose.
If you chose to act on behalf of user, you should be able to get that (ID) using this endpoint: https://graph.microsoft.com/v1.0/me/
If you chose to act as an app with admin consent, you should be able to search for user using the https://graph.microsoft.com/v1.0/me/people/?$search= with search query parameters. Here are the docs for this endpoint
Now, the only thing left, is to supply that ID to one of the Outlook api methods. You can find docs for them here. Specifically, it seems like you want to list all messages and then read a specific message.
Also, keep an eye on what methods you use with which type of auth. On behalf of user, you usually want url's that contain /me, on behalf of app with given admin privelages, you usually want some endpoint that enables you to pass user id.
Hope I helped!
PS: There is no code in this response, because there is a lot of stuff that just cannot be coded without your decisions, actions on Azure and so on. I suggest you read a little bit about auth and graph api using microsoft docs I linked earlier.
This code worked for me using MSAL after registering the app in azure and getting a client secret.
var options = new ConfidentialClientApplicationOptions
{
ClientId = "<ClientID or Application ID>",
TenantId = "<Azure TenantId>",
RedirectUri = "http://localhost"
};
string clientSecret = "<Client Secret Goes here>";
var confidentialClientApplication = ConfidentialClientApplicationBuilder
.CreateWithApplicationOptions(options)
.WithClientSecret(clientSecret)
.Build();
var scopes = new string[] {
"https://outlook.office365.com/.default"
};
var authToken = await confidentialClientApplication.AcquireTokenForClient(scopes).ExecuteAsync();

Get Plans returns 401 - Unauthorized

I am new to Microsoft Graph. I wanted to create a console app which will fetch all the Group Ids based on a Group Name and then fetch a particular Plan Id with that Group Id and Plan Name (I may or may not be a member of that group).
I have written a code which is able to fetch group Id but when I try to use it to fetch the Plan Id:
var targetGroup = await _graphClient
.Groups
.Request()
.Filter($"startsWith(displayName,'{groupName}')")
.GetAsync();
var groupId = targetGroup.First().Id;
var plans = await _graphClient
.Groups[groupId]
.Planner
.Plans
.Request()
.GetAsync();
This last line fires an exception:
401 - Unauthorized: Access is denied due to invalid credentials.
I am using the O365 E3 Trial version. I have read all the solutions posted here and tried them but the error is still there. I have allowed all the permissions (Delegated and Application) for Users, Tasks, Directory, Files, and Groups.
Could it be a problem with permissions or the trail version?
Update:
I also tried this:
GraphHttpClient.MicrosoftGraphV1BaseUri + $"groups/{groupId}/planner/plans";
It works fine for groupId but when I add /planner/plans it throws the same exception.
Are you using User access token or Application token? Because that api (GET /groups/{group-id}/planner/plans) it's not supported for Application token according to documentation.
I'm not sure for User access token but I think you must be a member of the group to reach out to plans.
The sample code you've refferenced is using the Client Credentials OAuth grant. This will result in assigning Application scopes to your token rather than Delegate scopes:
AuthenticationResult authResult = null;
authResult = await _clientApplication
.AcquireTokenForClient(_scopes) // This is "Client Credentials"
.ExecuteAsync();
return authResult.AccessToken;
Specifically, the AcquireTokenForClient call is where this is happening. From the documentation:
Acquires a token from the authority configured in the app, for the confidential client itself (in the name of no user) using the client credentials flow. See https://aka.ms/msal-net-client-credentials.
As Martin Jones stated in his answer, /plans only supports Delegated permission scopes:
Delegated (work or school account): Group.Read.All, Group.ReadWrite.All
Delegated (personal Microsoft account): Not supported.
Application: Not supported.
In order to call /plans, you will need to acquire a token using a different OAuth Grant which supports Delegated permissions such as AcquireTokenOnBehalfOf or AcquireTokenByAuthorizationCode.

Create Microsoft Graph GraphServiceClient with user/password unattended

I am creating a console application that connects to Microsoft Graph using the Microsoft Graph API (as shown in https://github.com/microsoftgraph/console-csharp-connect-sample).
Everything is working fine, but I wonder if there is a way where I can authenticate a user (when I already know their user/password) without them needing to manually enter their credentials on the "Sing in to your account" window rendered on the desktop.
The idea is basically to run the application unattended, so there is no need for the user to be entering their credentials when the application starts. I can´t find any relevant information on the subject.
Is that even possible?
EDIT
After following the link #DanSilver posted about geting access without a user, I tried the sample suggested in that link (https://github.com/Azure-Samples/active-directory-dotnet-daemon-v2). Although that is an MVC application that forces users to authenticate (precisely what I wanted to avoid) I have managed to use part of the authentication code in that sample with my console application. After giving authorization to the application manually through a request to https://login.microsoftonline.com/myTenantId/adminconsent I can create a GraphServiceClient in my console app that connects to Graph without user interaction. So I mark the answer as valid.
Just in case someone is in the same situation, the GraphServiceclient is created as:
GraphServiceClient graphServiceClientApplication = new GraphServiceClient("https://graph.microsoft.com/v1.0", new DelegateAuthenticationProvider(
async (requestMessage) =>
{
string clientId = "yourClientApplicationId";
string authorityFormat = "https://login.microsoftonline.com/{0}/v2.0";
string tenantId = "yourTenantId";
string msGraphScope = "https://graph.microsoft.com/.default";
string redirectUri = "msalXXXXXX://auth"; // Custom Redirect URI asigned in the Application Registration Portal in the native Application Platform
string clientSecret = "passwordGenerated";
ConfidentialClientApplication daemonClient = new ConfidentialClientApplication(clientId, String.Format(authorityFormat, tenantId), redirectUri, new ClientCredential(clientSecret), null, new TokenCache());
AuthenticationResult authResult = await daemonClient.AcquireTokenForClientAsync(new string[] { msGraphScope });
string token = authResult.AccessToken;
requestMessage.Headers.Authorization = new AuthenticationHeaderValue("bearer", token);
}
));
One idea is using the "app only" authorization flow. The idea is that you can have long running apps access the Microsoft Graph without user authentication. The main difference is instead of the access token granting access to a particular user, it grants your app access to resources that you've consented to in advance. There will be no user login dialog and you can programmatically fetch access tokens to call the Graph API.
To reiterate that these tokens aren't for a particular user, consider making a GET request to 'https://graph.microsoft.com/v1.0/me'. This will return an error since the access token isn't for a particular user and "me" doesn't mean anything. Requests should be sent with full user ids "like graph.microsoft.com/users/someuser#contosos.com".
More information on this can be found at the Get access without a user documentation page.
Another idea is to let the user authenticate the first time they use your app and then store a refresh token. These tokens live longer (a few months IIRC) and then you won't need to prompt for user consent each time the app runs. Refresh tokens can be exchanged for access tokens that live 60 minutes and those can be used to call Graph API on behalf of users.
More info on refresh tokens: https://developer.microsoft.com/en-us/graph/docs/concepts/auth_v2_user#5-use-the-refresh-token-to-get-a-new-access-token
I did want to come back out here and share, since I ran into this problem yesterday, and the idea of granting read/write mailbox access for my application... to EVERYONE'S EMAIL BOX IN THE ENTIRE ORGANIZATION... was way over the top for my needs. (And that is exactly what happens when you start talking about granting Application level permissions instead of delegated permissions to your registered app).
It's a simple use case: I had a nightly process that needed to automate sending of emails from a shared mailbox using a traditional AD service account.
Thankfully... even though they are on the march to eliminate passwords (lol)... someone at Microsoft still recognizes my use case, and it's lack of apples-to-apples alternatives in Azure AD. There is still an extension method we can lean on to get the job done:
private AuthenticationContext authContext = null;
authContext = new AuthenticationContext("https://login.microsoftonline.com/ourmail.onmicrosoft.com",
new TokenCache());
result = authContext.AcquireTokenAsync("https://graph.microsoft.com/",
"12345678-1234-1234-1234-1234567890",
new UserPasswordCredential(
Environment.GetEnvironmentVariable("UID", EnvironmentVariableTarget.User),
Environment.GetEnvironmentVariable("UPD", EnvironmentVariableTarget.User)
)).Result;
You can replace those GetEnvironmentVariable calls with your Username (UID) and Password (UPD). I just stuff them in the environment variables of the service account so I didn't have to check anything into source control.
AcquireTokenAsync is an extension method made available from the Microsoft.IdentityModel.Clients.ActiveDirectory namespace. From there, it's a simple business to fire up a GraphClient.
string sToken = result.AccessToken;
Microsoft.Graph.GraphServiceClient oGraphClient = new GraphServiceClient(
new DelegateAuthenticationProvider((requestMessage) => {
requestMessage
.Headers
.Authorization = new AuthenticationHeaderValue("bearer", sToken);
return Task.FromResult(0);
}));
The last bit of magic was to add these permissions to Application registration I created in Azure AD (where that GUID came from). The application has be defined as a Public client (there's a radio button for that towards the bottom of the authentication tab). I added the following 5 DELEGATED permissions (NOT application permissions):
Microsoft Graph
1. Mail.ReadWrite.Shared
2. Mail.Send.Shared
3. User.Read
4. email
5. openid
Since user consents are actually blocked in our organization, another permissions admin had to review my application definition and then do an admin level grant of those rights, but once he did, everything lit up and worked like I needed: limited access by a service account to a single shared mailbox, with the actual security of that access being managed in Office 365 and not Azure AD.

Categories