My client has synchronization set up between an on-premises Active Directory (AD) and Azure Active Directory (AAD).
I am able to retrieve user information from AAD using Microsoft Graph without a problem but, I specifically need to get the AD UserID, ie ({domain}/{userid}).
I tried calling https://graph.microsoft.com/v1.0/users/firstname.lastname#domain.com/$select=userId but it did not work.
My questions are, is it possible? And in that case what is the actual attribute name? I have been looking around but haven´t been able to find a complete list of attributes.
EDIT:
After receiving one answer from Marilee I am including the C# code I have been using, ish. Both the calls do work for receiving user information from AAD, but not the AD UserID, ie ({domain}/{userid}) that I am looking for.
Attempt no 1
var requestUri = GraphBaseUri + $"/v1.0/users/{upn}?$select=userId";
var response = await _httpClient.GetAsync(new AuthenticationHeaderValue("Bearer", accessToken), requestUri).ConfigureAwait(false);
var content = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
dynamic responseObj = JsonConvert.DeserializeObject(content) as JObject;
return responseObj.UserId; //NOT WORKING
Attempt no 2
var graphClient = new GraphServiceClient(new DelegateAuthenticationProvider((requestMessage) => {
requestMessage
.Headers
.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
return Task.FromResult(0);
}));
// Retrieve a user by userPrincipalName
var user = await graphClient
.Users[upn]
.Request()
.GetAsync();
return user.ObjectId; //NOT WORKING
The attribute you're referring to is the objectID. From Graph API you can use UPN like you said:
GET /users/{id | userPrincipalName}
You can look up the user in a few different ways. From the /users endpoint you can either use their id (the GUID assigned to each account) or their userPrincipalName (their email alias for the default domain):
// Retrieve a user by id
var user = await graphClient
.Users["00000000-0000-0000-0000-000000000000"]
.Request()
.GetAsync();
// Retrieve a user by userPrincipalName
var user = await graphClient
.Users["user#tenant.onmicrosoft.com"]
.Request()
.GetAsync();
If you're using either the Authorization Code or Implicit OAuth grants, you can also look up the user who authenticated via the /me endpoint:
var user = await graphClient
.Me
.Request()
.GetAsync();
From Powershell you can query Object IDs:
$(Get-AzureADUser -Filter "UserPrincipalName eq 'myuser#consoso.com'").ObjectId
The way to do this is;
var requestUri = GraphBaseUri + $"/v1.0/users/{upn}?$select=onPremisesSamAccountName";
var response = await _httpClient.GetAsync(new AuthenticationHeaderValue("Bearer", accessToken), requestUri).ConfigureAwait(false);
var content = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
var responseObj = JsonConvert.DeserializeObject<ResponseObj>(content);
return responseObj.OnPremisesSamAccountName;
Notice the select where we collect the onPremisesSamAccountName. Though, I haven´t found a comprehensive list of all attributes possible to retrieve which would have been nice.
Related
Azure is currently setup to sync from on
prem. Active Directory. What is the best way to update the department name
in Active Directory and Azure for these users? Below is a sample of the list.
Name
Old Department Name
New Department Name
Larry Lue
Collections
Collector Members
Erica Anderson
Collections
Collector Members
Mary Lee
Collections
Collector Members
You can use ms graph api to do the update by code. But this api doesn't provide a batch operation, so you have to update user profile one by one. Here's my test result:
Before updating the department, the original department look like this:
After update it by code. it will be changed to the new value.
Here's the code snippet:
using Microsoft.Graph;
using Azure.Identity;
public async Task<IActionResult> IndexAsync()
{
var scopes = new[] { "https://graph.microsoft.com/.default" };
var tenantId = "tenant_name.onmicrosoft.com";
var clientId = "azure_ad_app_id";
var clientSecret = "azure_ad_app_client_secret";
var clientSecretCredential = new ClientSecretCredential(
tenantId, clientId, clientSecret);
var graphClient = new GraphServiceClient(clientSecretCredential, scopes);
//query user information
var user = await graphClient.Users["user_id"]
.Request().Select("displayname, department").GetAsync();
//update user information
var userDept = new User
{
Department = "Collector Members"
};
await graphClient.Users["9a8ae89f-711a-434a-8d08-43e1f7d29af2"]
.Request()
.UpdateAsync(userDept);
}
Given I have the following info from Azure app registration:
Application (client) ID,
Client secret,
Directory (tenant) ID,
Object ID
Is there a way to check it's a valid credential programmatically (like using curl etc but not powershell)?
If you meant to check client secret validity or even the properties of that app ,then please check if the below c# code can be worked around .We can try to query the application and see expiry date of secret. Please grant the app with Directory.Read.All ,Application.Read.All permission to this API for using client credentials flow.
var graphResourceId = "https://graph.microsoft.com";
var applicationId= "";
var ObjectId = "";
var clientsecret = "";
var clientCredential = new ClientCredential(applicationId,secret);
var tenantId = "xxx.onmicrosoft.com";
AuthenticationContext authContext = new AuthenticationContext($"https://login.microsoftonline.com/{tenantId}");
//get accesstoken
var accessToken = authContext.AcquireTokenAsync(graphResourceId, clientCredential).Result.AccessToken;
Uri servicePointUri = new Uri(graphResourceId);
Uri serviceRoot = new Uri(servicePointUri, tenantId);
ActiveDirectoryClient activeDirectoryClient = new ActiveDirectoryClient(serviceRoot, async () => await Task.FromResult(accessToken));
var app = activeDirectoryClient.Applications.GetByObjectId(appObjectId).ExecuteAsync().Result;
foreach (var passwordCredential in app.PasswordCredentials)
{
Console.WriteLine($"KeyID:{passwordCredential.KeyId}\r\nEndDate:{passwordCredential.EndDate}\r\n");
}
If you want , you can even request token using curl this way and validate using post man or by checking token in https://jwt.io .
Reference: check client secret expiry using C#
I'm using the Graph API in an azure function to get all users from active directory.
The code I am using shown below but pseudocode is
if there is no previous deltaLink, get all users, else use the deltalink.
Get the first page.
While the userPage.NextPageRequest is not null, get the next page.
save this page to a database.
first page of 200 users returns as expected.
After this I expect each subsequent page will have its limit of 200 users, until the last page but this is not what I am seeing.
Very often the next page has less than 200 users, 187 to be exact and they are mostly duplicates of what I got in the first page.
Am I doing something incorrect here to cause this behavior?
var scopes = new[] { "https://graph.microsoft.com/.default" };
IUserDeltaCollectionPage usersPage;
UserDeltaCollectionPage lastDeltaPage = new UserDeltaCollectionPage();
if (deltaLink == null)//get all users, there is no delta link from a previous full load.
{
usersPage = await graphClient
.Users
.Delta()
.Request()
.GetAsync();
SaveUsersToDatabase(usersPage, sqlConnString, "insert");
}
else//use delta query to look for updates since last load.
{
lastDeltaPage.InitializeNextPageRequest(graphClient, deltaLink.ToString());
usersPage = await lastDeltaPage.NextPageRequest.GetAsync();
SaveUsersToDatabase(usersPage, sqlConnString, "insert");
}
while (usersPage.NextPageRequest != null)
{
usersPage = await usersPage.NextPageRequest.GetAsync();
SaveUsersToDatabase(usersPage, sqlConnString, "insert");
}
if (usersPage.NextPageRequest == null)//get delta link if this is the last page
{
usersPage.AdditionalData.TryGetValue("#odata.deltaLink", out newDeltaLink);
}
FYI - SaveUsersToDatabase just serializes the usersPage to json and sends it to a database.
Regarding user count, you can check at Azure AD for the count of users and may be duplicates are there itself. Another way to check that is to pull the data by providing Top to validate the count
usersPage = await graphClient
.Users
.Delta()
.Request()
.Top(400)
.GetAsync();
However, I would suggest you to use PageIterator to iterate over all the users as shown below in that case, you don't need to check the nextLink.
List<User> userResult = new List<User>();
users = await graphClient
.Users
.Delta()
.Request()
.GetAsync();
var userIterator = PageIterator<User>
.CreatePageIterator(graphClient, users, user) =>
{
userResult.Add(users);
return true;
});
//get all users
await userIterator.IterateAsync();
Troubleshooting shows that only the delta query is returning the duplicates, and is also returning the same ones each time.
We are going to avoid delta query for the moment and may come back to it in future.
thanks for the advice all.
I am attempting to update a user's AppRole assignments via the Graph Client. As per MS documents I am attempting to do it from the service principal side rather than the user side.
var sp = await _graphServiceClient.ServicePrincipals[objectId].Request().GetAsync();
ServicePrincipal newSp = new ServicePrincipal
{
Id = objectId,
AppId = _configuration["AzureAd:AppId"]
};
newSp.AppRoleAssignedTo = new ServicePrincipalAppRoleAssignedToCollectionPage();
newSp.AppRoleAssignedTo.Add(new AppRoleAssignment
{
PrincipalId = new Guid(u.Id),
ResourceId = new Guid(objectId),
AppRoleId = new Guid(r)
});
await _graphServiceClient.ServicePrincipals[objectId].Request().UpdateAsync(newSp);
I am getting 'One or more property values specified are invalid' but of course no real info on what property or even which object is the problem.
Anyone see anything obvious? I'm guessing on the syntax for the client usage bc I don't see much documentation or examples for it.
I test with same code with yours and met same issue and do some modification but still can't solve the issue. For your requirement of update user's AppRole assignment, I'm not sure if we can do it by the code you provided, but I can provide another solution which is more directly.
The code you provided is new a service principal and add the role assignment into it, then update the service principal. Here provide another solution, it can add the app role assignment directly:
var appRoleAssignment = new AppRoleAssignment
{
PrincipalId = Guid.Parse("{principalId}"),
ResourceId = Guid.Parse("{resourceId}"),
AppRoleId = Guid.Parse("{appRoleId}")
};
await graphClient.Users["{userId}"].AppRoleAssignments
.Request()
.AddAsync(appRoleAssignment);
The code above request this graph api in backend.
i 'm working on an azure functions that make some graph call to different tenant (multitenant)
I want to reuse a GraphServiceClient and leveraging token cache
I generate the GraphServiceClient in this way:
List<string> scopes = new List<string>() { "https://graph.microsoft.com/.default" };
var authProvider = ConfidentialClientApplicationBuilder.Create("e9b93362-a788-4644-8623-da9f4d4776a7")
.WithAuthority(AzureCloudInstance.AzurePublic, AadAuthorityAudience.AzureAdMultipleOrgs)
.WithClientSecret("fkpx53225awyQJDHV35:^][")
.Build();
var dd = new MsalAuthenticationProvider(authProvider, scopes.ToArray(),"ugochotmail.onmicrosoft.com");
var appGraphClient = new GraphServiceClient(dd);
Than i should call
authResult = await _clientApplication.AcquireTokenForClient(_scopes)
.WithAuthority(AzureCloudInstance.AzurePublic, Tenant)
.ExecuteAsync();
To obtain a token for the app to access the specific tenant.
The problem is in the authentication provider that is call on every send request but doen't offer a parameter with the tenant name
public async Task AuthenticateRequestAsync(HttpRequestMessage request)
{
var token = await GetTokenAsync();
request.Headers.Authorization = new AuthenticationHeaderValue("bearer", token);
}
At the moment i just add a property to the Authentication provider to set the tenant. It works but i would like to know if there is a better solution
Per my understanding, it seems your function doesn't allow a parameter which specify the tenant name and then use the tenant name when do GetTokenAsync() method. And now you can just hardcode the tenant name in the line new MsalAuthenticationProvider(... to specify the tenant.
For this problem, I think you can add a variable named tenant in the "Application settings" of your function app (as below screenshot show).
Then add a line of code string tenant = System.Environment.GetEnvironmentVariable("tenant"); above var token = await GetTokenAsync();
After that, you can add parameter in method GetTokenAsync() like GetTokenAsync(tenant). Then you do not need to hardcode tenant name in code, you just need to change the tenant name in "Application settings" of your function.
If I misunderstand your requirement, please provide more details.
=============================Update===============================
It seems you just want to specify the tenant in your code by a parameter, but not add the tenant name as a property in var dd = new MsalAuthenticationProvider(authProvider, scopes.ToArray(),"tenant name");. If so, you can refer to the code below (just add a line .WithTenantId("xxx.onmicrosoft.com") when do ConfidentialClientApplicationBuilder)
No it doesn't fix the problem as, in a multitenant, the target tenant is send as a parameter to the function. I'm working on an other approach i will come back when i will finish tests.
Thanks a lot