How to authenticate Azure Service Management Requests via AAD - c#

I've tried 3 ways with no result:
According to this article https://msdn.microsoft.com/en-us/library/azure/ee460782.aspx I've registered new web application in AAD with permissions to Access Azure Service Management API (steps 1-9) and written the recommended two lines of code to acquire the token:
var context = new AuthenticationContext($"https://login.windows.net/{tenantId}");
var result = context.AcquireToken("https://management.core.windows.net/", clientId, new Uri(redirectUri));
, but it fails with the exception:
Microsoft.IdentityModel.Clients.ActiveDirectory.AdalServiceException was unhandled
Message: An unhandled exception of type 'Microsoft.IdentityModel.Clients.ActiveDirectory.AdalServiceException' occurred in Microsoft.IdentityModel.Clients.ActiveDirectory.dll
Additional information: AADSTS90014: The request body must contain the following parameter: 'client_secret or client_assertion'.
Trace ID: aa2d6962-5aea-4f8e-bed4-9e83c7631887
Correlation ID: f7f1a61e-1720-4243-96fa-cff182150931
Also I've tried:
var context = new AuthenticationContext($"https://login.windows.net/{tenantId}");
var result = context.AcquireToken("https://management.core.windows.net/", new ClientCredential(clientId, clientSecret));
where clientSecret is secret app key of my application.
This version returns a token, but requests with this token returns 403 Forbidden:The server failed to authenticate the request. Verify that the certificate is valid and is associated with this subscription.
The last, I've found http://blogs.msdn.com/b/cloud_solution_architect/archive/2015/03/02/authenticating-azure-service-management-api-with-azure-ad-user-credentials.aspx, which recommends:
var context = new AuthenticationContext(string.Format("https://login.windows.net/{0}", tenantId));
// TODO: Replace with your Azure AD user credentials (i.e. admin#contoso.onmicrosoft.com)
string user = "{YOUR-USERID]";
string pwd = "{YOUR-USER-PASSWORD}";
var userCred = new UserCredential(user, pwd);
AuthenticationResult result =
await context.AcquireTokenAsync("https://management.core.windows.net/", clientId, userCred);
but it also fails with the same exception as in the first case...
Could you please assist me?

You should change the "Application Type" to "NATIVE CLIENT APPLICATION" while creating the application in the Azure portal.

Related

SharePointOnline CSOM 401 Unauthorized Using Provided Access Token

I am building a feature that automates the retrieval of documents and other SharePoint files from a Web API, but I'm having a difficult time getting authorized to perform even basic read operations. I am testing this in a .NET 6 console application using the Microsoft.SharePointOnline.CSOM NuGet package.
I have registered an application in Azure Active Directory and given it the Sites.Read.All permission. I've taken the ClientID, ClientSecret and TenantID as reported by that registered application and I'm using those in my console application. I can retrieve an access token without issue, and decoding that JWT shows that it comes with Sites.Read.All permission. But regardless of what I try, ClientContext.ExecuteQueryAsync() consistently throws an exception complaining that the remote server responded with a 401.
Here is the code that I'm testing this with:
var clientId = "myClientId";
var clientSecret = "myClientSecret";
var tenantId = "myTenantId";
var authority = "https://login.microsoftonline.com/" + tenantId;
var siteUrl = "https://myorg.sharepoint.com";
var app = new ConfidentialClientApplicationBuilder
.Create()
.WithClientSecret(clientSecret)
.WithAuthority(authority)
.WithTenantId(tenantId)
.Build();
var paramBuilder = app.AcquireTokenForClient(new[] { siteUrl + "/.default" });
var authResult = await paramBuilder.ExecuteAsync();
// authResult has successfully retrieved an access token at this point
var context = new ClientContext(siteUrl);
context.ExecutingWebRequest += (_, e) =>
{
e.WebRequestExecutor.RequestHeaders["Authorization"] = "Bearer " + authResult.AccessToken;
}
context.Load(context.Web);
await context.ExecuteQueryAsync(); // 401 is thrown here
var title = context.Web.Title;
I have tried several different ways of getting around this to no avail:
I have gone to the Admin center of my SharePoint site and given the app FullControl permissions, as well as giving the app those permissions in Azure AD. This doesn't seem to have changed anything, I still get the same 401.
I have registered an entirely new app directly from my SharePoint sub-site admin center and given it FullControl permissions. I used the new client ID and client secret that were generated, and I was able to get back an access token. No luck, still get the 401 calling ClientContext.ExecuteQueryAsync()
I have tried changing my siteUrl to a SharePoint site-specific URL (e.g. https://myorg.sharepoint.com/sites/mySite), but once I do that I am no longer able to retrieve an access token. I instead get an Msal exception thrown, AADSTS500011, which reads:
"The resource principal named https://myorg.sharepoint.com/sites/mysite was not found in the tenant named (my tenant). This can happen if the application has not been installed by the administrator of the tenant or consented to by any user in the tenant. You might have sent your authentication request to the wrong tenant.
I have also tried using the base siteUrl to retrieve the token, then giving the site-specific URL to ClientContext. I get the same 401 result.
I have tried several different authorities in case the token I'm being provided is invalid. I've tried using the V1 token URL, the V2 token URL, no token-specific URL (only the default authority address + tenant ID). All of these return an access token, but none of them avoid the 401.
A MS documentation article suggests appending an additional "/" to the requested .default scope in instances where a 401 is being returned (e.g. https://myorg.sharepoint.com/sites/mysite//.default). This doesn't seem to have changed anything.
My application seems to have the permissions it needs to do this basic read operation, but I am continually rebuffed. I am using the ClientID, ClientSecret and Tenant ID as copied directly from the AAD application page. The code I'm using above is recommended by Microsoft to use the new SharePointOnline.CSOM package. What am I missing here?
Constructor of ClientContext requires site url including site name.
var clientId = "myClientId";
var clientSecret = "myClientSecret";
var tenantId = "myTenantId";
var authority = "https://login.microsoftonline.com/" + tenantId;
var siteUrl = "https://myorg.sharepoint.com";
var siteName = "MySiteName";
var app = new ConfidentialClientApplicationBuilder
.Create()
.WithClientSecret(clientSecret)
.WithAuthority(authority)
.WithTenantId(tenantId)
.Build();
var paramBuilder = app.AcquireTokenForClient(new[] { siteUrl + "/.default" });
var authResult = await paramBuilder.ExecuteAsync();
// authResult has successfully retrieved an access token at this point
var webFullUrl = $"{siteUrl}/sites/{siteName}";
var context = new ClientContext(webFullUrl);
If the site has some prefix
var webFullUrl = $"{siteUrl}/sites/{sitePrefix}/{siteName}";
I wound up "solving" this problem by using the PnP.Framework NuGet package instead of Microsoft.SharePointOnline.CSOM. I changed nothing else about my app registration or its designated permissions, and PnP.Framework was able to handle it without issue (and with fewer arguments). It seems to know something that SharePointOnline.CSOM doesn't considering that the following simple console app works:
using System;
using PnP.Framework
const string clientId = "myClientId";
const string clientSecret = "myClientSecret";
const string siteUrl = "https://myorg.sharepoint.com/sites/mysite";
using var clientContext = new AuthenticationManager()
.GetACSAppOnlyContext(siteUrl, clientId, clientSecret);
cc.Load(cc.Web);
await cc.ExecuteQueryAsync(); // no longer throws a 401
Console.WriteLine(cc.Web.Title); // prints my site's title
I tried to use the newer PnP.Core SDK, but I couldn't find any documentation or examples on how to get that package working with an app-only client secret authenticated context. PnP.Framework's API is the cleanest and most reliable that I've found as of yet.

Getting MsalClientException while requesting token from EWS.AccessAsUser.All using delegate permission

I have followed all the step mentioned in this answer given by #Robert Muehsig and added the code in .net framework console app after running I am getting below error,
Microsoft.Identity.Client.MsalClientException: 'There was an error parsing WS-Trust response from the endpoint. This may occur if there is an issue with your ADFS configuration. See https://aka.ms/msal-net-iwa-troubleshooting for more details. Error Message: returned error: ID3242: The security token could not be authenticated or authorized. '
code part
var pcaOptions = new PublicClientApplicationOptions
{
ClientId = "ClientId",
TenantId = "TenantId"
};
var pca = PublicClientApplicationBuilder.CreateWithApplicationOptions(pcaOptions).Build();
var securePassword = new SecureString();
foreach (char c in "Password")
securePassword.AppendChar(c);
var cred = new NetworkCredential("Username", securePassword);
var authResult = await pca.AcquireTokenByUsernamePassword(new string[] { "https://outlook.office.com/EWS.AccessAsUser.All" }, cred.UserName, cred.SecurePassword).ExecuteAsync();
During registration I have set "Treat application as public client" true.
From postman I able to get token but if add it during debug point it will throw 401 error.
It looks like either the username or password passed in the
credentials are incorrect.
Also use current or latest version of dot net to clear the issues
regarding compatibility
As you are getting token, after decoding it check the audience is
valid, and see if it is either clientId or appId uri.
If not try to change the accessTokenAcceptedVersion to 2 if it is
null or to 1 or null if it is 2.
And make sure the scope https://outlook.office.com/EWS.AccessAsUser.All that is given in code is also given in azure ad portal and granted admin consent from admin and users if set to consent by them .

how reproduce result of powershell > az login in c#?

The result of "az login" is list of subscriptions: like here:
https://learn.microsoft.com/en-us/rest/api/resources/subscriptions/list?source=docs
How to make token for this request without providing tenantId or clientId exactly how it was made on website's login?
I can make token is quite close to required but do not have what I see inside token from website:
var functionCred = new Microsoft.IdentityModel.Clients.ActiveDirectory.ClientCredential(clientId, secret);
var context = new AuthenticationContext("https://login.microsoftonline.com/" + tenantId, false);
var result = context.AcquireTokenAsync("https://management.core.windows.net/", functionCred).Result;
var token44 = result.AccessToken;
What should I do to improve token?
By default, az login command logs in with a user account. CLI will try to launch a web browser to log in interactively, if browser is not available then CLI will fall back to device code login.
For the question -
How to make token for this request without providing tenantId or clientId exactly how it was made on website's login?
It is not feasible to get an authentication token without using tenantId and clientId. Mostly token is generated by client id and secret. Token can be acquired via app registration + tenentId or user credentials + tenentId.
Below code snippet shows how you get the access token using Azure Active Directory Authentication Library (ADAL).
static AuthenticationResult AccessToken()
{
string resourceUri = "https://datacatalog.azure.com";
// register a client app and get a Client ID
string clientId = clientIDFromAzureAppRegistration;
//A redirect uri gives AAD more details about the specific application that it will authenticate.
string redirectUri = "https://login.live.com/oauth20_desktop.srf";
// Create an instance of AuthenticationContext to acquire an Azure access token
string authorityUri = "https://login.windows.net/common/oauth2/authorize";
AuthenticationContext authContext = new AuthenticationContext(authorityUri);
// AcquireToken takes a Client Id that Azure AD creates when you register your client app.
return authContext.AcquireToken(resourceUri, clientId, new Uri(redirectUri), PromptBehavior.RefreshSession);
}
For more information check this Steps to get an access token section of the Microsoft Document.

Authentication to Azure Active Directory and receive AADSTS90019 error

I am trying to authenticate against AAD using the following code:
string userName = "something.com"; //(just an example)
string password = "IafksdfkasdaFadad=asdad=a="; //(just an example)
string clientId = "6cd6590f-4db9-4c6b-98d1-476f9e90912f"; //(just an example)
var credentials = new UserPasswordCredential(userName, password);
var authenticationContext = new AuthenticationContext("https://login.windows.net/common");
var result = await authenticationContext.AcquireTokenAsync("https://api.partnercenter.microsoft.com", clientId, credentials);
return result;
and I got AADSTS90019 error: No tenant-identifying information found in either the request or implied by any provided credentials.
As a remark, it is just a console application made in Visual Studio using C#.
Based on the information from https://learn.microsoft.com/en-us/azure/active-directory/develop/reference-aadsts-error-codes the explication for AADSTS90019 error is: MissingTenantRealm - Azure AD was unable to determine the tenant identifier from the request.
So, my question is: What is the tentant identifier and how should I use it in my request?
Should it be the one from the following screenshot? The screenshot is made from the Azure account.
Azure Application Overview
Any information can help.
Thank you.
You should initialize your authentication context with a tenant-specific authority instead of common:
var authenticationContext = new AuthenticationContext("https://login.microsoftonline.com/your-directory-id");
Replace your-directory-id with your Directory (tenant) id.
var authenticationContext = new AuthenticationContext("https://login.windows.net/common");
Here Replace the string "common" with the tenant name.

Microsoft Graph The token contains no permissions, or permissions cannot be understood

I am working with Microsoft Graph and have created an app that reads mail from a specific user.
However, after getting an access token and trying to read the mailfolders, I receive a 401 Unauthorized answer. The detail message is:
The token contains no permissions, or permissions cannot be understood.
This seems a pretty clear message, but unfortunately I am unable to find a solution.
This is what I have done so far:
Registering the app on https://apps.dev.microsoft.com
Giving it
application permissions Mail.Read, Mail.ReadWrite
(https://learn.microsoft.com/en-us/graph/api/user-list-mailfolders?view=graph-rest-1.0)
Have gotten administrator consent.
The permissions are:
- Written the code below to acquire an access token:
// client_secret retrieved from secure storage (e.g. Key Vault)
string tenant_id = "xxxx.onmicrosoft.com";
ConfidentialClientApplication client = new ConfidentialClientApplication(
"..",
$"https://login.microsoftonline.com/{tenant_id}/",
"https://dummy.example.com", // Not used, can be any valid URI
new ClientCredential(".."),
null, // Not used for pure client credentials
new TokenCache());
string[] scopes = new string[] { "https://graph.microsoft.com/.default" };
AuthenticationResult result = client.AcquireTokenForClientAsync(scopes).Result;
string token = result.AccessToken;
So far so good. I do get a token.
Now I want to read the mail folders:
url = "https://graph.microsoft.com/v1.0/users/{username}/mailFolders";
handler = (HttpWebRequest)WebRequest.Create(url);
handler.Method = "GET";
handler.ContentType = "application/json";
handler.Headers.Add("Authorization", "Bearer " + token);
response = (HttpWebResponse)handler.GetResponse();
using (StreamReader sr = new StreamReader(response.GetResponseStream()))
{
returnValue = sr.ReadToEnd();
}
This time I receive a 401 message, with the details:
The token contains no permissions, or permissions cannot be understood.
I have searched the internet, but can’t find an answer to why my token has no permissions.
Thanks for your time!
update 1
If I use Graph Explorer to read the mailfolders, then it works fine. Furthermore: if I grap the token id from my browser en use it in my second piece of code, then I get a result as well. So, the problem is really the token I receive from the first step.
To ensure this works like you expect, you should explicitly state for which tenant you wish to obtain the access token. (In this tenant, the application should, of course, have already obtained admin consent.)
Instead of the "common" token endpoint, use a tenant-specific endpoint:
string url = "https://login.microsoftonline.com/{tenant-id}/oauth2/v2.0/token";
(Where {tenant-id} is either the tenant ID of the tenant (a Guid), or any verified domain name.)
I would also strongly recommend against building the token request on your own, as you show in your question. This may be useful for educational purposes, but will tend to be insecure and error-prone in the long run.
There are various libraries you can use for this instead. Below, an example using the Microsoft Authentication Library (MSAL) for .NET:
// client_secret retrieved from secure storage (e.g. Key Vault)
string tenant_id = "contoso.onmicrosoft.com";
ConfidentialClientApplication client = new ConfidentialClientApplication(
client_id,
$"https://login.microsoftonline.com/{tenant_id}/",
"https://dummy.example.com", // Not used, can be any valid URI
new ClientCredential(client_secret),
null, // Not used for pure client credentials
new TokenCache());
string[] scopes = new string[] { "https://graph.microsoft.com/.default" };
AuthenticationResult result = client.AcquireTokenForClientAsync(scopes).Result
string token = result.AccessToken;
// ... use token

Categories