How To Update MS Graph Client Service Principal AppRoleAssignments - azure-active-directory

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.

Related

How to update a User Claim after on a active SAML Session in .Net Core?

Is it possible to update a User Claim during a active SAML Session without a complete relogging to the application?
I want to change a specific claim (activeSite) that we use for authorization in backend and for filtering in queries.
I'm working with an .Net Core API and a Angular/Ionic frontend.
The code we use is from the TestWebAppCoreAngularApi Project with a little adoption by setting some custom user claims.
[Route("AssertionConsumerService")]
public async Task<IActionResult> AssertionConsumerService()
{
var binding = new Saml2PostBinding();
var saml2AuthnResponse = new Saml2AuthnResponse(config);
binding.ReadSamlResponse(Request.ToGenericHttpRequest(), saml2AuthnResponse);
if (saml2AuthnResponse.Status != Saml2StatusCodes.Success)
{
throw new AuthenticationException($"SAML Response status: {saml2AuthnResponse.Status}");
}
binding.Unbind(Request.ToGenericHttpRequest(), saml2AuthnResponse);
var relayStateQuery = binding.GetRelayStateQuery();
var activeSite = relayStateQuery.ContainsKey(relayStateActiveSite) ? relayStateQuery[relayStateActiveSite] : Url.Content("-1");
AddCustomClaims(saml2AuthnResponse, Int32.Parse(activeSite));
await saml2AuthnResponse.CreateSession(HttpContext, claimsTransform: (claimsPrincipal) => ClaimsTransform.Transform(claimsPrincipal));
var returnUrl = relayStateQuery.ContainsKey(relayStateReturnUrl) ? relayStateQuery[relayStateReturnUrl] : Url.Content("~/");
return Redirect(returnUrl);
}
Is it even possible to update that active claim (activeSite) during a active session?
And if so how can I update the claim in .Net?
I tryed something like removing and adding the claim with new value but this seems not to work correctly.
var principle = (ClaimsIdentity)User.Identity;
principle.RemoveClaim(claim);
principle.AddClaim(claim);
It is NOT possible to change claims after a successfully authentication flow. Changing claims require a re-login.
Hovewer, it is possible to change claims after user login in the authentication flow, by calling the ClaimsTransform.Transform, and the ClaimsTransform class.

Use Graph inside a multitenant azure function (App credentials)

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

How to retrieve the userid from userprincipalname in azure active directory?

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.

Updating claims with Azure B2C / .NET Core

I've spent some time getting my MVC 6 .NET Core website working with Azure B2C and everything seems to be working great. However, there are a few questions surrounding claims that I can't seem to figure out the correct strategy.
Say a user signs up on my site with email, firstname, lastname. Once the registration is complete, I would like to add a record into a UserProfile table in my database that references this user.
Question 1:
Should I create a "UserProfileId" claim in Azure B2C? Or should I create an "ObjectId" field in my database table that references the AD user? What would make more sense?
Question 2:
Once a user registers, where and how would I update an AD User claim? Would I do it in one of these events? Or somewhere else? I see there is a "User is new" claim that I could check for?
OnAuthenticationValidated
OnAuthorizationCodeReceived
OnRedirectToAuthenticationEndpoint
Question 3:
To update the claims, would I use: Microsoft.Azure.ActiveDirectory.GraphClient? Does anyone have any sample code for how to update a custom claim? I've tried this but it doesn't seem to persist:
var identity = context.AuthenticationTicket.Principal.Identity as ClaimsIdentity;
identity?.AddClaim(new Claim("EmployeeId", "33"));
Here is my authentication configuration. Thanks!!!!!
public void ConfigureAuth(IApplicationBuilder app, IOptions<PolicySettings> policySettings, AuthenticationHelper authHelper)
{
app.UseCookieAuthentication(options =>
{
options.AutomaticAuthenticate = true;
options.AutomaticChallenge = true;
options.AccessDeniedPath = "/Home/Forbidden";
options.CookieSecure = CookieSecureOption.Always;
options.ExpireTimeSpan = TimeSpan.FromHours(1);
options.SlidingExpiration = true;
});
app.UseOpenIdConnectAuthentication(options =>
{
options.PostLogoutRedirectUri = policySettings.Value.PostLogoutRedirectUri;
options.AutomaticAuthenticate = true;
options.AutomaticChallenge = true;
options.ClientId = policySettings.Value.ClientId;
options.CallbackPath = new PathString("/signin-mysite");
options.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.Scope.Add("openid");
options.Scope.Add("profile");
options.Scope.Add("email");
options.ResponseType = OpenIdConnectResponseTypes.IdToken;
options.Authority = string.Format(CultureInfo.InvariantCulture, "{0}/{1}", policySettings.Value.AadInstance, policySettings.Value.Tenant);
options.Events = new OpenIdConnectEvents {
OnAuthenticationValidated = OnAuthenticationValidated,
OnAuthorizationCodeReceived = OnAuthorizationCodeReceived,
OnAuthenticationFailed = OnAuthenticationFailed,
OnRedirectToAuthenticationEndpoint = OnRedirectToAuthenticationEndpoint
};
options.ConfigurationManager = new PolicyConfigurationManager(
String.Format(CultureInfo.InvariantCulture, "{0}/{1}/{2}/{3}", policySettings.Value.AadInstance, policySettings.Value.Tenant, "v2.0", OpenIdProviderMetadataNames.Discovery),
new string[] { policySettings.Value.SignUpInPolicyId, policySettings.Value.ProfilePolicyId, policySettings.Value.PasswordPolicyId });
});
}
Question 1: Should I create a "UserProfileId" claim in Azure B2C? Or should I create an "ObjectId" field in my database table that references the AD user? What would make more sense?
1a - I didn't add anything to the B2C tenant.
1b - I take the object id from B2C and store it in my table as an alternate key. My table has a unique id of it's own. Should I ever wish to have additional identity providers, this will be necessary.
I only use the object id from B2C to look up users and get my own id.
Question 2: Once a user registers, where and how would I update an AD User claim? Would I do it in one of these events? Or somewhere else? I see there is a "User is new" claim that I could check for?
When you say "update the claim" do you mean update permanently in the B2C tenant, or do you mean add it to the other claims and use it temporarily during the life of this particular token?
There's no connection back to B2C without using the graph client.
The userIsNew claim comes from B2C one time and only at the end of the signup process. You use that to determine if you have a new user trying to access your system. I hook that to create new entries in my tables from the claims that B2C gives me and from then on, the claims all come from the information in my tables.
Question 3: To update the claims, would I use: Microsoft.Azure.ActiveDirectory.GraphClient? Does anyone have any sample code for how to update a custom claim? I've tried this but it doesn't seem to persist:
I have to ask the "update" question again.
What you may be looking for is to "transform" the claims. That's usually done during a TicketReceived event for cookies. That occurs when they have authenticated for the first time. (Not to be confused with signing up.)
I'm not all that bright, but I'll tell you that I spent waaaay too much time on this trying to get it right. Mostly it's because there are a vast number of options and no one can tell you all the right ones for your project. So you just see reams of information that you have go through to find the bits you want.
I found this book (and it's author) incredibly helpful. It's current and he's a Microsoft guy who writes really well.
HTH
Regarding question 1: I did the same as nhwilly: I store the additional information in my database.
Regarding question 2: you can add claims in the OnsigningIn event:
app.UseCookieAuthentication(new Microsoft.AspNetCore.Builder.CookieAuthenticationOptions()
{
Events = new CookieAuthenticationEvents()
{
OnSigningIn = (context) =>
{
ClaimsIdentity identity = (ClaimsIdentity)context.Principal.Identity;
identity.AddClaim(new Claim("sb:tID", "555"));
return Task.FromResult(0);
}
}
});
I got the information from Transforming Open Id Connect claims in ASP.Net Core.
Regarding question 3: I haven't done it myself, but this link should get you kickstarted: https://learn.microsoft.com/en-us/azure/active-directory-b2c/active-directory-b2c-devquickstarts-graph-dotnet
Hope that helps!

Azure Graph service not finding newly created user

I have a web application that uses Azure ACS and Azure AD to handle our authentication.
We have a user management feature in the web application that allows a user to create new users. This takes the details such as username, password, email etc. and uses the graph service to create a user in azure.
var newUser = new Microsoft.WindowsAzure.ActiveDirectory.User
{
userPrincipalName = user.UserName,
mailNickname = user.MailNickname,
accountEnabled = true,
displayName = user.FirstName + " " + user.Surname,
givenName = user.FirstName,
surname = user.Surname
};
newUser.passwordProfile = new PasswordProfile
{
forceChangePasswordNextLogin = false,
password = user.Password
};
var graphService = GetGraphService(tenantName);
graphService.AddTousers(newUser);
graphService.SaveChanges();
We are then required to create a record in the web application database for this user. The record needs the object ID from azure. So we use the graphService to get the newly-created user details. This is where my problem lies. It doesn't find the user.
private string GetObjectIdFromAzure(string userName, string tenantName)
{
var graphService = GetGraphService(tenantName);
var users = graphService.users;
QueryOperationResponse<Microsoft.WindowsAzure.ActiveDirectory.User> response;
response = users.Execute() as QueryOperationResponse<Microsoft.WindowsAzure.ActiveDirectory.User>;
var user = response.FirstOrDefault(x => x.userPrincipalName == userName);
return user != null ? user.objectId : "";
}
My code was working without any issues for a few months and only today I am having issues. What frustrates me more it that I have another deployment of the same code where it works without any issues. Some differences between the two deployments are:
The deployments use different Access control namespaces in Azure
The deployments have separate applications in Azure AD
One is https, one is http
The users for both system are under the same Directory.
I have put in logging in both deployments to get the number of users returned by
users.Execute()
In both systems it reported 100 (they share the same users)
Any ideas of what would cause this to stop working? I didn't change any code relating to this recently, I haven't changed any configuration on Azure and I didn't change the web.config of the application
The problem was caused by the fact that I was filtering the users after retrieving them. The graph API was only returning a maximum of 100 users.
So the process was like so:
User created in Azure
Success message returned
Web App searches Azure for user to get Object ID
Graph Api only returns top 100 users. User was not in top 100 alphabetically so error thrown
The reason it was working on our second deployment was that I was prefixing the user name with demo_ (we use this site to demo new features before releasing). This meant that it was being returned in the top 100 users.
I changed the code as follows so it filters during the retrieval instead of after:
private Microsoft.WindowsAzure.ActiveDirectory.User GetUserFromAzure(string userName, string tenantName, out DirectoryDataService graphService)
{
graphService = GetGraphService(tenantName);
var users = (DataServiceQuery<Microsoft.WindowsAzure.ActiveDirectory.User>)graphService.users.Where(x => x.userPrincipalName == userName);
QueryOperationResponse<Microsoft.WindowsAzure.ActiveDirectory.User> response;
response = users.Execute() as QueryOperationResponse<Microsoft.WindowsAzure.ActiveDirectory.User>;
var user = response.FirstOrDefault();
return user;
}

Resources