Issue with Remove Application Role in AzureAD using GraphAPI / C# - c#

I am trying to Add/Remove Application Role in AzureAD using VisualStudio/C#/GraphAPI. I can successfully add user to ApplicationRole but Remove(or Delete) role doesn't work.
I researched on internet and it seems an issue with AzureAD graph API itself. check:
https://social.msdn.microsoft.com/Forums/sqlserver/en-US/5707763c-41f7-4465-abdb-3a8d8ded153b/graph-api-apiversion15-how-to-remove-user-from-application-role-using-c-net?forum=WindowsAzureAD
However, it's an old post so not sure if any workaround is available now.
Any help is appreciated to fix this issue.

I can successfully add user to ApplicationRole but Remove(or Delete) role doesn't work.
I can remove the application role with follow code.
var listRoles = user.AppRoleAssignments.ToList();
user.AppRoleAssignments.Remove(listRoles[0]); //just demo: you could remove the role as your wanted
user.UpdateAsync().Wait();
The following is my detail test demo code
1.get access token
private static async Task<string> GetAppTokenAsync(string graphResourceId, string tenantId, string clientId, string secretKey)
{
string aadInstance = "https://login.microsoftonline.com/" + tenantId + "/oauth2/token";
AuthenticationContext authenticationContext = new AuthenticationContext(aadInstance, false);
var result = await authenticationContext.AcquireTokenAsync(graphResourceId,
new ClientCredential(clientId, userId));
return result.AccessToken;
}
2.Init the graphclient.
var graphResourceId = "https://graph.windows.net";
var tenantId = "tenantId";
var clientId = "client Id";
var secretKey = "secret key";
var servicePointUri = new Uri(graphResourceId);
var serviceRoot = new Uri(servicePointUri, tenantId);
var activeDirectoryClient = new ActiveDirectoryClient(serviceRoot, async () => await GetAppTokenAsync(graphResourceId, tenantId, clientId, secretKey));
3.create application and service principal
Application appObject = new Application { DisplayName = "Test-Demo App" };
appObject.IdentifierUris.Add("https://localhost/demo/" + Guid.NewGuid());
appObject.ReplyUrls.Add("https://localhost/demo");
AppRole appRole = new AppRole
{
Id = Guid.NewGuid(),
IsEnabled = true,
DisplayName = "Something",
Description = "Anything",
Value = "policy.write"
};
appRole.AllowedMemberTypes.Add("User");
appObject.AppRoles.Add(appRole);
activeDirectoryClient.Applications.AddApplicationAsync(appObject).Wait();
// create a new Service principal
ServicePrincipal newServicePrincpal = new ServicePrincipal
{
DisplayName = appObject.DisplayName,
AccountEnabled = true,
AppId = appObject.AppId
};
activeDirectoryClient.ServicePrincipals.AddServicePrincipalAsync(newServicePrincpal).Wait();
4.add role assginments
User user = (User) activeDirectoryClient.Users.GetByObjectId("userobjectId").ExecuteAsync().Result;
AppRoleAssignment appRoleAssignment = new AppRoleAssignment
{
Id = appRole.Id,
ResourceId = Guid.Parse(newServicePrincpal.ObjectId),
PrincipalType = "User",
PrincipalId = Guid.Parse(user.ObjectId),
};
user.AppRoleAssignments.Add(appRoleAssignment);
user.UpdateAsync().Wait();
5.remove the role from user
var listRoles = user.AppRoleAssignments.ToList();
user.AppRoleAssignments.Remove(listRoles[0]);
user.UpdateAsync().Wait();

Related

How to retrieve all the groups that a user has from Azure AD in C# ASP.NET Core 6.0?

I'm trying to get all the groups that an user has but I can't achieve that. Here's what I've been trying:
public async Task<string> traerGrupos(string userID)
{
string currentUser = "null";
try
{
var tenant = "mytenant";
var clientID = "myclientid";
var secret = "mysecretkey";
var clientSecretCred = new ClientSecretCredential(tenant, clientID, secret);
GraphServiceClient graphClient = new GraphServiceClient(clientSecretCred);
var usr = graphClient.Users[userID.ToString()].Request()
.Select(x => x.DisplayName).GetAsync().Result;
currentUser = usr.DisplayName;
return currentUser;
}
catch (Exception ex)
{
return currentUser = ex.Message;
}
}
But I cannot see an option to get the groups. Besides, I get this error:
Code: Authorization_RequestDenied Message: Insufficient privileges to complete the operation.
Inner error: AdditionalData: date: 2022-12-06T19:54:23...
but my app has every permission that it requires.
How could I solve this? Thank you very much!
Firstly, your requirement can be done by this api.
And I noticed that you are using Graph SDK, so your code should look like this:
var memberOf = await graphClient.Users["{user-id}"].MemberOf
.Request()
.GetAsync();
Then I noticed you are using client credential flow by this code new ClientSecretCredential, so I agree with Charles Han that you should set the scope as https://graph.microsoft.com/.default.
Then the whole progress is:
Adding correct API permission in Azure AD application for Application type Directory.Read.All, Directory.ReadWrite.All
Your code should look like this:
using Microsoft.Graph;
using Azure.Identity;
var scopes = new[] { "https://graph.microsoft.com/.default" };
var tenantId = "tenant_name.onmicrosoft.com";
var clientId = "aad_app_id";
var clientSecret = "client_secret";
var clientSecretCredential = new ClientSecretCredential(
tenantId, clientId, clientSecret);
var graphClient = new GraphServiceClient(clientSecretCredential, scopes);
var memberOf = await graphClient.Users["{user-id}"].MemberOf.Request().GetAsync();
If you have the scope set up correctly in the app registration, try to add the scope in your GraphServiceClient constructor,
string[] scopes = new string[] { "https://graph.microsoft.com/.default" };
GraphServiceClient graphClient = new GraphServiceClient(clientSecretCred, scope);
Given that you have the correct credentials/rights for the graph API as Charles Han says.
Remember that you can try the explorer the Graph API and read more in the docs about transitiveMemberOf
I would do/have done something like this
...
//{userPrincipalName} = email or id {GUID} of user
var usersTentativeGroups = new List<ADTentativeGroup>();
await SetAccessTokenInHeader();
var url = $"https://graph.microsoft.com/v1.0/users/{userPrincipalName}/transitiveMemberOf";
var jsonResp = await _client.GetAsync(url);
var result = JsonConvert.DeserializeObject<ADGroupRoot>(await jsonResp.Content.ReadAsStringAsync());
AddResultIfNotNull(usersTentativeGroups, result);
while (!string.IsNullOrEmpty(result?.NextLink))
{
await SetAccessTokenInHeader();
jsonResp = await _client.GetAsync(result.NextLink);
result = JsonConvert.DeserializeObject<ADGroupRoot>(await jsonResp.Content.ReadAsStringAsync());
AddResultIfNotNull(usersTentativeGroups, result);
}

Authentication of a user in ASP.NET Core 5 Web API

In a Web API project I am using the following method to sign in my user but at a latter point I want to get my user first name and last name. I am using Web API and .NET 5.
public AuthenticateResponse Authenticate(AuthenticateRequest model, string ipAddress)
{
var user = _userManager.Users
.Where(w => w.UserName == model.Username)
.FirstOrDefault();
// This doesn't count login failures towards account lockout
// To enable password failures to trigger account lockout, set
lockoutOnFailure: true
var result = _signInManager.PasswordSignInAsync(model.Username, model.Password, true, lockoutOnFailure: false);
User users = new User();
users.Username = model.Username;
users.Password = model.Password;
users.FirstName = user.FirstName;
users.LastName = user.LastName;
if (result.Result.Succeeded)
{
// return null if user not found
if (user == null)
return null;
}
// authentication successful so generate jwt and refresh tokens
var jwtToken = generateJwtToken(users);
var refreshToken = generateRefreshToken(ipAddress);
// save refresh token
// users.RefreshTokens.Add(refreshToken);
return new AuthenticateResponse(users, jwtToken, null);
}
I have this method in a UserService class how would one best access the values from
users.FirstName
users.LastName
from the Web API controller which could be say clubs. As you see I am using the signin manager and usermanager should I simply load an instance of that in my ClubController.
My API method is
[HttpPost]
[Route("Clubs/Create")]
public async Task<IActionResult> Create(ClubViewModelApi clubModel)
{
if (ModelState.IsValid)
{
Club _club = new Club();
_club.Name = clubModel.Name;
_club.Description = clubModel.Description;
_club.isActive = clubModel.isActive;
_club.isDeleted = clubModel.isDeleted;
_club.CreatedDate = DateTime.Now;
_club.CreatedBy = insert first lastname here;
_club.CreatedBy = User.Identity.
_context.Clubs.Add(_club);
await _context.SaveChangesAsync();
return Ok();
}
return View(clubModel);
}
I wish to insert the first name and last name here _club.CreatedBy at this point of the above api end point.
I create my token with this code when I authenticate:
private string generateJwtToken(User user)
{
var tokenHandler = new JwtSecurityTokenHandler();
var secret = _configRoot.GetValue<string>("JWTSecret");
_logger.Log(LogLevel.Information, $"JWT Secret from Everleap={secret}");
var key = Encoding.ASCII.GetBytes(secret);
var tokenDescriptor = new SecurityTokenDescriptor
{
Subject = new ClaimsIdentity(new Claim[]
{
new Claim(ClaimTypes.Name, user.Id.ToString())
}),
Expires = DateTime.UtcNow.AddMinutes(15),
SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature)
};
var token = tokenHandler.CreateToken(tokenDescriptor);
return tokenHandler.WriteToken(token);
}
My details are stored in a jwttoken. Do I go out to that token again and decrypt it on the controller level.
Response body
{
"id": 0,
"firstName": "david",
"lastName": "buckley",
"username": "davidbuckleyweb#outlook.com",
"jwtToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1bmlxdWVfbmFtZSI6IjAiLCJuYmYiOjE2MTY0MzAzNzUsImV4cCI6MTYxNjQzMTI3NSwiaWF0IjoxNjE2NDMwMzc1fQ.P8smaC0PAB5uSrBbI8bbHoc2PKbwIj7mI0jLnBuJz4s",
"refreshToken": null
}
I figured out that what I need to do is extend my claims so that its stored in the token it self I use the following to encode the token
var tokenDescriptor = new SecurityTokenDescriptor
{
Subject = new ClaimsIdentity(new Claim[]
{
new Claim("CreatedBy", user.FirstName.Substring(0,1).ToUpper() + " " + user.LastName.Substring(0,1).ToUpper()),
new Claim(ClaimTypes.Email, user.Username),
new Claim(ClaimTypes.Name, user.FirstName + " " + user.LastName),
new Claim(ClaimTypes.Role,roles)
}),
Expires = DateTime.UtcNow.AddMinutes(15),
SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature)
};
Then to decode it I use the following.
var authorization = Request.Headers[HeaderNames.Authorization];
if (AuthenticationHeaderValue.TryParse(authorization, out var headerValue))
{
// we have a valid AuthenticationHeaderValue that has the following details:
var scheme = headerValue.Scheme;
var JWTToken = headerValue.Parameter;
var token = new JwtSecurityToken(jwtEncodedString: JWTToken);
string name = token.Claims.First(c => c.Type == "CreatedBy").Value;
_club.CreatedBy = name;
}

User.Identity.IsAuthenticated Always false in SAML 2.0 using ITFoxtec

I am using itfoxtec-identity-saml2 in my Dotnet 3.1 Project. I am initiating request from server and validating the login till here everything is working fine.
After getting response assertion from server and getting claims transformed and creating a session but still my application is unable to login.
Below are snippets of my code for reference.
AuthController.cs
[Route("AssertionConsumerService")]
public async Task<IActionResult> AssertionConsumerService()
{
try
{
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);
await saml2AuthnResponse.CreateSession(HttpContext, claimsTransform: (claimsPrincipal) => ClaimsTransform.TransformClaims(claimsPrincipal),isPersistent:true, lifetime: new TimeSpan(1, 0, 0));
var auth = HttpContext.User.Identity.IsAuthenticated;
}
catch (Exception ex)
{
}
return Redirect("~/");
}
ClaimsTransform.cs
public static ClaimsPrincipal TransformClaims(ClaimsPrincipal claimsPrincipal)
{
ClaimsIdentity identity = (ClaimsIdentity)claimsPrincipal.Identity;
var tenantId = identity.FindFirst(ClaimTypes.NameIdentifier);
var Name = identity.FindFirst("firstName");
var firstName = identity.FindFirst("firstName");
var Email = identity.FindFirst("Email");
var UserID = identity.FindFirst("UserID");
var claimsToKeep = new List<Claim> { tenantId, Name,firstName, Email, UserID };
var newIdentity = new ClaimsIdentity(claimsToKeep, identity.AuthenticationType, ClaimTypes.NameIdentifier, ClaimTypes.Role);
ClaimsPrincipal newClaims = new ClaimsPrincipal(newIdentity);
return new ClaimsPrincipal(new ClaimsIdentity(claimsToKeep, identity.AuthenticationType, ClaimTypes.Name, ClaimTypes.Role)
{
BootstrapContext = ((ClaimsIdentity)claimsPrincipal.Identity).BootstrapContext
});
//return newClaims;
}
After all this my application is redirecting back to login page instead of home page of the application with logged in user.
Help will be appreciated.
You need to set the users identity claim to a claim which exist in the claim set, otherwise the user is not accepted as being authenticated.
If eg. the tenantId claim is the users identity then the users identity claim is ClaimTypes.NameIdentifier in new ClaimsPrincipal(... ClaimTypes.NameIdentifier, ClaimTypes.Role)
ClaimsTransform.cs
public static ClaimsPrincipal TransformClaims(ClaimsPrincipal claimsPrincipal)
{
ClaimsIdentity identity = (ClaimsIdentity)claimsPrincipal.Identity;
var tenantId = identity.FindFirst(ClaimTypes.NameIdentifier);
var Name = identity.FindFirst("firstName");
var firstName = identity.FindFirst("firstName");
var Email = identity.FindFirst("Email");
var UserID = identity.FindFirst("UserID");
var claimsToKeep = new List<Claim> { tenantId, Name,firstName, Email, UserID };
return new ClaimsPrincipal(new ClaimsIdentity(claimsToKeep, identity.AuthenticationType, ClaimTypes.NameIdentifier, ClaimTypes.Role)
{
BootstrapContext = ((ClaimsIdentity)claimsPrincipal.Identity).BootstrapContext
});
}

Unable to get Department Name , Manager etc using Microsoft Graph API C# Code

I am using below code :
using Microsoft.Graph;
using Microsoft.Identity.Client;
using System;
namespace MSGraphAPI
{
class Program
{
private static string clientId = "XXX";
private static string tenantID = "XXXX";
private static string objectId = "XXXX";
private static string clientSecret = "XXXXX";
static async System.Threading.Tasks.Task Main(string[] args)
{
int Flag = 0;
var tenantId = "XXX.onmicrosoft.com";
var clientId = "XXXXX";
var clientSecret = "XXXXX"; // Or some other secure place.
var scopes = new string[] { "https://graph.microsoft.com/.default" };
// Configure the MSAL client as a confidential client
var confidentialClient = ConfidentialClientApplicationBuilder
.Create(clientId)
.WithAuthority($"https://login.microsoftonline.com/XXX.onmicrosoft.com/v2.0")
.WithClientSecret(clientSecret)
.Build();
GraphServiceClient graphServiceClient =
new GraphServiceClient(new DelegateAuthenticationProvider(async (requestMessage) => {
// Retrieve an access token for Microsoft Graph (gets a fresh token if needed).
var authResult = await confidentialClient
.AcquireTokenForClient(scopes)
.ExecuteAsync();
// Add the access token in the Authorization header of the API request.
requestMessage.Headers.Authorization =
new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", authResult.AccessToken);
})
);
var users = await graphServiceClient.Users.Request().GetAsync();
do
{
foreach (User user in users)
{
Console.WriteLine(user.DisplayName);
Console.WriteLine(user.MobilePhone);
Console.WriteLine(user.BusinessPhones);
Console.WriteLine(user.DirectReports);
Console.WriteLine(user.Manager);
var directoryObject = await graphClient.Users[user.userPrincipalName].Manager
.Request()
.GetAsync();
foreach (User user1 in directoryObject )
{
// unable to print using user1.displayName
}
}
}
while (users.NextPageRequest != null && (users = await users.NextPageRequest.GetAsync()).Count > 0);
Console.WriteLine("------");
}
}
}
But, i am unable to fetch directReports or any of the user, i am able to fetch DisplayName etc.
Permission for user.Read etc is already provided in Azure app registration Portal.
I have tried user.manager also to print the details but unable to print the value. I have attached screenshot also of app access.
var directoryObject = await graphClient.Users["{id|userPrincipalName}"].Manager
.Request()
.GetAsync();
directoryObject shows the displayName in debug mode but unable to print the displayName as directoryObject.displayName.
Please Help, as i am unable to fetch the required detail.
First of all, you need to make sure that the user has a manager. You can use the Microsoft Graph explorer to check it.
Then, you need to call graphClient.Users["{id|userPrincipalName}"].Manager to get the manager.
var user = graphServiceClient.Users["user#XX.onmicrosoft.com"].Request().GetAsync().Result;
var manager = (User) graphServiceClient.Users["user#XX.onmicrosoft.com"].Manager.Request().GetAsync().Result;
Console.WriteLine("user_displayName: "+user.DisplayName);
Console.WriteLine("manager_displayName: "+manager.DisplayName);

Create AWS Cognito user with account status "CONFIRMED" and without email address

How can I create a Cognito user with the account status confirmed using c#? After a user is created the account status displays FORCE_CHANGE_PASSWORD. Another thing is I need to create user without email address.
AmazonCognitoIdentityProviderClient cognitoProvider =
new AmazonCognitoIdentityProviderClient(region);
string userName = "user";
string tempPassword = "Temp#3434";
string newPassword = "RealPass#2019";
AdminCreateUserRequest adminUserCreateRequest = new AdminCreateUserRequest()
{
UserPoolId = poolId,
Username = userName,
TemporaryPassword = tempPassword
};
AdminCreateUserResponse signUpResponse = await cognitoProvider.AdminCreateUserAsync(adminUserCreateRequest);
Admin InitiateRequest
Dictionary<string, string> initialParams = new Dictionary<string, string>();
initialParams.Add("USERNAME", userName);
initialParams.Add("PASSWORD", tempPassword);
AdminInitiateAuthRequest initialRequest = new AdminInitiateAuthRequest()
{
AuthFlow = AuthFlowType.ADMIN_NO_SRP_AUTH,
AuthParameters = initialParams,
ClientId = appClientId_tenantApi,
UserPoolId = poolId
};
AdminInitiateAuthResponse resInitAuth = await cognitoProvider.AdminInitiateAuthAsync(initialRequest);
InitiateAuthRresponse has email as a required attribute.
{[requiredAttributes, ["userAttributes.email"]]}
But the documentation doesn't say so.
For ADMIN_NO_SRP_AUTH: USERNAME (required), SECRET_HASH (if app client is configured with client secret), PASSWORD (required), DEVICE_KEY
Admin Respond to challenge
var authParameters = new Dictionary<string, string>();
authParameters.Add("USERNAME", userName);
authParameters.Add("NEW_PASSWORD", newPassword);
AdminRespondToAuthChallengeRequest adminAuthRequest = new AdminRespondToAuthChallengeRequest()
{
UserPoolId = poolId,
ClientId = appClientId_tenantApi,
ChallengeName = ChallengeNameType.NEW_PASSWORD_REQUIRED,
ChallengeResponses = authParameters,
Session = session
};
cognitoProvider.AdminRespondToAuthChallengeAsync(adminAuthRequest);
I am thinking I may missed some user settings in Cognito to avoid email. Any one have similar experience ? or is this not possible to create user without email ?
During the creation of the user pool, under general settings;attributes as in the photocognito creation on aws one is required to choose the attributes that must be present, i believe in your case the email was selected by default hence the challenge request response you got.
The admin create user request requires the client to confirm the email for purposes of verification that the user owns the email.
A hack for the same would be to allow users to sign themselves up on your cognito configuration, then sign someone up then follow with a username and password, then proceed to confirm them as an admin
var signup = await cognitoClient.SignUpAsync(new SignUpRequest
{
Username = person.Username,
ClientId = cognitoOptions.ClientId,
Password = person.IdNumber,
});
var confirm = await cognitoClient.AdminConfirmSignUpAsync(new AdminConfirmSignUpRequest
{
Username = person.Username,
UserPoolId = cognitoOptions.UserPoolId
});
In case if anyone still looking for answer
Initalize Provider.
AmazonCognitoIdentityProviderClient provider = new AmazonCognitoIdentityProviderClient("*************", "************", Amazon.RegionEndpoint.USWest);
Create user
AdminCreateUserResponse adminCreateUserResponse = await provider.AdminCreateUserAsync(new AdminCreateUserRequest
{
Username = "TestUser",
TemporaryPassword = "TempPassword#1",
UserPoolId = "us-west-**********"
});
Authenticate user
CognitoUserPool userPool = new CognitoUserPool("us-west-***", "***", provider);
CognitoUser user = new CognitoUser("TestUser", "******", userPool, provider, "**********");
InitiateSrpAuthRequest authRequest = new InitiateSrpAuthRequest()
{
Password = "TempPassword#1"
};
AuthFlowResponse authResponse = await user.StartWithSrpAuthAsync(authRequest).ConfigureAwait(false);
Vaidate user authentication result and get the user AccessToken
if (authResponse.AuthenticationResult == null)
{
if (authResponse.ChallengeName == ChallengeNameType.NEW_PASSWORD_REQUIRED)
{
//Console.WriteLine("Enter your desired new password:");
string newPassword = "NewPWD#1";// Console.ReadLine();
Dictionary<string, string> att = new Dictionary<string, string>();
att.Add("userAttributes.email", "testemail#xyz.com");
user.Attributes.Add("preferred_username", "TestUser1");
And update the new password using Accesstoken ( post update the User status will be confirmed)
authResponse = await user.RespondToNewPasswordRequiredAsync(new RespondToNewPasswordRequiredRequest()
{
SessionID = authResponse.SessionID,
NewPassword = newPassword,
},att);
accessToken = authResponse.AuthenticationResult.AccessToken;
}

Categories