I'm new to graph API. I'm creating an app that can access user email using Graph API. I've been using the device token method which is used here.
The above code is working with my application. But I want to automate this process. I found some help from Microsoft documents here, from the sample codes here, and from this SO post.
But I can't get my code to obtain the token automatically. I thought it was an API permissions issue so I set them up like below.
I was able to get the AcquireTokenInteractive but AcquireTokenSilent is not working.
UPDATE: I've managed to get an exception the message is this "No account or login hint was passed to the AcquireTokenSilent call.". Also found that variable FirstAccount seems to be empty.
Below is my code for obtaining the token.
using Azure.Core;
using Azure.Identity;
using Microsoft.Graph;
using Microsoft.Identity.Client;
using System.Net.Http.Headers;
namespace DEA
{
public class GraphHelper
{
private static GraphServiceClient? graphClient;
private static AuthenticationResult token;
private static IPublicClientApplication? application;
public static async void InitializeAuto(string ClientID, string InstanceID, string TenantID, string GraphUrl, string[] scopes)
{
string auth = string.Concat(InstanceID, TenantID);
application = PublicClientApplicationBuilder.Create(ClientID)
.WithAuthority(auth)
.WithDefaultRedirectUri()
.Build();
try
{
var accounts = await application.GetAccountsAsync();
graphClient = new GraphServiceClient(GraphUrl,
new DelegateAuthenticationProvider(async (requestMessage) =>
{
token = await application.AcquireTokenInteractive(scopes, accounts.FirstOrDefault()).ExecuteAsync();
requestMessage.Headers.Authorization = new AuthenticationHeaderValue("bearer", token.AccessToken);
}
));
}
catch (Exception ex)
{
Console.WriteLine("Exception thrown: {0}", ex.Message);
}
}
}
The above function is called from my "Program.cs".
using DEA;
using Microsoft.Extensions.Configuration;
var appConfig = LoadAppSettings();
if (appConfig == null)
{
Console.WriteLine("Set the graph API pemissions. Using dotnet user-secrets set .... They don't exsits in this computer.");
return;
}
var appId = appConfig["appId"];
var TenantId = appConfig["TenantId"];
var Instance = appConfig["Instance"];
var GraphApiUrl = appConfig["GraphApiUrl"];
string[] scopes = new string[] { "https://graph.microsoft.com/.default" };
GraphHelper.InitializeAuto(appId, Instance, TenantId, GraphApiUrl, scopes);
static IConfigurationRoot? LoadAppSettings()
{
var appConfig = new ConfigurationBuilder()
.AddUserSecrets<Program>()
.Build();
// Check for required settings
if (string.IsNullOrEmpty(appConfig["appId"]) ||
string.IsNullOrEmpty(appConfig["scopes"]))
{
return null;
}
return appConfig;
}
I don't know what I'm doing wrong. I used a try ... catch but still nothing. The only error I get is an exception thrown by the graph client call when I press option 2 of my app.
Can someone please help me to solve this, please?
After reading #Marc and #Jeremy Lakeman I rewrote the code using IConfidentialClientApplication. And used this as a guide from Microsoft guides.
And came up with the below code and it works now.
public static async void InitializeAuto(string ClientID, string InstanceID, string TenantID, string GraphUrl, string ClientSecret, string[] scopes)
{
string auth = string.Concat(InstanceID, TenantID);
application = ConfidentialClientApplicationBuilder.Create(ClientID)
.WithClientSecret(ClientSecret)
.WithAuthority(new Uri(auth))
.Build();
Console.WriteLine("Auth: {0}", auth);
Console.WriteLine("Client Secrets: {0}", ClientSecret);
try
{
graphClient = new GraphServiceClient(GraphUrl,
new DelegateAuthenticationProvider(async (requestMessage) =>
{
AuthToken = await application.AcquireTokenForClient(scopes).ExecuteAsync();
requestMessage.Headers.Authorization = new AuthenticationHeaderValue("bearer", AuthToken.AccessToken);
}
));
/*result = await application.AcquireTokenForClient(scopes)
.ExecuteAsync();
Console.WriteLine("Token: {0}", result.AccessToken);*/
}
catch (MsalUiRequiredException ex)
{
// The application doesn't have sufficient permissions.
// - Did you declare enough app permissions during app creation?
// - Did the tenant admin grant permissions to the application?
Console.WriteLine("Exception: {0}", ex.Message);
}
catch (MsalServiceException ex) when (ex.Message.Contains("AADSTS70011"))
{
// Invalid scope. The scope has to be in the form "https://resourceurl/.default"
// Mitigation: Change the scope to be as expected.
Console.WriteLine("Scope provided is not supported");
}
}
Could you please do a fresh start and try again with AcquireTokenSilent(), lets see if you got any error .
{
var accounts = await application.GetAccountsAsync();
result = await application.AcquireTokenSilent(scopes,
accounts.FirstOrDefault())
.ExecuteAsync();
}
catch (MsalUiRequiredException ex)
{
result = await application.AcquireTokenInteractive(scopes)
.WithClaims(ex.Claims)
.ExecuteAsync();
}
doc - https://github.com/Azure-Samples/ms-identity-dotnet-desktop-tutorial/tree/master/1-Calling-MSGraph/1-1-AzureAD
Related
I'm struggeling to understand how to use SharePoint CSOM to add a user as Site Collection Admin.
So far this code works for a Global Admin to add a user as Site Colleciton Admin, eventhough the Global Admin is not included as Site Admin.
I've tried to run the code as normal user which is only Site Collection Admin, to add another user as Site Colleciton Admin. But then I get some errors:
If I use the SharePoint Admin URL to get the Access Token, then the code crash on row #48 as 401 "Unauthorized"
If I use the Site Collection URL to get the Access Token, I get error when trying to get the acces token saying that Site is doesn't exist on the environment.
If I use the root site URL ("https://domain-admin.sharepoint.com/) to get the Access Token, then the code crash on row #51 as 401 "Unauthorized".
I'm using the PnP.PowerShell code as reference: https://github.com/pnp/powershell/blob/dev/src/Commands/Admin/SetTenantSite.cs#L547-L574
And my process is pretty much the same as here: MSAL AD token not valid with SharePoint Online CSOM
But I don't have clear if it's a issue of access token or the CSOM commands I use.
Does anyone has any idea how to move forward?
btw, I guess if I use the Global Admin account I only need to use tenant.SetSiteAdmin(siteCollection, userEmail, true);. I read somewhere that even for global admin I need EnsureUser(userEmail);, but so far the code seems working without it.
using Microsoft.Identity.Client;
using Microsoft.SharePoint.Client;
namespace ScriptTester
{
internal class Program
{
static async Task Main(string[] args)
{
await AddUserAdmin();
}
public static async Task AddUserAdmin()
{
string siteAdmin = "https://domain-admin.sharepoint.com/";
string siteRoot = "https://domain.sharepoint.com/";
string siteCollection = "https://domain.sharepoint.com/sites/SiteName/";
string userEmail = "_email";
string accessToken = await GetAccessToken(siteRoot);
using (var context = new Microsoft.SharePoint.Client.ClientContext(siteRoot))
{
context.ExecutingWebRequest += (sender, e) =>
{
e.WebRequestExecutor.RequestHeaders["Authorization"] = "Bearer " + accessToken;
};
var tenant = new Microsoft.Online.SharePoint.TenantAdministration.Tenant(context);
try
{
addLog("Try using tenant context");
tenant.SetSiteAdmin(siteCollection, userEmail, true);
tenant.Context.ExecuteQueryRetry();
}
catch (Exception ex)
{
addLog("Failed using Tenant context");
addLog(ex.Message);
using (var site = tenant.Context.Clone(siteCollection))
{
var user = site.Web.EnsureUser(userEmail);
user.Update();
user.IsSiteAdmin= true;
site.Load(user);
site.ExecuteQueryRetry();
tenant.SetSiteAdmin(siteCollection, userEmail, true);
tenant.Context.ExecuteQueryRetry();
}
}
}
}
public static async Task<string> GetAccessToken(string siteUrl)
{
string tenantId = "xxxx-xxxx-xxxx-xxxx-xxx";
string clientId = "xxxx-xxxx-xxxx-xxxx-xxx";
Uri authority = new Uri($"https://login.microsoftonline.com/{tenantId}");
string redirectUri = "http://localhost";
string defaultPermissions = siteUrl + "/.default";
string[] scopes = new string[] { defaultPermissions };
var app = PublicClientApplicationBuilder.Create(clientId)
.WithAuthority(authority)
.WithRedirectUri(redirectUri)
.Build();
AuthenticationResult result;
result = await app.AcquireTokenInteractive(scopes)
.WithUseEmbeddedWebView(false)
.ExecuteAsync();
return result.AccessToken;
}
}
}
I will recommend you to use PnP Core component to access site collection and add admin. Please refer to the following code
string siteUrl = "https://xxx.sharepoint.com/";
string userName = "xxxx#xxx.onmicrosoft.com";
string password = "*******";
AuthenticationManager authManager = new AuthenticationManager();
try
{
using (var clientContext = authManager.GetSharePointOnlineAuthenticatedContextTenant(siteUrl, userName, password))
{
List<UserEntity> admins = new List<UserEntity>();
UserEntity admin = new UserEntity();
admin.LoginName = "nirmal";
admins.Add(admin);
clientContext.Site.RootWeb.AddAdministrators(admins, true);
Console.WriteLine("User added as Site Collection Admin");
Console.ReadKey();
}
}
catch (Exception ex)
{
Console.WriteLine("Error Message: " + ex.Message);
Console.ReadKey();
}
I am using a windows application to upload and download a file from onedrive api.
Code to retrieve token (This code is directly downloaded from azure portal after creating an app registration)
string graphAPIEndpoint = "https://graph.microsoft.com/v1.0/me";
string[] scopes = new string[] { "user.read" };
AuthenticationResult authResult = null;
var app = App.PublicClientApp;
ResultText.Text = string.Empty;
TokenInfoText.Text = string.Empty;
var accounts = await app.GetAccountsAsync();
var firstAccount = accounts.FirstOrDefault();
try
{
authResult = await app.AcquireTokenSilent(scopes, firstAccount)
.ExecuteAsync();
}
catch (MsalUiRequiredException ex)
{
System.Diagnostics.Debug.WriteLine($"MsalUiRequiredException: {ex.Message}");
try
{
authResult = await app.AcquireTokenInteractive(scopes)
.WithAccount(firstAccount)
.WithParentActivityOrWindow(new WindowInteropHelper(this).Handle) // optional, used to center the browser on the window
.WithPrompt(Prompt.SelectAccount)
.ExecuteAsync();
}
catch (MsalException msalex)
{
ResultText.Text = $"Error Acquiring Token:{System.Environment.NewLine}{msalex}";
}
}
This is the code to get the download url of an item from onedrive api
string url="https://graph.microsoft.com/v1.0/me/drive/root:/Qwerty/test.txt";
string token=authResult.AccessToken;
var httpClient = new System.Net.Http.HttpClient();
System.Net.Http.HttpResponseMessage response;
try
{
var request = new System.Net.Http.HttpRequestMessage(System.Net.Http.HttpMethod.Get, url);
//Add the token in Authorization header
request.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token);
response = await httpClient.SendAsync(request);
var content = await response.Content.ReadAsStringAsync();
if (response.StatusCode == System.Net.HttpStatusCode.OK)
{
Response cls = new Response();
cls.Success = "TRUE";
cls.Method = "GetAllFiles";
cls.Data = content;
return cls;
}
else
{
Response cls = new Response();
cls.Success = "FALSE";
cls.Method = "GetAllFiles";
cls.Data = content;
return cls;
}
Im getting this error "Must be authenticated to use '/drive' syntax". this app works with one of my personal app registration . but when i use the below app registration its strating to show this error. i followed the exact same steps in creating the app registration i dnt knw why this error.
client id with error: 463921cd-72a3-495d-847e-259b99dda89e
Please help me
This is the sreenshot
If you would like to download the contents of a DriveItem, you must add one of the following permissions to call this API. And you need to add the delegated permission when using /me. The scope in your code is also changed with permission.
Then you could refer to the code sample:
C# with graph SDK: https://stackoverflow.com/a/63806689/13308381
Try to follow these steps in Postman:
1.Request an authorization code
GET https://login.microsoftonline.com/{tenant}/oauth2/v2.0/authorize?client_id={client_id}&scope={scope}
&response_type=code&redirect_uri={redirect_uri}
2.Request an access token
POST https://login.microsoftonline.com/{tenant}/oauth2/v2.0/token
client_id={client_id}&scope={scope}&redirect_uri={redirect_uri}&client_secret={client_secret}
&code={code}&grant_type=authorization_code
3.Call Microsoft Graph API
GET /me/drive/root:/{item-path}:/content
I have found out the issue. issue was when creating an access token we need specify scopes like(Files.Read,Files.ReadWrite,etc (add whatever we need)) and then use the same token for downloading's a file and it was ok
I am trying to successfully construct the graph client with delegate permissions in .net 4.5 framework. I have literally tried all the ways I have found on the internet, and all of them are not working.
I have my application registered in azure ad with delegate permissions, but have not had any luck constructing it. Here is the latest that I have tried:
//private string[] _scopes = new string[] { "https://graph.microsoft.com/.default" };
//private string[] _scopes = new string[] { "https://graph.microsoft.com/User.ReadWrite.All" };
//private readonly string[] _scopes = new string[] { "User.Read" };
private readonly string[] _scopes = new string[] { "User.Read.All" };
public GraphServiceClient GetAuthenticatedGraphClient(ClaimsIdentity userIdentity) =>
new GraphServiceClient(new DelegateAuthenticationProvider(
async requestMessage =>
{
// Passing tenant ID to the sample auth provider to use as a cache key
var accessToken = await _authProvider.GetUserAccessTokenAsync();
// Append the access token to the request
requestMessage.Headers.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
}));
}
public async Task<string> GetUserAccessTokenAsync()
{
try
{
var result = await _app.AcquireTokenForClient(_scopes)
.ExecuteAsync();
return result.AccessToken;
}
// Unable to retrieve the access token silently.
catch (Exception ex)
{
throw new ServiceException(new Error
{
Code = GraphErrorCode.AuthenticationFailure.ToString(),
Message = "Caller needs to authenticate. Unable to retrieve the access token silently."
});
}
}
Can I get a full code snipet to construct the Microsoft graph client properly for delegate permission for a full framework web application?
I found a valid example finally that had the proper setup for delegate permissions for microsoft graph:
https://developer.microsoft.com/en-us/graph/quick-start?appID=383cb18f-fedb-4165-a82f-e9ed354d124c&appName=My%20ASP.NET%20App&redirectUrl=https://localhost:44375/&platform=option-dotnet
We implement to get the phone numbers being used in MFA of the signed-in user. We use password grant flow where we have a service account(with Global admin role) that will call MS Graph API on behalf of the user.
We are able to get the access token. However, when making a call to MS Graph encounters the error below.
Error:
ServiceException: Code: InvalidAuthenticationToken
Message: CompactToken parsing failed with error code: 80049217
MS Graph API call:
MicrosoftGraphClientSDK client = new MicrosoftGraphClientSDK();
var graphClient = client.GetAuthenticatedClient();
// Error encountered here:
var phones = await graphClient.Me.Authentication.PhoneMethods[{objectiD of the user}].Request().GetAsync();
This is how we get the access token in GetAuthenticatedClient
public MicrosoftGraphClientSDK()
{
_app_public = PublicClientApplicationBuilder.Create(clientID)
.WithAuthority("https://login.microsoftonline.com/{tenantID}")
.Build();
}
public Beta.GraphServiceClient GetAuthenticatedClient()
{
var accessToken = GetUserAccessTokenAsync();
var delegateAuthProvider = new DelegateAuthenticationProvider((requestMessage) =>
{
requestMessage.Headers.Authorization = new AuthenticationHeaderValue("Bearer", accessToken.ToString());
return Task.FromResult(0);
});
_graphClient = new Beta.GraphServiceClient(delegateAuthProvider);
return _graphClient;
}
public async Task<string> GetUserAccessTokenAsync()
{
AuthenticationResult result;
var accounts = await _app_public.GetAccountsAsync();
if (accounts.Any())
{
result = await _app_public.AcquireTokenSilent(_scopes, accounts.FirstOrDefault())
.ExecuteAsync();
}
else
{
SecureString password = new SecureString();
foreach (char c in pass)
password.AppendChar(c);
result = await _app_public
.AcquireTokenByUsernamePassword(_scopes, username, password)
.ExecuteAsync();
}
return result.AccessToken;
}
I have search online about the error but could not get figure out the solution.
I appreciate your response. Thanks.
I need to collect Microsoft Teams data from a C# console application using Microsoft Graph.
I am using ADAL and cloned the authentication methods from the https://github.com/microsoftgraph/console-csharp-connect-sample sample.
The only difference is that I am using an HttpClient client and not a GraphServiceClient that does not implement Teams objects.
The list of required permissions have been determined with a Fiddler trace of a request made with Graph Explorer (no need for User.Read.All or User.Write.All) :
User.Read, Mail.Send, Files.ReadWrite, User.ReadWrite, User.ReadBasic.All, Sites.ReadWrite.All, Contacts.ReadWrite, People.Read, Notes.ReadWrite.All, Tasks.ReadWrite, Mail.ReadWrite, Files.ReadWrite.All, Calendars.ReadWrite
Everything works fine with my console app as long as I am not requesting any Teams resource:
I can get the list of groups "that are Teams" with the following
request: https://graph.microsoft.com/beta/groups?$filter=resourceProvisioningOptions/any(v:v eq 'Team')&$select=id,displayname,groupTypes,resourceBehaviorOptions,resourceProvisioningOptions
I can successfully get the group details with: https://graph.microsoft.com/beta/groups/{groupId}
But when I try to get the team view of that group (which I am member of) it fails with HTTP
403-Unautorized:
https://graph.microsoft.com/beta/groups/{groupId}/team
Very
frustrating to see that this last step is working well from the
Graph Explorer
My problem is very similiar with Access Denied when querying Teams in Microsoft Graph but in my case I am member of the teams I am trying to access and the request works with Graph Explorer.
Code details:
class AuthenticationHelper
{
// The Client ID is used by the application to uniquely identify itself to the v2.0 authentication endpoint.
static string clientId = Constants.ClientId;
// The list of required permissions have been determined with a Fiddler trace of a request made with Graph Explorer
// e.g. below are the permissions Grap Explorer requires to run the sample requests
public static string[] Scopes = {
"User.Read"
, "Mail.Send"
, "Files.ReadWrite"
, "User.ReadWrite"
, "User.ReadBasic.All"
, "Sites.ReadWrite.All"
, "Contacts.ReadWrite"
, "People.Read"
, "Notes.ReadWrite.All"
, "Tasks.ReadWrite"
, "Mail.ReadWrite"
, "Files.ReadWrite.All"
, "Calendars.ReadWrite"
};
public static PublicClientApplication IdentityClientApp = new PublicClientApplication(clientId);
public static string UserToken = null;
public static DateTimeOffset Expiration;
//-----------------------------------------------------------------------------------------------------------------
public static async Task<HttpClient> GetAuthenticatedHttpClient()
{
HttpClient client = null;
try
{
client= new HttpClient(new HttpClientHandler { UseCookies = true });
var token = await GetTokenForUserAsync();
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);
// This header has been added to identify our sample in the Microsoft Graph service. If extracting this code for your project please remove.
client.DefaultRequestHeaders.Add("SampleID", "TestCSharp-AzureToken");
return client;
}
catch (Exception ex)
{
Debug.WriteLine("Could not create a graph client: " + ex.Message);
}
return client;
}
//-----------------------------------------------------------------------------------------------------------------
public static async Task<string> GetTokenForUserAsync()
{
AuthenticationResult authResult;
try
{
IEnumerable<IAccount> accounts = await IdentityClientApp.GetAccountsAsync();
IAccount firstAccount = accounts.FirstOrDefault();
authResult = await IdentityClientApp.AcquireTokenSilentAsync(Scopes, firstAccount);
UserToken = authResult.AccessToken;
}
catch (Exception)
{
if (UserToken == null || Expiration <= DateTimeOffset.UtcNow.AddMinutes(5))
{
authResult = await IdentityClientApp.AcquireTokenAsync(Scopes );
UserToken = authResult.AccessToken;
Expiration = authResult.ExpiresOn;
}
}
return UserToken;
}
}
//----------------------------------------------------
// Console entry point
class Program
{
//public static GraphServiceClient client;
public static HttpClient _client;
static async Task<string> GetHttpResponse(string url)
{
string responseBody = null;
_client = await AuthenticationHelper.GetAuthenticatedHttpClient();
HttpResponseMessage response = await _client.GetAsync(url);
response.EnsureSuccessStatusCode();
using (HttpContent content = response.Content)
{
responseBody = await response.Content.ReadAsStringAsync();
logger.Trace(responseBody);
}
return responseBody;
}
static void Main(string[] args)
{
// call 1 is working: list groups that "are Microsoft Teams"
string s;
string url = "https://graph.microsoft.com/beta/groups?$filter=resourceProvisioningOptions/any(v:v eq 'Team')&$select=id,displayname,groupTypes,resourceBehaviorOptions,resourceProvisioningOptions";
s = await GetHttpResponse(url);
Console.WriteLine(s);
// call 2 is working: Display details of one of these groups
Console.Write($"Enter the id of the group/teams to search for: ");
string groupId = Console.ReadLine().Trim().ToLower();
url = $"https://graph.microsoft.com/beta/groups/{groupId}";
s = await GetHttpResponse(url);
Console.WriteLine(s);
// call 3 is failing: Display the team view of this groups
url = url + "/team";
s = await GetHttpResponse(url);
Console.WriteLine(s);
}
}
You're missing a scope. You need to have Group.Read.All in order to read a Group or Team.