A while ago I created an application to create Outlook events in the calendars of our employees with Microsoft Graph in a background process. Now I want to expand the current application and also manage contacts.
First of all I want to created a new contact person.
Code that calls the GraphHelper.cs:
AuthenticationConfig config = AuthenticationConfig.ReadFromJsonFile("appsettings.json");
string[] scopes = new string[] { $"{config.ApiUrl}.default" };
IConfidentialClientApplication confidentialClientApplication = ConfidentialClientApplicationBuilder
.Create($"{config.ClientId}")
.WithTenantId($"{config.Tenant}")
.WithAuthority(new Uri(config.Authority))
.WithClientSecret($"{config.ClientSecret}")
.Build();
ClientCredentialProvider authenticationProvider = new ClientCredentialProvider(confidentialClientApplication, scopes[0]);
GraphHelper.Initialize(authenticationProvider);
string status = CreateContact().GetAwaiter().GetResult();
private static async Task<string> CreateContact()
{
return await GraphHelper.CreateContact("admin#premed.be");
}
GraphHelper.cs:
public static void Initialize(IAuthenticationProvider authProvider)
{
graphClient = new GraphServiceClient(authProvider);
}
public static async Task<string> CreateContact(string userName)
{
var contact = new Contact
{
GivenName = "GivenNameTest",
Surname = "SurnameTest",
EmailAddresses = new List<EmailAddress>()
{
new EmailAddress
{
Address = "GivenNameTest.SurnameTest#hotmail.com",
Name = "GivenNameTest SurnameTest Test"
}
},
BusinessPhones = new List<String>()
{
"+32489789654"
}
};
try
{
await graphClient.Users[userName]
.Contacts
.Request()
.AddAsync(contact);
return "OK";
}
catch (ServiceException ex)
{
return ex.Message;
}
}
When I run the code, I get an ErrorAccessDenied error "Access is denied. Check credentials and try again."
But I don't understand why I get the error. For the autorisation I use the same functionality I used for the calendar. Also the same tenant, client-id and client secret is used. In the Azure portal all permissions are given.
For example: creating an event is no problem.
public static async Task<string> CreateEvent(Event newEvent, string userName)
{
try
{
// POST /users/{id | userPrincipalName}/events
var returnEvent = await graphClient.Users[userName]
.Events
.Request()
.Header("Prefer", "outlook.timezone=\"Europe/Paris\"")
.AddAsync(newEvent);
return returnEvent.Id;
}
catch (ServiceException ex)
{
return ex.Message;
}
}
Can someone help me please?
Related
I have code to authenticate with EWS using oAuth working fine if I call it from winform button click, but not working if I place my code inside custom class and call it inside constructor I don't know what is the problem ?
Authenticate Code function :
public async Task<AuthenticationResult> oAuthLoginRequest()
{
System.Net.ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
var cca = ConfidentialClientApplicationBuilder
.Create(Settings.Default.appId)
.WithClientSecret(Settings.Default.clientSecret)
.WithTenantId(Settings.Default.tenantId)
.Build();
var ewsScopes = new string[] { "https://outlook.office365.com/.default" };
try
{
_authenticationResult = await cca.AcquireTokenForClient(ewsScopes)
.ExecuteAsync();
return _authenticationResult;
}
catch (Exception ex)
{
string.Format("oAuthLoginRequest: Exception= {0}", ex.Message).LogIt(TLogType.ltError);
return _authenticationResult;
}
}
Working well and I got access token :
private async void button1_Click(object sender, EventArgs e)
{
oAuthLoginRequest();
//Access Token Available here
var accessToken = _authenticationResult.AccessToken ; //Working fine
}
NOT WORKING :
public class TServiceController
{
private bool _started = false;
public bool Started { get { return _started; } }
TEWSService mailService = null;
public ExchangeService _service = null;
public AuthenticationResult _authenticationResult = null;
public DateTimeOffset TokenExpiresOn { get; set; }
public async Task<AuthenticationResult> oAuthLoginRequest()
{
System.Net.ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
var cca = ConfidentialClientApplicationBuilder
.Create(Settings.Default.appId)
.WithClientSecret(Settings.Default.clientSecret)
.WithTenantId(Settings.Default.tenantId)
.Build();
// "https://outlook.office365.com/.default" ,"https://outlook.office365.com/EWS.AccessAsUser.All" , "https://graph.microsoft.com/Mail.Send"
// "https://ps.outlook.com/full_access_as_app"
var ewsScopes = new string[] { "https://outlook.office365.com/.default" };
try
{
_authenticationResult = await cca.AcquireTokenForClient(ewsScopes).ExecuteAsync();
TokenExpiresOn = _authenticationResult.ExpiresOn;
("AccessToken:" + _authenticationResult.AccessToken).LogIt(TLogType.ltDebug);
}
catch (Exception ex)
{
string.Format("oAuthLoginRequest: Exception= {0}", ex.Message).LogIt(TLogType.ltError);
}
return _authenticationResult;
}
public TServiceController()
{
var auth = oAuthLoginRequest().Result; //STUCK HERE
"Service controller started.".LogIt();
} //end constructore
} //END CLASS
Any explanation ?
I tried two methods one of them work just fine in winform click button and other solution not working within my class constructor .
Getting error when requesting my DisplayName from Azure AD
Framework: .NET Core 5.0
Authentication: AuthorizationCodeCredential
Code:
public class GraphDataHelper
{
private readonly string[] _scopes;
private readonly string _tenantId;
private readonly string _clientId;
private readonly TokenCredentialOptions _options;
readonly AuthorizationCodeCredential _authCodeCredential;
readonly GraphServiceClient _client;
public GraphDataHelper()
{
try
{
_ = new Common();
_clientId = AppSettings.ClientId;
_tenantId = AppSettings.TenantId;
string clientSecret = "I pasted secret here";
var authorizationCode = "I pasted code here";
_scopes = new[] { "User.Read" };
_options = new TokenCredentialOptions
{
AuthorityHost = AzureAuthorityHosts.AzurePublicCloud
};
_authCodeCredential = new AuthorizationCodeCredential(
_tenantId, _clientId, clientSecret, authorizationCode, _options);
_client = new GraphServiceClient(_authCodeCredential, scopes: _scopes);
}
catch (Exception ex)
{
}
}
public async Task<string> GetFullNameAsync()
{
try
{
var user = await _client.Me
.Request()
.Select(data => data.DisplayName)
.GetAsync(); // Getting error here
return user.DisplayName;
}
catch (Exception ex)
{
return "404 Not Found";
}
}
}
Error Details:
AuthorizationCodeCredential authentication failed: A configuration issue is preventing authentication - check the error message from the server for details. You can modify the configuration in the application registration portal. See https://aka.ms/msal-net-invalid-client for details. Original exception: AADSTS500112: The reply address 'https://replyUrlNotSet' does not match the reply address 'https://localhost:44300' provided when requesting Authorization code.
Trace ID: 413467fb-04a8-4518-ab23-87b16f7ca100
Correlation ID: 8d89f9d5-dfdf-4226-8234-b1bf6b6ea9fe
Timestamp: 2022-03-25 13:52:03Z
It means that you have not specifically enabled a public client application in the Azure portal and are attempting to do an authentication flow that is only available on public clients, such as Username/Password, Integrated Windows Auth, or Device code flow.
you have to turn on the option Treat client as public client
I have a class that acts as a wrapper for the MS Graph SDK. Fairly simple purpose, inside the class there are methods for getting various data sets out of Graph for a particular user.
EDIT: this runs under the context of an application, so no user creds are ever used.
All of that part works fine, what isn't working is the DelegateAuthenticationProvider never finds the access token in the cache. Each call to a graph endpoint gets a new token, even in the same instance of the class. Within the class I'm using a singleton pattern for the GraphServiceClient.
Here is the code I'm using to handle the client:
private static GraphServiceClient _graphServiceClient;
private static AuthenticationContext _authContext;
private static readonly object _locker = new();
private GraphServiceClient GetClient(M365ServiceOptions options)
{
if (_graphServiceClient == null)
{
lock (_locker)
{
if (_graphServiceClient == null)
{
_authContext = new AuthenticationContext($"https://login.microsoftonline.com/{options.TenantId}/");
var provider = new DelegateAuthenticationProvider(async (requestMessage) =>
{
AuthenticationResult accessToken;
try
{
//Use Token from cache or refresh token
accessToken = await _authContext.AcquireTokenSilentAsync(options.GraphURL, options.ClientId);
_logger.LogDebug("Cache Hit");
}
catch (AdalSilentTokenAcquisitionException)
{
//If no cached token, get a new one
_logger.LogDebug($"Cache Miss: {_authContext.TokenCache?.Count}");
var credentials = new ClientCredential(options.ClientId, options.ClientSecret);
accessToken = _authContext.AcquireTokenAsync(options.GraphURL, credentials).Result;
}
requestMessage.Headers.Authorization = new AuthenticationHeaderValue("Bearer", accessToken.AccessToken);
});
_graphServiceClient = new GraphServiceClient(provider);
}
}
}
return _graphServiceClient;
}
While debugging it is clear the token cache has an item in it, and the details all seem to match, but no matter what, the AcquireTokenSilentAsync always throws the AdalSilentTokenAcquisitionException exception and forces it to get a new token for each call. This is impacting performance as no matter what, each call to the graph gets a new token.
Thank you for any assistance.
Please try this class. First call AuthenticationHelper.GetAuthenticatedClient() to get the a GraphServiceClient, then you this to access the user information.
public class AuthenticationHelper
{
static readonly string clientId = "";
public static string[] Scopes = { "User.Read" };
public static PublicClientApplication IdentityClientApp = new PublicClientApplication(clientId);
public static string TokenForUser = null;
public static DateTimeOffset Expiration;
private static GraphServiceClient graphClient = null;
// Get an access token for the given context and resourced. An attempt is first made to
// acquire the token silently. If that fails, then we try to acquire the token by prompting the user.
public static GraphServiceClient GetAuthenticatedClient()
{
if (graphClient == null)
{
// Create Microsoft Graph client.
try
{
graphClient = new GraphServiceClient(
"https://graph.microsoft.com/v1.0",
new DelegateAuthenticationProvider(
async (requestMessage) =>
{
var token = await GetTokenForUserAsync();
requestMessage.Headers.Authorization = new AuthenticationHeaderValue("bearer", token);
requestMessage.Headers.Add("SampleID", "MSGraphConsoleApp");
}));
return graphClient;
}
catch (Exception ex)
{
Debug.WriteLine("Could not create a graph client: " + ex.Message);
}
}
return graphClient;
}
public static async Task<string> GetTokenForUserAsync()
{
AuthenticationResult authResult;
try
{
authResult = await IdentityClientApp.AcquireTokenSilentAsync(Scopes, IdentityClientApp.GetAccountsAsync().Result.First());
TokenForUser = authResult.AccessToken;
}
catch (Exception)
{
if (TokenForUser == null || Expiration <= DateTimeOffset.UtcNow.AddMinutes(5))
{
authResult = await IdentityClientApp.AcquireTokenAsync(Scopes);
TokenForUser = authResult.AccessToken;
Expiration = authResult.ExpiresOn;
}
}
return TokenForUser;
}
public static void SignOut()
{
foreach (var user in IdentityClientApp.GetAccountsAsync().Result)
{
IdentityClientApp.RemoveAsync(user);
}
graphClient = null;
TokenForUser = null;
}
}
I am trying to build a simple console application in C# that would allow me to call in this case the beta api. I am also trying to call a method delegate method.
This is my main call
static void Main(string[] args)
{
const string clientId = <clientID>;
const string tenantId = <tenantID>;
const string clientSecret = <secretKey>;
const string tokenUrl = "https://login.microsoftonline.com/common/oauth2/v2.0/token";
List<string> scopeURL = new List<string>();
scopeURL.Add("https://graph.microsoft.com/.default");
var graphClient = DelegateApp(clientId,tenantId,clientSecret,scopeURL);
try
{
getUsersAsync(graphClient).GetAwaiter().GetResult();
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
Console.ReadLine();
}
This is how i defined my graph call
private static GraphServiceClient DelegateApp(string clientId, string tenantId, string clientSecret, List<string> scope)
{
IConfidentialClientApplication confidentialClientApplication = ConfidentialClientApplicationBuilder
.Create(clientId)
.WithClientSecret(clientSecret)
.Build();
OnBehalfOfProvider authProvider = new OnBehalfOfProvider(confidentialClientApplication, scope);
var graphClient = new GraphServiceClient(authProvider);
graphClient.BaseUrl = "https://graph.microsoft.com/beta";
return graphClient;
}
And this is what I am trying to call
public async static Task getUsersAsync(GraphServiceClient client)
{
try
{
var bookingBusiness = await client.BookingBusinesses.Request().GetAsync();
foreach (var business in bookingBusiness)
{
Console.WriteLine(business.DisplayName);
}
}
catch(Exception ex)
{
Console.WriteLine(ex.Message);
}
}
This is the exception I get.
ex.Message = "Code: generalException\r\nMessage: An error occurred sending the request.\r\n"
This happens when i call client.bookingbusinesses.request.getasync();
I am trying to execute below code:
using System;
using Microsoft.Graph;
using Microsoft.Graph.Auth;
using Microsoft.Graph.Core;
using Microsoft.Identity.Client;
using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
using System.Linq;
namespace AzureActiveDirectory
{
class Program
{
//3. Copy the following code as is to your application.
// Register your app on the Azure AD application registration portal
// Remember to :
// 1. Check the redirect uri starting with "msal"
// 2. Set "Treat application as public client" to "Yes"
const string clientId = "XXXXXX";
const string tenant = "XXXXX";
const string redirectUri = "http://localhost";
// Change the following between each call to create/update user if not deleting the user
private static string givenName = "test99";
private static string surname = "user99";
private static void Main(string[] args)
{
// Initialize and prepare MSAL
//What we want to do
// string[] scopes = new string[] { "user.read", "user.readwrite.all" };
string[] scopes = new string[] { "user.read" };
IPublicClientApplication app = PublicClientApplicationBuilder.Create(clientId)
.WithAuthority(new Uri($"https://login.microsoftonline.com/{tenant}"))
.WithRedirectUri(redirectUri)
.Build();
// Initialize the Graph SDK authentication provider
InteractiveAuthenticationProvider authenticationProvider = new InteractiveAuthenticationProvider(app, scopes);
GraphServiceClient graphServiceClient = new GraphServiceClient(authenticationProvider);
// Get information from Graph about the currently signed-In user
Console.WriteLine("--Fetching details of the currently signed-in user--");
GetMeAsync(graphServiceClient).GetAwaiter().GetResult();
Console.WriteLine("---------");
// Create a new user
Console.WriteLine($"--Creating a new user in the tenant '{tenant}'--");
User newUser = CreateUserAsync(graphServiceClient).Result;
PrintUserDetails(newUser);
Console.WriteLine("---------");
// Update an existing user
if (newUser != null)
{
Console.WriteLine("--Updating the detail of an existing user--");
User updatedUser = UpdateUserAsync(graphServiceClient, userId: newUser.Id, jobTitle: "Program Manager").Result;
PrintUserDetails(updatedUser);
Console.WriteLine("---------");
}
// List existing users
Console.WriteLine("--Listing all users in the tenant--");
List<User> users = GetUsersAsync(graphServiceClient).Result;
users.ForEach(u => PrintUserDetails(u));
Console.WriteLine("---------");
// Delete this user
Console.WriteLine("--Deleting a user in the tenant--");
if (newUser != null)
{
DeleteUserAsync(graphServiceClient, newUser?.Id).GetAwaiter().GetResult(); ;
}
Console.WriteLine("---------");
// List existing users after deletion
Console.WriteLine("--Listing all users in the tenant after deleting a user.--");
users = GetUsersAsync(graphServiceClient).Result;
users.ForEach(u => PrintUserDetails(u));
Console.WriteLine("---------");
Console.WriteLine("Press any key to exit");
Console.ReadKey();
}
private static async Task GetMeAsync(GraphServiceClient graphServiceClient)
{
// Call /me Api
var me = await graphServiceClient.Me.Request().GetAsync();
Console.WriteLine($"Display Name from /me->{me.DisplayName}");
var directreports = await graphServiceClient.Me.DirectReports.Request().GetAsync();
foreach (User user in directreports.CurrentPage)
{
Console.WriteLine($"Report's Display Name ->{user.DisplayName}");
}
}
private static async Task<User> CreateUserAsync(GraphServiceClient graphServiceClient)
{
User newUserObject = null;
string displayname = $"{givenName} {surname}";
string mailNickName = $"{givenName}{surname}";
string upn = $"{mailNickName}{tenant}";
string password = "p#$$w0rd!";
try
{
newUserObject = await graphServiceClient.Users.Request().AddAsync(new User
{
AccountEnabled = true,
DisplayName = displayname,
MailNickname = mailNickName,
GivenName = givenName,
Surname = surname,
PasswordProfile = new PasswordProfile
{
Password = password
},
UserPrincipalName = upn
});
}
catch (ServiceException e)
{
Console.WriteLine("We could not add a new user: " + e.Error.Message);
return null;
}
return newUserObject;
}
private static void PrintUserDetails(User user)
{
if (user != null)
{
Console.WriteLine($"DisplayName-{user.DisplayName}, MailNickname- {user.MailNickname}, GivenName-{user.GivenName}, Surname-{user.Surname}, Upn-{user.UserPrincipalName}, JobTitle-{user.JobTitle}, Id-{user.Id}");
}
else
{
Console.WriteLine("The provided User is null!");
}
}
private static async Task<User> UpdateUserAsync(GraphServiceClient graphServiceClient, string userId, string jobTitle)
{
User updatedUser = null;
try
{
// Update the user.
updatedUser = await graphServiceClient.Users[userId].Request().UpdateAsync(new User
{
JobTitle = jobTitle
});
}
catch (ServiceException e)
{
Console.WriteLine($"We could not update details of the user with Id {userId}: " + $"{e}");
}
return updatedUser;
}
private static async Task<List<User>> GetUsersAsync(GraphServiceClient graphServiceClient)
{
List<User> allUsers = new List<User>();
try
{
IGraphServiceUsersCollectionPage users = await graphServiceClient.Users.Request().Top(5).GetAsync();
// When paginating
//while(users.NextPageRequest != null)
//{
// users = await users.NextPageRequest.GetAsync();
//}
if (users?.CurrentPage.Count > 0)
{
foreach (User user in users)
{
allUsers.Add(user);
}
}
}
catch (ServiceException e)
{
Console.WriteLine("We could not retrieve the user's list: " + $"{e}");
return null;
}
return allUsers;
}
private static async Task DeleteUserAsync(GraphServiceClient graphServiceClient, string userId)
{
try
{
await graphServiceClient.Users[userId].Request().DeleteAsync();
}
catch (ServiceException e)
{
Console.WriteLine($"We could not delete the user with Id-{userId}: " + $"{e}");
}
}
}
}
After providing access to app during execution , i got below exception:
Microsoft.Graph.ServiceException: 'Code: generalException
Message: An error occurred sending the request.
'
NullReferenceException: Object reference not set to an instance of an object.
at GetMeAsync(graphServiceClient).GetAwaiter().GetResult();
Please help.
Shortest code to reproduce problem.
using System;
using Microsoft.Graph;
using Microsoft.Graph.Auth;
using Microsoft.Graph.Core;
using Microsoft.Identity.Client;
using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
using System.Linq;
namespace AzureActiveDirectory
{
class Program
{
//3. Copy the following code as is to your application.
// Register your app on the Azure AD application registration portal
// Remember to :
// 1. Check the redirect uri starting with "msal"
// 2. Set "Treat application as public client" to "Yes"
const string clientId = "XXXXXX";
const string tenant = "XXXXX";
const string redirectUri = "http://localhost";
// Change the following between each call to create/update user if not deleting the user
private static string givenName = "test99";
private static string surname = "user99";
private static void Main(string[] args)
{
// Initialize and prepare MSAL
//What we want to do
// string[] scopes = new string[] { "user.read", "user.readwrite.all" };
string[] scopes = new string[] { "user.read" };
IPublicClientApplication app = PublicClientApplicationBuilder.Create(clientId)
.WithAuthority(new Uri($"https://login.microsoftonline.com/{tenant}"))
.WithRedirectUri(redirectUri)
.Build();
// Initialize the Graph SDK authentication provider
InteractiveAuthenticationProvider authenticationProvider = new InteractiveAuthenticationProvider(app, scopes);
GraphServiceClient graphServiceClient = new GraphServiceClient(authenticationProvider);
// Get information from Graph about the currently signed-In user
Console.WriteLine("--Fetching details of the currently signed-in user--");
GetMeAsync(graphServiceClient).GetAwaiter().GetResult();
Console.WriteLine("---------");
// Create a new user
Console.WriteLine("---------");
Console.WriteLine("Press any key to exit");
Console.ReadKey();
}
private static async Task GetMeAsync(GraphServiceClient graphServiceClient)
{
// Call /me Api
var me = await graphServiceClient.Me.Request().GetAsync();
Console.WriteLine($"Display Name from /me->{me.DisplayName}");
var directreports = await graphServiceClient.Me.DirectReports.Request().GetAsync();
foreach (User user in directreports.CurrentPage)
{
Console.WriteLine($"Report's Display Name ->{user.DisplayName}");
}
}
}
}
The interactive flow is used by mobile applications (Xamarin and UWP) and desktops applications to call Microsoft Graph in the name of a user. Please go to Azure portal to check if you have added the correct platform.
The code works fine.