Access token validation failure Microsoft Graph API - c#

I am building a console application in C#. I want to make some calls to Microsoft Graph API to access and edit some Excel files in my SharePoint so I can automate some processes in my Organization.
The logic of the app is simple.
I call Azure Active Directory to authenticate this console application using the clients credential flow which means we will provide a clientsID and AppKey. I took the clientsID and AppKey from Azure Active Directory > App Registrations.
Then I want to receive the access token and use this to make a GET Request to the Microsoft Graph API.
E.g https://graph.microsoft.com/v1.0/me/
But then response I get is this:
{
"error": {
"code": "InvalidAuthenticationToken",
"message": "Access token validation failure. Invalid audience.",
"innerError": {
"request-id": "0a3ec**************",
"date": "2019-10-15T13:54:33"
}
}
}
Below you will find the full code of my application with the two methods of getting the access token and calling the Graph API:
using Microsoft.IdentityModel.Clients.ActiveDirectory;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IdentityModel.Tokens;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Threading.Tasks;
using AuthenticationContext = Microsoft.IdentityModel.Clients.ActiveDirectory.AuthenticationContext;
namespace Project_Budget
{
class Program
{
private const string clientId = "14f1****************";
private const string aadInstance = "https://login.microsoftonline.com/{0}";
private const string tenant = "******.onmicrosoft.com";
private const string resource = "https://graph.windows.net";
private const string appKey = "IKV***********";
static string authority = String.Format(CultureInfo.InvariantCulture, aadInstance, tenant);
private static HttpClient httpClient = new HttpClient();
private static AuthenticationContext context = null;
private static ClientCredential credential = null;
static void Main(string[] args)
{
context = new AuthenticationContext(authority);
credential = new ClientCredential(clientId,appKey);
Task<string> token = GetToken();
token.Wait();
//Console.WriteLine(token.Result + "\n");
Task<string> graphCall = GetExcelFile(token.Result);
graphCall.Wait();
Console.WriteLine(graphCall.Result + "\n");
Console.ReadLine();
}
private static async Task<string> GetExcelFile(string result)
{
string apiJsonResult = null;
var apiCallString = "https://graph.microsoft.com/v1.0/me/";
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", result);
var getResult = await httpClient.GetAsync(apiCallString);
if (getResult.Content != null)
{
apiJsonResult = await getResult.Content.ReadAsStringAsync();
}
return apiJsonResult;
}
private static async Task<string> GetToken()
{
AuthenticationResult result = null;
string token = null;
result = await context.AcquireTokenAsync(resource, credential); //authentication context object
token = result.AccessToken;
return token;
}
}
}
I have given all the access required for the app to run. Also I run the query on Graph Explorer and runs properly.
Why do I get this error on the console application?

Ideally, the resource should actually be
private const string resource = "https://graph.microsoft.com";
But you still need to select the scopes that you want to target in your application.
The way you are doing it at the moment does seem to acquire/set the relevant scopes which is done for you by Graph Explorer.
I would suggest following this quick start tutorial on how to build a dot net core console app and you should be up and running in no time.
It uses the MSAL library which works better than the ADAL library you are using in your scenario.
https://learn.microsoft.com/en-us/graph/tutorials/dotnet-core

I think the problem is with resource value you're specifying in your code.
Current Code: (This resource value https://graph.windows.net corresponds to Azure AD Graph API which is older API)
private const string resource = "https://graph.windows.net";
Try changing this to: (This resource value https://graph.microsoft.com corresponds to newer Microsoft Graph API which is the one you're calling in code that comes later var apiCallString = "https://graph.microsoft.com/v1.0/me/";)
private const string resource = "https://graph.microsoft.com";

Related

Bring up Sharepoint prompt in .NET5

We used to be able to log into SharePoint Online using the SharePointOnlineCredentials class eg here.
This class is not available in .NET5 and I don't have the necessary Azure permissions to configure the application in Azure AD as described here
I'd like a C# program bring up a prompt, so the user can log into Sharepoint manually. Once the user has done this the program will do what it needs to do. Is there some C# code that will bring up the Sharepoint online login prompt?
I've downloaded Pnp and CSOM. This code contains the following errors.
The name 'GetSecureString' does not exist in the current context
No overload for method 'GetContext' takes 3 arguments
'Program.GetContext(Uri, string, SecureString)': not all code paths return a value
The name 'context' does not exist in the current context
using System;
using System.Security;
using Microsoft.SharePoint.Client;
using System.Threading.Tasks;
using PnP.Framework;
namespace ConsoleApplication1
{
class Program
{
public static async Task Main(string[] args)
{
Uri site = new Uri("https://contoso.sharepoint.com/sites/siteA");
string user = "joe.doe#contoso.onmicrosoft.com";
SecureString password = GetSecureString($"Password for {user}");
// Note: The PnP Sites Core AuthenticationManager class also supports this
using (var authenticationManager = new AuthenticationManager())
using (var context = authenticationManager.GetContext(site, user, password))
{
context.Load(context.Web, p => p.Title);
await context.ExecuteQueryAsync();
Console.WriteLine($"Title: {context.Web.Title}");
}
}
public ClientContext GetContext(Uri web, string userPrincipalName,
SecureString userPassword)
{
context.ExecutingWebRequest += (sender, e) =>
{
// Get an access token using your preferred approach
string accessToken = MyCodeToGetAnAccessToken(new Uri($"
{web.Scheme}://{web.DnsSafeHost}"), userPrincipalName, new
System.Net.NetworkCredential(string.Empty, userPassword).Password);
// Insert the access token in the request
e.WebRequestExecutor.RequestHeaders["Authorization"] =
"Bearer " + accessToken;
};
}
}
}

Invalid domain name Azure Active directory issue and Migration to Microsoft Graph C# application

I am trying to run below code to get the user details as response from Azure Active Directory :
using Microsoft.IdentityModel.Clients.ActiveDirectory;
using System;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading.Tasks;
namespace AADConsole2
{
class Program
{
private const string aadInstance = "https://login.microsoftonline.com/{0}";
private const string resource= "https://graph.windows.net";
private const string GraphServiceObjectId = "XXX";
private const string TenantId = "XXXXX";
private const string tenant = "company.onmicrosoft.com";
private const string ClientId = "XXXX";
private static string appKey= "XXXXXXXXXXXXXXXX";
static string authority = String.Format(System.Globalization.CultureInfo.InvariantCulture, aadInstance, tenant);
private static HttpClient httpclient = new HttpClient();
private static AuthenticationContext context = null;
private static ClientCredential credential = null;
static void Main(string[] args)
{
context = new AuthenticationContext(authority);
credential = new ClientCredential(ClientId, appKey);
Task<string> token = GetToken();
token.Wait();
Console.WriteLine(token.Result);
Task<string> users = GetUsers(token.Result);
users.Wait();
Console.WriteLine(users.Result);
Console.ReadLine();
}
private static async Task<string> GetUsers(string result) {
//throw new NotImplementedException();
string users = null;
string queryString = "test";
var uri = "https://graph.windows.net/{your_tenant_name}.onmicrosoft.com/users?api-version=1.6";
httpclient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", result);
var getResult = await httpclient.GetAsync(uri);
if(getResult.Content != null)
{
users = await getResult.Content.ReadAsStringAsync();
}
return users;
}
private static async Task<string> GetToken()
{
AuthenticationResult result = null;
string token = null;
result = await context.AcquireTokenAsync(resource, credential);
token = result.AccessToken;
return token;
}
}
}
I am getting below error:
uMCJ9.FdttazjoKYZWP_SmC5B7Nd3kOF-jRs62WLKYovDA8qMvybLTw8yIUoihp7I00ctJGJHDoEbhbIi0XHp9Ujdq0bNPlG-L5SoE9IFSoxX3ZQOZwSf90b_nDapbHJ8KCHZUnCBOwVnYiTXtpIQfrDVqqENarrIGa_uUbiriomYiB8gVkKWe6PB-I4lsYPEmMNnnpdvIf1eV_CsTmvUA54Ch1Zdip9mxrzRqrUqsx6vUTo0riCmiCxRg7mH2DuMaEPTZuQAMwhrQM_EwNsgx1yX1VsCKkL1Gu7CV_dqW5xxYlE7NEQmorT8W6aySbiBzsUWisJNnaR8RqZzeAUlSVMKBiw
{"odata.error":{"code":"Request_BadRequest","message":{"lang":"en","value":"Invalid domain name in the request url."},"requestId":"01ab745b-8a3f-48cc-9542-0c6abcae8950","date":"2020-02-17T22:41:28"}}
r
Also ,help me in getting tenant name ( it's a name or alphanumeric id ?) ,currently i am just using below tenant name.
private const string tenant = "company.onmicrosoft.com";
I only want to use Microsoft Graph API in this code. Thanks.
i also want to know the whether this code is using Microsoft Graph or
Azure AD.
This code is using Azure AD Graph API. It works fine if the request uri is correct.You should use your tenant name, not company.onmicrosoft.com, and you must specify the api-version. So the request uri should be
var uri = "https://graph.windows.net/{your_tenant_name}.onmicrosoft.com/users?api-version=1.6";
If the tenant name is wrong, you will encounter Invalid domain name error.
I am not sure whether to use https://graph.microsoft.com or
https://graph.windows.net.
You can use either one. But the official document strongly recommend that you use Microsoft Graph instead of Azure AD Graph API to access Azure Active Directory (Azure AD) resources.
If you want to use Microsoft Graph API, just change the value of resource and request api url. The resource will be https://graph.microsoft.com. The request api url will be var uri = "https://graph.microsoft.com/v1.0/users";.
Note: Remember to grant your application the correct permissions in Azure portal and grant admin consent.

Microsoft Graph api code in C# displays only limited number of users

I am running below Microsoft Graph Api code:
using Microsoft.IdentityModel.Clients.ActiveDirectory;
using System;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading.Tasks;
namespace AADConsole2
{
class Program
{
private const string aadInstance = "https://login.microsoftonline.com/{0}";
// private const string ResourceUrl = "https://graph.windows.net";
private const string resource = "https://graph.microsoft.com";
private const string GraphServiceObjectId = "XXX";
private const string TenantId = "XXX";
private const string tenant = "XXXX.onmicrosoft.com";
private const string ClientId = "XXX";
private static string appKey= "XXXX";
static string authority = String.Format(System.Globalization.CultureInfo.InvariantCulture, aadInstance, tenant);
private static HttpClient httpclient = new HttpClient();
private static AuthenticationContext context = null;
private static ClientCredential credential = null;
static void Main(string[] args)
{
context = new AuthenticationContext(authority);
credential = new ClientCredential(ClientId, appKey);
Task<string> token = GetToken();
token.Wait();
Console.WriteLine(token.Result);
Task<string> users = GetUsers(token.Result);
users.Wait();
Console.WriteLine(users.Result);
Console.ReadLine();
}
private static async Task<string> GetUsers(string result) {
//throw new NotImplementedException();
string users = null;
var uri = "https://graph.microsoft.com/v1.0/users";
httpclient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", result);
var getResult = await httpclient.GetAsync(uri);
if(getResult.Content != null)
{
users = await getResult.Content.ReadAsStringAsync();
}
return users;
}
private static async Task<string> GetToken()
{
AuthenticationResult result = null;
string token = null;
result = await context.AcquireTokenAsync(resource, credential);
token = result.AccessToken;
return token;
}
}
}
I am getting the results of user detail printed on console ,but only limited number of users are printed. i.e only whose name starts with letter 'a'.
And also some user details are missing. How to get all user details .Am i missing some api in this code?
Thanks.
Most Microsoft Graph endpoints return paged result sets. Your initial request only returns the first page of data. To retrieve the next page, you follow the URI provided in the #odata.nextLink property. Each subsequent page will return the next page's #odata.nextLink until you the last page of data (denoted by the lack of a #odata.nextLink in the result). There is a step-by-step walkthrough of how this works at Paging Microsoft Graph data in your app.
The single most important tip I can give you here is to not use $top to force it to return large pages of data. This is an extremely inefficient method for calling the API and inevitably leads to network errors and request throttling. It also doesn't eliminate the need to handle paging since even $top=999 (the maximum) can still return multiple pages.
Implement paging, keep your page sizes small, and process the results after each page is returned before moving on to the next page. This will ensure you capture all of the data and allow your application to pick up where it left off should it encounter any errors during processing.
Something like this will get all of your users. Also if you want properties outside of the default you need need to specify them with a select. Not all properties are returned by default.
String Properties = "Comma Separated List of Properties You actaully Need";
List<User> AllUsers = new List<User>();
IGraphServiceUsersCollectionPage users = graphServiceClient.Users
.Request()
.Select(Properties)
.GetAsync()
.Result;
do
{
QueryIncomplete = false ;
AllUsers.AddRange(users);
if (users.NextPageRequest != null)
{
users = users.NextPageRequest.GetAsync().Result;
QueryIncomplete = true;
}
}while (QueryIncomplete);
return AllUsers;

Microsoft Graph API query involving externalUserState C#

Hi I'm trying to write a simple console app which I intend to make into a batch file and get a list of external users who were invited by email have and now they have guest accounts in our Azure tenant and they have redeemed the url that was sent to them in email. When they redeem, their extenalUserState sets to "Accepted". I want to find which ones have that status.
I was told that I have to point to beta version of the API and not v.1.0 of the graph endpoint.
I have the following rudimentary code I have written looking at various examples I could find on GitHub/MS documentation for API etc.
using Microsoft.Graph;
using Microsoft.IdentityModel.Clients.ActiveDirectory;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http.Headers;
using System.Text;
using System.Threading.Tasks;
namespace CreateAzureADUser
{
class Program
{
static string TenantDomain;
static string TenantId;
static string ClientId;
static string ClientSecret;
static void Main(string[] args)
{
GetUsers();
//Console.WriteLine("------------------------------------------\n\n");
//GetGroupsAndMembers();
//CreateAzureADUserNow();
}
private static void GetUsers()
{
var graphServiceClient = CreateGraphServiceClient();
var users = graphServiceClient.Users.Request().Filter("userType eq 'Guest' and startswith(mail,'phs')")
.Select("id,mail,OnPremisesExtensionAttributes,userType,displayName,externalUserState")
.GetAsync()
.Result;
Console.WriteLine("Users found: {0}", users.Count);
Console.WriteLine();
foreach (var item in users)
{
Console.WriteLine("displayName: {3} \nuser id: {0} \nuser email: {1} \nExtensionAttribute8: {2}\n", item.Id, item.Mail, item.OnPremisesExtensionAttributes.ExtensionAttribute8, item.DisplayName);
}
}
public static GraphServiceClient CreateGraphServiceClient()
{
TenantDomain = "mycompanytenant.onmicrosoft.com";
TenantId = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
ClientId = "yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy";
ClientSecret = "zzzzzzzzzzzz";
var clientCredential = new ClientCredential(ClientId, ClientSecret);
var authenticationContext = new AuthenticationContext($"https://login.microsoftonline.com/mycompanytenant.onmicrosoft.com");
var authenticationResult = authenticationContext.AcquireTokenAsync("https://graph.microsoft.com", clientCredential).Result;
var delegateAuthProvider = new DelegateAuthenticationProvider((requestMessage) =>
{
requestMessage.Headers.Authorization = new AuthenticationHeaderValue("bearer", authenticationResult.AccessToken);
return Task.FromResult(0);
});
// Use this for v.1.0 endpoint
//return new GraphServiceClient(delegateAuthProvider);
// Use this for connecting to beta endpoint
return new GraphServiceClient("https://graph.microsoft.com/beta", delegateAuthProvider);
}
}
}
When I run through the debugger, I do not see "ExternalUserState" as an attribute on the users that are returned.
How to access ExternalUserState attribute on the guest user object?
You're using the SDK so you're using Graph v1.0, not the Beta. The SDKs are all generated from v1.0 metadata so beta properties and methods simply do not exist in the models.
From time to time there is a beta build pushed out to GitHub but it is generally a few versions behind. Currently, the latest beta SDK available seems to be v1.12.0 (for reference, the current SDK is v1.15).

How to access Firebase Database from App server using C#

I am writing an App server application in C# that needs to access Firebase Database. It uses REST protocol. To authentication i want to use an service account.
Unfortunately there is no library written in C#, so i am trying to put the bellow http Request to work.
I follow this steps:
To get the accesstoken i follow the https://github.com/google/google-api-dotnet-client-samples. The code prints the token so should be ok to that point.
Invoke GET web request passing the token in the access_token query parameter as documented at https://firebase.google.com/docs/reference/rest/database/user-auth.
I tried all variations i could remember, in headers, with apostrophe, APN request style, but always got 401 error or 403. Error code 403 should mean that the API recognize the user but denys access to the resource, but i am not sure if this works this way in this case.
The account is defined in the API console and it has project edit and owner profile, for the Firebase app.
The rules are set like this:
{
"rules": {
".read": "auth != null",
".write": "auth != null"
}
}
Can't figure out were i went wrong. I don't think i need to go written an JWT token if i use google API library. Rules should not apply to this account so i guess i am not passing the token correctly. By inspecting the token retrieved i can see that it is of type Bear, so i tried to pass it on header with no success too.
Test code:
using System;
using System.Security.Cryptography.X509Certificates;
using Google.Apis.Auth.OAuth2;
using System.Threading.Tasks;
using System.Net;
using System.IO;
namespace FirebaseAppServer
{
/// </summary>
public class Program
{
public static void Main(string[] args)
{
accessFirebase();
Console.WriteLine("Press any key to continue...");
Console.ReadKey();
}
public async static Task accessFirebase()
{
String serviceAccountEmail = "serviceaccount1#myapp.iam.gserviceaccount.com";
var certificate = new X509Certificate2(#"App.p12", "notasecret", X509KeyStorageFlags.Exportable); //App2 is the certificate i downloaded from API console
ServiceAccountCredential credential = new ServiceAccountCredential(
new ServiceAccountCredential.Initializer(serviceAccountEmail)
{
Scopes = new[] { "https://www.googleapis.com/auth/firebase.database" //from https://developers.google.com/identity/protocols/googlescopes
,"https://www.googleapis.com/auth/firebase"
,"https://www.googleapis.com/auth/cloud-platform"}
}.FromCertificate(certificate));
var task = await credential.RequestAccessTokenAsync(System.Threading.CancellationToken.None);
Console.WriteLine("AccessToken " + credential.Token.AccessToken); //accessToken has a value, so guess is all good so far.
var request = (HttpWebRequest)WebRequest.Create("https://<Myapp>.firebaseio.com/.json?access_token=" + credential.Token.AccessToken);
request.Method = "GET";
request.ContentType = "application/json";
using (var response = (HttpWebResponse)request.GetResponse()) //Throw error 403 - forbidden
{
var responseString = new StreamReader(response.GetResponseStream()).ReadToEnd();
Console.WriteLine("responseString " + responseString);
}
}

Categories

Resources