Get user mail from o365 using graph sdk in a console app - c#

I want to read a users mail from a .net console app without user interaction.
I would like to give the app access to read only selected user(s) mail and not as global admin that can read all users mail.
I would like to use the .net Microsoft.Graph library and not raw REST interface.
I think i need more or less step-by-step instructions i this i seven possible
I have created an new application registration and a client secret
If i give Application permission to mail it works but i cant get delegated permission to work.
The code is just one of many i have tried , but i cant really find any that do what i want to do.
var tenantId = "domain123.onmicrosoft.com";
var client_Id = "1234567789";
var client_Secret = "123243456777";
var scopes = new[] { "https://graph.microsoft.com/.default" };
// Configure app builder
var authority = $"https://login.microsoftonline.com/{tenantId}";
var app = ConfidentialClientApplicationBuilder
.Create(client_Id)
.WithClientSecret(client_Secret)
.WithAuthority(new Uri(authority))
.WithLogging(MyLoggingMethod, LogLevel.Verbose,
enablePiiLogging: true,
enableDefaultPlatformLogging: true)
.Build();
// Acquire tokens for Graph API
var authenticationResult = await app.AcquireTokenForClient(scopes).ExecuteAsync();
// Create GraphClient and attach auth header to all request (acquired on previous step)
var graphClient = new GraphServiceClient(
new DelegateAuthenticationProvider(requestMessage =>
{
requestMessage.Headers.Authorization =
new AuthenticationHeaderValue("bearer", authenticationResult.AccessToken);
return Task.FromResult(0);
}));
// Call Graph API
var user = await graphClient.Users["user123#domain123.onmicrosoft.com"].Messages.Request().GetAsync();
Code: NoPermissionsInAccessToken
Message: The token contains no permissions, or permissions can not be understood.
Inner error

Can I suggest rather than trying to roll your own DelegateAuthenticationProvider, that you use one of the provided ones? e.g.
IConfidentialClientApplication confidentialClientApplication = ConfidentialClientApplicationBuilder
.Create(clientId)
.WithTenantId(tenantID)
.WithClientSecret(clientSecret)
.Build();
ClientCredentialProvider authProvider = new ClientCredentialProvider(confidentialClientApplication);
var graphClient = new GraphServiceClient(authProvider);
Docs: https://learn.microsoft.com/en-us/graph/sdks/choose-authentication-providers?tabs=CS#ClientCredentialsProvider
Nuget: https://www.nuget.org/packages/Microsoft.Graph.Auth/1.0.0-preview.1

I did try this earlier with success but since the comment on UsernamePasswordProvider is NOT RECOMMENDED I was hoping for a better solution.
To get this to work I did following steps
Create new App registration with redirect as public client/native = myapp://auth
Go into Authentication and set "Treat application as public client" = Yes
To allow app to access user data, go to:
https://login.microsoftonline.com/[tenant]/oauth2/v2.0/authorize?client_id=[client_id]&response_type=code&redirect_uri=myapp://auth&response_mode=query&scope=user.read&state=12345
Login with user that you want to use in app , this page will hand but it ok.
Is this really the best way to do what i want?
static async Task Main(string[] args)
{
var GraphClient = CreateGraphClient();
User me = await GraphClient.Me.Request()
.WithUsernamePassword("user123#domain123.onmicrosoft.com", new NetworkCredential("", "MyPassword").SecurePassword)
.GetAsync();
Console.WriteLine("OK:" + me.DisplayName);
Console.ReadLine();
}
public static GraphServiceClient CreateGraphClient()
{
string clientId = "1234567-1234-1234-12345-1234567890";
string tenantID = "domain123.onmicrosoft.com";
IPublicClientApplication publicClientApplication = PublicClientApplicationBuilder
.Create(clientId)
.WithTenantId(tenantID)
.Build();
UsernamePasswordProvider authProvider = new UsernamePasswordProvider(publicClientApplication, null);
GraphServiceClient graphClient = new GraphServiceClient(authProvider);
return graphClient;
}
Another solution could be to make an application permission and then set access policy using PowerShell new-applicationaccesspolicy
https://learn.microsoft.com/en-us/powershell/module/exchange/organization/new-applicationaccesspolicy?view=exchange-ps
I have not tried that one yet, anyone know if this could help?

Related

Get members of a Security Group from Azure AD via Microsoft Graph

I have been trying to get the members of a specific securitygroup from Azure AD with the following code from Graph api
var members = await graphClient.Groups["{group-id}"].Members
.Request()
.GetAsync();
I followed the following link which says to give the following permission to the registered app link: https://learn.microsoft.com/en-us/graph/api/group-list-members?view=graph-rest-1.0&tabs=csharp
and the permission for the application has been granted by the Admin;
But I keep getting the following error
ServiceException: Code: Authorization_RequestDenied Message: Insufficient privileges to complete the operation
I Using a client secret to create graphClient. And I grant permission like below, it works for me. You also can use other provider to do that.
My test code
public async Task<JsonResult> test()
{
// Values from app registration
var clientId = "fb2****-29ee-****-ab90-********0c7e1";
var clientSecret = "w7N*******........***yO8ig";
var scopes = new[] { "https://graph.microsoft.com/.default" };
// Multi-tenant apps can use "common",
// single-tenant apps must use the tenant ID from the Azure portal
var tenantId = "e4c9ab4e-****-40d5-****-230****57fb";
var options = new TokenCredentialOptions
{
AuthorityHost = AzureAuthorityHosts.AzurePublicCloud
};
// https://learn.microsoft.com/dotnet/api/azure.identity.clientsecretcredential
var clientSecretCredential = new ClientSecretCredential(
tenantId, clientId, clientSecret, options);
var graphClient = new GraphServiceClient(clientSecretCredential, scopes);
try
{
var members = await graphClient.Groups["13ad4665-****-43e9-9b0f-ca****eb"].Members.Request().GetAsync();
return Json(members);
}
catch (Exception e)
{
return Json("");
throw;
}
}
My test result
1st step : you will have to register an AD app and give permission on graph to read users and groups, please check this stackoverflow answer

How to get profile picture with Microsoft.Graph SDK or User.Identity

I have implemented authentication successfully with the quickstart project in Microsoft Azure, but now I'm stuck with getting the profile picture of a logged-in user.
I have tried using Microsoft.Graph SDK, but the Photo keep getting null
IConfidentialClientApplication confidentialClientApplication = ConfidentialClientApplicationBuilder
.Create(ClientId)
.WithTenantId(TenantId)
.WithClientSecret(ClientSecret)
.Build();
AuthorizationCodeProvider authProvider = new AuthorizationCodeProvider(confidentialClientApplication);
// Create an authentication provider.
ClientCredentialProvider authenticationProvider = new ClientCredentialProvider(confidentialClientApplication);
// Configure GraphServiceClient with provider.
GraphServiceClient graphServiceClient = new GraphServiceClient(authenticationProvider);
var users = await graphServiceClient.Users.Request().GetAsync();
I also tried getting a specific user with id and .Select("Photo") but the result is the same
var temp = await graphServiceClient.Users[user_id]
.Request()
.Select("Photo")
.GetAsync();
Maybe my implementation was wrong, any help or suggestions will be appreciated.
To get a photo as a stream you can use this code
using (var photoStream = await graphServiceClient.Users[user_id].Photo.Content
.Request()
.GetAsync())
{
// your code
}

Get my user's information using MS Graph API

I'm trying to get my information using the Graph API SDK for C# (the idea is the application running without user interaction), as shows code sample below:
IConfidentialClientApplication confidentialClientApplication = ConfidentialClientApplicationBuilder
.Create("xxx")
.WithTenantId("xxx")
.WithClientSecret("xxx")
.Build();
ClientCredentialProvider authProvider = new ClientCredentialProvider(confidentialClientApplication);
GraphServiceClient graphClient = new GraphServiceClient(authProvider);
var user = await graphClient.Me.Request()
.Select(u => new
{
u.DisplayName,
u.Mail,
u.UserPrincipalName
})
.GetAsync();
It's returning 'WaitingForActivation' on Status, I don't know where is the problem.
This is not like you think. If you call the /me endpoint, you must log in the user. It actually obtains the logged-in user information, so you cannot use the client credential flow to obtain the access token. For the client credential flow, it is usually used for server-to-server interactions that must run in the background and do not interact with the user immediately(No user logged in). For the /me endpoint, it needs to accept the user token, because it has user interaction. So, I suggest you use auth code flow to get access token,which requires you to log in to the user and obtain the authorization code, and then use the authorization code to redeem the access token.
Authorization code provider:
IConfidentialClientApplication confidentialClientApplication = ConfidentialClientApplicationBuilder
.Create(clientId)
.WithRedirectUri(redirectUri)
.WithClientSecret(clientSecret) // or .WithCertificate(certificate)
.Build();
AuthorizationCodeProvider authProvider = new AuthorizationCodeProvider(confidentialClientApplication, scopes);
By the way, if you want to get user information in a scenario where no user is logged in, you can call the /users endpoint:
IConfidentialClientApplication confidentialClientApplication = ConfidentialClientApplicationBuilder
.Create("xxx")
.WithTenantId("xxx")
.WithClientSecret("xxx")
.Build();
ClientCredentialProvider authProvider = new ClientCredentialProvider(confidentialClientApplication);
GraphServiceClient graphClient = new GraphServiceClient(authProvider);
var user = await graphClient.Users["{userId or userPrincipalName}"].Request()
.Select(u => new
{
u.DisplayName,
u.Mail,
u.UserPrincipalName
})
.GetAsync();

Permissions problems reading users from azure AD with User.ReadBasic.All?

I am trying to get azure users and i am getting permissions error, even if instead of Users i place ME, why ? shouldnt it be something that i had no need to have admin consent? Any help is appreciated!!
IConfidentialClientApplication confidentialClientApplication = ConfidentialClientApplicationBuilder
.Create("****")
.WithTenantId("****")
.WithClientSecret("***")
.Build();
ClientCredentialProvider authProvider = new ClientCredentialProvider(confidentialClientApplication);
// Create a new instance of GraphServiceClient with the authentication provider.
GraphServiceClient graphClient = new GraphServiceClient(authProvider);
var user = await graphClient.Users
.Request()
.WithScopes(graphScopes)
.Select(u => new {
u.DisplayName
})
.GetAsync();
```
For this problem it seems you do not have permission to get the users. You can refer to the document of the graph api, you need the permission shown as below:
So please go to the application which registered in your azure ad, and click "API permissions", then add the permission into it.
After add the permissions, please do not forget click "Grand admin consent for xxx".
For the question why it still failed when you change the Users to me. You use client credential flow to do authentication to request the graph api, you just provide the information of clientId, tenantId and clientSecret. So it doesn't contain a user(or yourself) information. So you can't use .Me. If you want to use .Me, you can use password grant flow. It contains the user information, so system know who is .Me.
================================Update==============================
If you want to use delegated permission(such as User.ReadBasic.All), you can't use client_credential. Now your code use client_credential flow, the access token doesn't contain user identity. I provide a sample of username/password flow(and use delegated permission User.ReadBasic.All) below for your referencef:
In the "API permissions" tab of the registered app, I just add one permission User.ReadBasic.All.
The code shown as below:
using Microsoft.Graph;
using Microsoft.Graph.Auth;
using Microsoft.Identity.Client;
using System;
using System.Security;
namespace ConsoleApp3
{
class Program
{
static async System.Threading.Tasks.Task Main(string[] args)
{
IPublicClientApplication publicClientApplication = PublicClientApplicationBuilder
.Create("<clientId>")
.WithTenantId("<tenantId>")
.Build();
string[] scopes = new string[] { "https://graph.microsoft.com/.default" };
UsernamePasswordProvider authProvider = new UsernamePasswordProvider(publicClientApplication, scopes);
GraphServiceClient graphClient = new GraphServiceClient(authProvider);
var str = "<your password>";
var password = new SecureString();
foreach (char c in str) password.AppendChar(c);
var users = await graphClient.Users.Request().WithUsernamePassword("<your account/email>", password).GetAsync();
Console.WriteLine(users.Count);
}
}
}
And before run the code, you need to do "consent to use the application" once. You need to browse the url as this: https://login.microsoftonline.com/<tenantId>/oauth2/v2.0/authorize?client_id=<clientId>&response_type=code&redirect_uri=<redirectUri>&response_mode=query&scope=openid https://graph.microsoft.com/.default&state=12345 in your browser and the page will show as:
Click "Accept", then you can run the code to get user list success.

How to fetch all users from Azure Active Directory Group using C#

We have an application developed in ASP.NET MVC. We also have Acitve Directory configured in Azure and it has some Groups into it.
Requirement is, we need to fetch all users from Azure Active Directory's Group and add a new user into it.
We are using code below and it is asking extra authentication I guess. we want to provide all authentication in code it self without giving popup wondow to authenticate. Can you please help with this
// Build a client application.
IPublicClientApplication publicClientApplication = PublicClientApplicationBuilder
.Create("clientID")
.WithTenantId("tenantId")
.Build();
// Create an authentication provider by passing in a client application and graph scopes.
DeviceCodeProvider authProvider = new DeviceCodeProvider(publicClientApplication, new[] { "User.Read" });
// Create a new instance of GraphServiceClient with the authentication provider.
GraphServiceClient graphClient = new GraphServiceClient(authProvider);
var members = await graphClient.Groups["groupId"].Members
.Request()
.GetAsync();
Above code shows a message as
"To sign in, use a web browser to open the page https://microsoft.com/devicelogin and enter the code G9277ULC9 to authenticate."
How can we provide all authentication information in code itself to avoid this step?
Updated
API permissions are as below -
Thank you in advance.
You could use the Microsoft Graph SDK to do that.
List members of a group:
GraphServiceClient graphClient = new GraphServiceClient( authProvider );
var members = await graphClient.Groups["{id}"].Members
.Request()
.GetAsync();
Add member to a group:
GraphServiceClient graphClient = new GraphServiceClient( authProvider );
var directoryObject = new DirectoryObject
{
AdditionalData = new Dictionary<string, object>()
{
{"#odata.id","https://graph.microsoft.com/v1.0/directoryObjects/{id}"}
}
};
await graphClient.Groups["{id}"].Members.References
.Request()
.AddAsync(directoryObject);
Update:
If you want a non-interactive way, you need to use the client credential flow, i.e. create the authProvider instance as Client credentials provider.
IConfidentialClientApplication confidentialClientApplication = ConfidentialClientApplicationBuilder
.Create(clientId)
.WithTenantId(tenantID)
.WithClientSecret(clientSecret)
.Build();
ClientCredentialProvider authProvider = new ClientCredentialProvider(confidentialClientApplication);

Categories