How to Get a User List from Azure Actice Directory (Native App)? - c#

I want to get a list of users who have the right to use the application.
It is not possible to enter Secret keys in the Native App. I try to use the same accessToken as I log in. Authentication works fine. ObjectId is the same as my native app ObjectId.
result = await authContext.AcquireTokenAsync(graphResourceId, _ADClientId, redirectUri, new PlatformParameters(PromptBehavior.Never));
string accessToken = result.AccessToken;
ActiveDirectoryClient activeDirectoryClient = new ActiveDirectoryClient(serviceRoot, async () => await Task.FromResult(accessToken));
var groupFetcher = (IGroupFetcher)activeDirectoryClient.Groups.ExecuteAsync().Result.CurrentPage.First(g => g.ObjectId == "5deedc8c-2ba6-45d8-a4f2-xxxxxxx");
Error: Authorization_RequestDenied
What went wrong?

Within your Azure Active Directory, go to your Application registration and add a delegation permission for the Windows Active Directory API. Im not sure which is the least permission you can select (maybe Sign-in and read user profile may work) but Read directory data will give you the necessary permissions:

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.

ChangePassword operation throws exception "Unsupported User Type 'Unknown'" in GraphAPI

Users are registered using Active Directory B2C workflows so they appear as Members
What I am trying to do is to change users passwords like explained here
So my code looks like following:
var options = new TokenCredentialOptions
{
AuthorityHost = AzureAuthorityHosts.AzurePublicCloud
};
var userNamePasswordCredential = new UsernamePasswordCredential(
email, currentPassword, "my tenant id", "my client id", options);
var authentication = await userNamePasswordCredential.AuthenticateAsync();
var scopes = new[] { "User.Read" };
var graphClient = new GraphServiceClient(userNamePasswordCredential, scopes);
var user = await graphClient.Me
.Request()
.GetAsync();
await graphClient.Me
.ChangePassword(currentPassword, newPassword)
.Request()
.PostAsync();
I have tried multiple things but I always get the same exception:
The exception is the following:
MsalClientException: Unsupported User Type 'Unknown'. Please see
https://aka.ms/msal-net-up.
AuthenticationFailedException: UsernamePasswordCredential
authentication failed: Unsupported User Type 'Unknown'. Please see
https://aka.ms/msal-net-up.
So my question is, is it possible to auto change the password for users registered in ADB2C?
If so, what is causing the exception?
I know I can change password of users updating PasswordProfile as admin but I want to somehow verify they know their current password.
You cannot use an Azure AD B2C account to authenticate to Microsoft Graph API.
You must create a normal Azure AD Account from the AAD Users blade in the Azure Portal for this operation. Which means, this will not work for B2C users at all.
For Change Password flows, create a self service change-password flow that users can go through themselves.
Or, you must create an API endpoint which the B2C protected application can call. And the API must use client_credentials flow to call Graph API and perform the Update User operation on the password profile.

How to get user's information from Active Directory by email address using Microsoft Graph sdk

We would like to retrieve user's information from Azure Active directory using Microsoft Graph SDK.
Given a valid email address, but I get an error
Resource 'myemailaddress#live.com' does not exist or one of its queried reference-property objects are not present.
Code is below. Can you please guide?
IConfidentialClientApplication confidentialClientApplication = ConfidentialClientApplicationBuilder.Create(clientId).WithTenantId(tenantID).WithClientSecret(clientSecret).Build();
ClientCredentialProvider authProvider = new ClientCredentialProvider(confidentialClientApplication);
GraphServiceClient graphClient = new GraphServiceClient(authProvider);
var user = await graphClient.Users["myemailaddress#live.com"].Request().GetAsync();
I can reproduce your issue. The account myemailaddress#live.com is a Guest in your tenant, navigate to the AAD in the portal -> find the account -> click it and fetch the Object ID, then use the Object ID in the code, it will work.
var user = await graphClient.Users["<Object ID>"].Request().GetAsync();
Or you can use filter to get the user, in your case, the format of the UserPrincipalName for the guest user will be like myemailaddress_live.com#EXT##tenantname.onmicrosoft.com, when using the filter, we need URL encode it, then it will be myemailaddress_live.com%23EXT%23%40tenantname.onmicrosoft.com, try the code as below, it works on my side.
var user = await graphClient.Users.Request().Filter("UserPrincipalName eq 'myemailaddress_live.com%23EXT%23%40tenantname.onmicrosoft.com'").GetAsync();
Update:
If you want to get the user via UserPrincipalName, you can also use the url encoded one as below.
var user = await graphClient.Users["myemailaddress_live.com%23EXT%23%40tenantname.onmicrosoft.com"].Request().GetAsync();

Azure REST Api Authentication using C#

I would like to be able to get information about one of my Azure SQL databases using this call: https://learn.microsoft.com/en-gb/rest/api/sql/manageddatabases/manageddatabases_get
When I use the Try It button and login to my account it works perfectly, however I can't get my C# function app to get an authentication token so it can work in C#. I've spent 3 days on this. I have tried the Keyvault way but haven't managed to set up the permissions correctly. Forgetting Keyvault, the nearest I've got I think is by using this code but I don't know what my app password is:
// I am using:
// tenant id is the Azure AD client id
// client id is the application id of my function app in Azure AD
public static string GetAccessToken(string tenantId, string clientId, string clientSecret)
{
var authContextUrl = "https://login.windows.net/" + tenantId;
var authenticationContext = new AuthenticationContext(authContextUrl);
var credential = new ClientCredential(clientId, clientSecret );
var result = authenticationContext.AcquireTokenAsync(resource: "https://management.azure.com/", clientCredential: credential).Result;
if (result == null)
{
throw new InvalidOperationException("Failed to obtain the JWT token");
}
var token = result.AccessToken;
return token;
}
When I use the Try It button and login to my account it works perfectly
When you click the Try it, you use the user credential with username and user_password to authenticate. And the code you provided is using App registered in Azure AD to authenticate, and it would work well with the following steps you have followed.
1.As silent said, you need to create a Service Principle in Azure Active Directory. You could refer to this article.
2.The Sign in value about TenantId, clientId and clientSecret you could refer to this link.
3.Finally, you would access to Azure SQL Database, you need to add permission to you Azure AD App. Click the App you registered in Azure AD before and click Settings, and add Require Permission. After adding API access, Grant Permission.
I found an answer that worked for me (after 3 days of trying different things and trying to read articles about it on the web - its not very well documented I don't think).
This link contains some powershell steps:
https://msftstack.wordpress.com/2016/01/03/how-to-call-the-azure-resource-manager-rest-api-from-c/
These are the steps I tried in PowerShell
Login-AzureRmAccount
Get-AzureRmSubscription
Select-AzureRmSubscription –SubscriptionID “id”
$SecurePassword=ConvertTo-SecureString <my password> –asplaintext –force
$azureAdApplication = New-AzureRmADApplication -DisplayName “my ARM App” -HomePage
“https://<a home page>” -IdentifierUris “https://<a home page>” -Password $SecurePassword
New-AzureRmADServicePrincipal -ApplicationId $azureAdApplication.ApplicationId
New-AzureRmRoleAssignment -RoleDefinitionName Contributor -ServicePrincipalName $azureAdApplication.ApplicationId
Get-AzureRmSubscription
$subscription = Get-AzureRmSubscription –SubscriptionId "id"
$creds=get-credential
(enter application id and password at this point)
Login-AzureRmAccount -Credential $creds -ServicePrincipal -Tenant $subscription.TenantId

How to pass credentials to use ResourceManagementClient to get all resources from azure resource group c#?

I have install nuget Microsoft.Azure.Management.ResourceManager and have following code to get all existing resources based on Resource Group Name
var resouceManagementClient = new ResourceManagementClient(credentials) { SubscriptionId = "mySubscriptionId" };
var listResources =
resouceManagementClient.ResourceGroups.ListResources("Demo-ResourceGroup");
I'm not sure from where I can get credentials parameter value.
I do not have Azure Active Directory access , I think its must , can
we bypass azure AD?.
In my azure portal I have create a Resource Group - Demo-ResourceGroup
and have many resources created.
I want only list of all existing resources using c# code.
One way is by grabbing an access token from Azure AD and passing it in to a TokenCredentials class.
var authContext = new AuthenticationContext(string.Format("https://login.windows.net/{0}", tenantId));
var credential = new ClientCredential(applicationId, password);
AuthenticationResult token = authContext.AcquireTokenAsync("https://management.core.windows.net/", credential).Result;
var credentials = new TokenCredentials(token.AccessToken);
The set of credentials you use to request the acces token (in this case clientId/secret) will determine whether the application has the appropriate rights to enumerate the resources. This is a good MS docs page on how to register your application with AAD. In the example above, applicationId and password come from the application registration in AAD
Microsoft has a page describing other ways you can get tokens from AAD.

Categories