How to update azure ad b2c custom user attribute using graph api - azure

I'm new to the Azure B2C .I created custom attribute extension_role against a user object.i want to update this attribute using graph api.I tried below code
public async Task UpdateUsersRole(string id)
{
IDictionary<string, object> extensionInstance = new Dictionary<string, object>();
extensionInstance.Add("extension_role", "admin");
var user = new User
{
AdditionalData = extensionInstance
};
await GraphClient.Users[id]
.Request()
.UpdateAsync(user);
}
is that correct way to update the custom attribute?.While executing i got an error also
Code: Request_BadRequestMessage: One or more property values specified
are invalid.Inner error

Please check the below code changes and also verify whether the user you are trying update has the custom attribute or not.
public static async Task UpdateCustomAtrributeUserId(GraphServiceClient graphClient)
{
Console.Write("Enter user object ID: ");
string userId = Console.ReadLine();
string CustomAtrribute = "B2C_Custom_AtrributeName";
Console.WriteLine($"Looking for user with object ID '{userId}'...");
try
{
//Get User details to Verify the existing values
var result = await graphClient.Users[userId]
.Request()
.Select($"id,givenName,surName,displayName,identities,{CustomAtrribute}")
.GetAsync();
Console.WriteLine(result);
if (result != null)
{
//Enter the New custom attribute value
Console.WriteLine("Enter custom attribute value");
string updatecustomeattribvalue = Console.ReadLine();
//Fill custom attribute value
IDictionary<string, object> extensionInstance = new Dictionary<string, object>();
extensionInstance.Add(CustomAtrribute, updatecustomeattribvalue);
//Updating the custom attribute
var updatedresult = await graphClient.Users[userId]
.Request()
.UpdateAsync(new User {
AdditionalData = extensionInstance
});
Console.WriteLine(JsonConvert.SerializeObject(updatedresult));
}
}
catch (Exception ex)
{
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine(ex.Message);
Console.ResetColor();
}
}

Related

mvc azure ad token expiration

I'm currently building a mvc5 app hosted on azure which will be in term used throught a WPF app.
As I need to check user group membership I implemented graph API by following the guidance in this article : https://azure.microsoft.com/fr-fr/documentation/samples/active-directory-dotnet-graphapi-web/
It works quite fine but some time after the user logged in the access to the following controller raise an access denied error :
public async Task<ActionResult> Index()
{
string uID = ClaimsPrincipal.Current.FindFirst("http://schemas.microsoft.com/identity/claims/objectidentifier").Value;
ActiveDirectoryClient client = AuthenticationHelper.GetActiveDirectoryClient();
IUser adUser = client.Users.Where(u => u.ObjectId == uID).ExecuteAsync().Result.CurrentPage.SingleOrDefault();
IList<Group> groupMembership = new List<Group>();
var userFetcher = (IUserFetcher)adUser;
IPagedCollection<IDirectoryObject> pagedCollection = await userFetcher.MemberOf.ExecuteAsync();
do
{
List<IDirectoryObject> directoryObjects = pagedCollection.CurrentPage.ToList();
foreach (IDirectoryObject directoryObject in directoryObjects)
{
if (directoryObject is Group)
{
var group = directoryObject as Group;
groupMembership.Add(group);
}
}
pagedCollection = await pagedCollection.GetNextPageAsync();
} while (pagedCollection != null);
ViewBag.User = adUser.UserPrincipalName;
ViewBag.UserDN = adUser.DisplayName;
ViewBag.UserGN = adUser.GivenName;
ViewBag.UserMail = adUser.Mail;
ViewBag.UserSN = adUser.Surname;
return View(groupMembership);
}
The exception is raised on GetActiveDirectoryClient(), the code of this method is a strict copy/paste from the article in the link and looks like this :
internal class AuthenticationHelper
{
public static string token;
/// <summary>
/// Async task to acquire token for Application.
/// </summary>
/// <returns>Async Token for application.</returns>
public static async Task<string> AcquireTokenAsync()
{
if (token == null || token.IsEmpty())
{
throw new Exception("Authorization Required. ");
}
return token;
}
/// <summary>
/// Get Active Directory Client for Application.
/// </summary>
/// <returns>ActiveDirectoryClient for Application.</returns>
public static ActiveDirectoryClient GetActiveDirectoryClient()
{
Uri baseServiceUri = new Uri(Constants.ResourceUrl);
ActiveDirectoryClient activeDirectoryClient =
new ActiveDirectoryClient(new Uri(baseServiceUri, Constants.TenantId), async () => await AcquireTokenAsync());
return activeDirectoryClient;
}
}
This code works perfectly right after the user has logged in but after some times the token become null and so the exception is raised.
I'm guessing this is related to some expiration time, so is there's a way to set some auto refresh on the token ?
Thanks !
Thanks for answering, I don't have yet set the [Authorize] tag as I would like to as Azure AD group membership to grant access to controllers and haven't yet figured out how to achieve it :)
It seems that appliying mofifications to the authenticationHelper solved the issue :
public static ActiveDirectoryClient GetActiveDirectoryClient()
{
Uri baseServiceUri = new Uri(Constants.ResourceUrl);
string userObjectID = ClaimsPrincipal.Current.FindFirst("http://schemas.microsoft.com/identity/claims/objectidentifier").Value;
AuthenticationContext authContext = new AuthenticationContext(Authority, new NaiveSessionCache(userObjectID));
ClientCredential credential = new ClientCredential(clientId, appKey);
ActiveDirectoryClient activeDirectoryClient = new ActiveDirectoryClient(new Uri(baseServiceUri, Constants.TenantId), async () =>
{
var result = await authContext.AcquireTokenSilentAsync(graphUrl, credential, new UserIdentifier(userObjectID, UserIdentifierType.UniqueId));
return result.AccessToken;
});
return activeDirectoryClient;
}
I don't know if that's a clean way to do thing it at least it works.

How to Submit Azure AD B2C NEW User Submission in MVC App

Loving Azure AD B2C... Looking forward to when it is out of preview!!!
Have a special case I need help putting my head around.
I have a page where I capture new email addresses via a web form.
After adding that email to my mailing list, I want to then create an AD B2C account automatically without making the user click any additional buttons using my ASP.NET MVC Site.
In reading the Article at: https://azure.microsoft.com/en-us/documentation/articles/active-directory-b2c-devquickstarts-graph-dotnet/
I see that it is possible to add a new user using the Graph API.
However, this example is written using a cmd program.
Does anyone know if there is some sample code to allow me to insert a user into AD B2C inside an MVC controller?
Heres some sample code on how I do it from ASP.Net MVC. Remeber that you need to include ClientId and Clientsecret (they are separate from the ASP.Net webapp) as explained in the article you mention. Code from controller--helperclass:
UserController:
// POST: User/Create
[HttpPost]
public async Task<ActionResult> Create(b2cuser usr)
{
try
{
usr.AlternativeSignInNamesInfo.First().Value = string.Format("{0}_{1}", usr.FirstName, usr.LastName);
usr.DisplayName = string.Format("{0} {1}", usr.FirstName, usr.LastName);
string json = Newtonsoft.Json.JsonConvert.SerializeObject(usr, Formatting.None);
Utils.GraphAPIHelper api = new Utils.GraphAPIHelper(graphAPIClientId, graphAPIClientSecret, tenant);
string res = await api.GraphPostRequest("/users/", json);
return RedirectToAction("Index");
}
catch (Exception e)
{
return View();
}
}
And in the GraphAPIHelper:
internal async Task<string> GraphPostRequest(string api, string json)
{
AuthenticationResult result = authContext.AcquireToken(graphResourceID, credential);
HttpClient http = new HttpClient();
string url = aadGraphEndpoint + tenant + api + "?" + aadGraphVersion;
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, url);
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", result.AccessToken);
request.Content = new StringContent(json, Encoding.UTF8, "application/json");
HttpResponseMessage response = await http.SendAsync(request);
if (!response.IsSuccessStatusCode)
{
string error = await response.Content.ReadAsStringAsync();
object formatted = JsonConvert.DeserializeObject(error);
throw new WebException("Error Calling the Graph API: \n" + JsonConvert.SerializeObject(formatted, Formatting.Indented));
}
return await response.Content.ReadAsStringAsync();
}
And finally, some samplecode from the model, pls note JsonProperty(Order:
public class b2cuser
{
[JsonProperty(Order = 0, PropertyName = "accountEnabled")]
public bool AccountEnabled = true;
[JsonProperty(Order = 1, PropertyName = "alternativeSignInNamesInfo")]
public List<AlternativeSignInNamesInfo> AlternativeSignInNamesInfo { get; set; }
[JsonProperty(Order = 2, PropertyName = "creationType")]
public string CreationType = "NameCoexistence";

MVC Identity 2 using FormsAuthenticationTicket

I am replacing the (HttpContext.Current.User) IPrincipal with a custom version so I can store more information login and the user. I have done this before using the FormsAuthtenticationTicket, but those other ways were based on the Memberhipship and SimpleMembership providers.
My question is, can i use the FormsAuthenticationTicket to store the cookie of my ICustomPrincipal with it interfering or breaking in OWIN Identity Pipline? I feel like would i be mixing apples and oranges.
example save:
var user = userRepository.Users.Where(u => u.Email == viewModel.Email).First();
CustomPrincipalSerializeModel serializeModel = new CustomPrincipalSerializeModel();
serializeModel.UserId = user.Id;
serializeModel.FirstName = user.FirstName;
serializeModel.LastName = user.LastName;
JavaScriptSerializer serializer = new JavaScriptSerializer();
string userData = serializer.Serialize(serializeModel);
FormsAuthenticationTicket authTicket = new FormsAuthenticationTicket(
1,
viewModel.Email,
DateTime.Now,
DateTime.Now.AddMinutes(15),
false,
userData);
string encTicket = FormsAuthentication.Encrypt(authTicket);
HttpCookie faCookie = new HttpCookie(FormsAuthentication.FormsCookieName, encTicket);
Response.Cookies.Add(faCookie);
example retrieve:
protected void Application_PostAuthenticateRequest(Object sender, EventArgs e)
{
HttpCookie authCookie = Request.Cookies[FormsAuthentication.FormsCookieName];
if (authCookie != null)
{
FormsAuthenticationTicket authTicket = FormsAuthentication.Decrypt(authCookie.Value);
JavaScriptSerializer serializer = new JavaScriptSerializer();
CustomPrincipalSerializeModel serializeModel = serializer.Deserialize<CustomPrincipalSerializeModel>(authTicket.UserData);
CustomPrincipal newUser = new CustomPrincipal(authTicket.Name);
newUser.UserId = serializeModel.UserId;
newUser.FirstName = serializeModel.FirstName;
newUser.LastName = serializeModel.LastName;
HttpContext.Current.User = newUser;
}
}
EDIT
I have this for the creating the claim
public ClaimsIdentity CreateIdentity(
LoginAttempt loginAttempt)
{
UserProfile userProfile = GetUserProfile(loginAttempt.UserName);
var applicationUser = FindById(userProfile.AspNetUserId);
ClaimsIdentity identity;
try
{
identity = UserManager.CreateIdentity(applicationUser, DefaultAuthenticationTypes.ApplicationCookie);
}
catch (Exception ex)
{
_log.Error(ex.Message, ex);
return null;
}
//UserManager.GetClaims()
identity.AddClaim(new Claim("LoginAttemptId", loginAttempt.LoginAttemptId.ToString(),ClaimValueTypes.String));
identity.AddClaim(new Claim("UserProfileId", loginAttempt.UserProfileId.ToString(), ClaimValueTypes.String));
identity.AddClaim(new Claim("SubscriptionType", userProfile.SubscriptionType, ClaimValueTypes.String));
IList<string> roles= UserManager.GetRoles(applicationUser.Id);
identity.AddClaim(new Claim(ClaimTypes.Role, roles.First()));
return identity;
}
and this for extracting
public static long GetLoginAttemptId(this IIdentity principal)
{
var claimsPrincipal = principal as ClaimsIdentity;
if (claimsPrincipal == null)
{
//throw new Exception("User is not logged in!");
return -1;
}
var nameClaim = claimsPrincipal.Claims.FirstOrDefault(c => c.Type == "LoginAttemptId");
if (nameClaim != null)
{
return Convert.ToInt64( nameClaim.Value);// as long;
}
return -1;
}
EDIT
These are the claims I am getting. I have logged off and logged back in.
There are Claims that serve exactly the same purpose. Only new API is actually purposed this way.
Claims are a basically a Dictionary<String, String> that is stored in auth-cookie and available through IPrincipal. But you don't need to do ICustomPrincipal because actual object that you get behind IPrincipal is ClaimsPrincipal and that has a list of claims.
You'd add extra information to Idnentity object just before the login:
public async override Task CreateIdentityAsync(ApplicationUser applicationUser)
{
var identity = await base.CreateIdentityAsync(applicationUser, DefaultAuthenticationTypes.ApplicationCookie);
identity.AddClaim(new Claim("MyApp:FullName", applicationUser.FullName));
return identity;
}
And then you'd be able to get this data out from IPrincipal via extension:
public static String GetFullName(this IPrincipal principal)
{
var claimsPrincipal = principal as ClaimsPrincipal;
if (claimsPrincipal == null)
{
throw new Exception("User is not logged in!");
}
var nameClaim = principal.Claims.FirstOrDefault(c => c.Type == "MyApp:FullName");
if (nameClaim != null)
{
return nameClaim.Value;
}
return String.Empty;
}
I have used this method successfully in a few projects already. See other similar answers for more code samples.
Here is another article, though I discourage from using Thread.CurrentPrincipal or ClaimsPrincipal.Current in MVC application - you don't always get what you expect, especially when user is not logged in or on early stages of AppPool start up.
This approach works for me (Using MVC4), slightly different from above.
public class ApplicationUser : IdentityUser
{
public async Task<ClaimsIdentity> GenerateUserIdentityAsync(UserManager<ApplicationUser> manager)
{
// Note the authenticationType must match the one defined in CookieAuthenticationOptions.AuthenticationType
var userIdentity = await manager.CreateIdentityAsync(this, DefaultAuthenticationTypes.ApplicationCookie);
// Add custom user claims here
userIdentity.AddClaim(new Claim("MyApp:OrganizationId", OrganizationID.ToString()));
return userIdentity;
}
public int OrganizationID { get; set; }
}
Extension method. Note you should use claimsPrincipal variable (instead of principal variable) to obtain the claims. I think that's a little mistake in the excelent answer of #trailmax (sorry for not comment this in your answer, my reputation doesn't allow me). Also, I use IIdentity instead of IPrincipal
public static class IdentityExtensions
{
public static int GetOrganizationId(this IIdentity principal)
{
var claimsPrincipal = principal as ClaimsIdentity;
if (claimsPrincipal == null)
{
throw new Exception("User is not logged in!");
}
var nameClaim = claimsPrincipal.Claims.FirstOrDefault(c => c.Type == "MyApp:OrganizationId");
if (nameClaim != null)
{
return int.Parse(nameClaim.Value);
}
throw new Exception("ID doesn't exists");
}
}
And then, I use the extension method in the controller like this:
var organizationId = User.Identity.GetOrganizationId();
Hope this is useful to someone.

How to delete user with UserManager in mvc5

I'm using mvc5, and everything about user account management I do with UserManager. It works good with roles, claims, etc. But I didn't find how to delete user with UserManager. Is there a way to delete user with UserManager? I can create Database context with dbset and then delete it from this context, but I don't want create dbcontext, userclass, etc. for one delete method.
I had issues with the above answer, though I was able to work out what's wrong. I kept getting a cascading error. Basically the user was being deleted without the role being deleted. DeleteAsync was not doing that for me (I have the latest build of Identity Framework). Ended up passing both the userid and role into my code, deleting the user from the role, then deleting the user. Seems to work fine.
[HttpPost, ActionName("Delete")]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Delete(string id, string role)
{
// Check for for both ID and Role and exit if not found
if (id == null || role == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
// Look for user in the UserStore
var user = UserManager.Users.SingleOrDefault(u => u.Id == id);
// If not found, exit
if (user == null)
{
return HttpNotFound();
}
// Remove user from role first!
var remFromRole = await UserManager.RemoveFromRoleAsync(id, role);
// If successful
if (remFromRole.Succeeded)
{
// Remove user from UserStore
var results = await UserManager.DeleteAsync(user);
// If successful
if (results.Succeeded)
{
// Redirect to Users page
return RedirectToAction("Index", "Users", new {area = "Dashboard"});
}
else
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
}
else
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
}
Delete was not supported in UserManager in 1.0, but its supported in the upcoming 2.0 release, and in the current 2.0 nightly builds if you want to preview the changes early.
Using the updated asp.net identity I have the following code:
public UserManagerController()
: this(new UserManager<User>(new UserStore<User>(new ApplicationDbContext())))
{
}
public UserManagerController(UserManager<User> userManager)
{
UserManager = userManager;
}
public UserManager<User> UserManager { get; private set; }
public async Task<ActionResult> Delete (string id)
{
var userContext = new ApplicationDbContext();
var user = UserManager.Users.SingleOrDefault(u => u.Id == id);
var userStore = new UserStore<User>(userContext);
await UserManager.DeleteAsync(user);
// var userManager = new UserManager<User>(userStore);
// await userManager.DeleteAsync(user);
return RedirectToAction("Index");
}
This one now deletes the user. It is also no need to delete from UserRoles table as that is taken care of by UserManager.DeleteAsync(user).
Hope this helps a few. I spent some time figuring out why I got some errors.
Trond

MVC5 (VS2012) UserManager doesn't sign user in

This is a continuation of this question.
If I override the userManager:
public class NHibernateAspnetUserManager<TUser> : UserManager<TUser> where TUser : IdentityUser
{
public NHibernateAspnetUserManager(IUserStore<TUser> store) : base(store)
{
}
public override Task<ClaimsIdentity> CreateIdentityAsync(TUser user, string authenticationType)
{
var identity = new ClaimsIdentity();
identity.AddClaim(new Claim(ClaimTypes.Name, user.UserName));
return Task.FromResult(identity);
}
}
This doesn't throw any errors but will not log the user in, (log process happens but #Request.IsAuthenticated will always return false). If I don't override it then I get a "System.Security.Claims.Claim..ctor" error as described in the other question. To try and solve that my own userstore implemented IUserClaimStore but simply return a new list of claims.
I am not sure what the default usermanager does under the hood that differs. I am guessing it sets up some form of claim identity object that allows MVC to recognise someone as logged in.
var identity = await UserManager.CreateIdentityAsync(user, DefaultAuthenticationTypes.ApplicationCookie);
AuthenticationManager.SignIn(new AuthenticationProperties { IsPersistent = isPersistent}, identity);
EDIT
Found out why the ctor error was occuring. The user object was coming back without the ID so the default UserManager was getting upset. Fixed that and used the default UserManager which now no longer throws an error, but still doesn't log the user in. The identity object it returns looks good from what I can tell.
FURTHER NOTE
So I installed VS2013 and copied the store and NHibernate repo across, all worked first time. I can only assume there is some sutble difference between creating it and updating MVC5 in VS2012 and doing it in VS2013.
So the main issue is that you aren't respecting the authentication type in your method, you need to create a ClaimsIdentity for DefaultAuthenticationType.ApplicationCookie, here's what the default claims factory does:
public override Task<ClaimsIdentity> CreateIdentityAsync(TUser user, string authenticationType)
{
var id = new ClaimsIdentity(authenticationType, UserNameClaimType, RoleClaimType);
id.AddClaim(new Claim(UserIdClaimType, ConvertIdToString(user.Id), ClaimValueTypes.String));
id.AddClaim(new Claim(UserNameClaimType, user.UserName, ClaimValueTypes.String));
I've faced the same problem implementing custom identity using ASP.NET 4.5. And the problem really was in adding null values into the Claims collection (see the comments):
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Register(RegisterViewModel model)
{
if (ModelState.IsValid)
{
var user = new AppUser { UserName = model.UserName };
// after the UserManager creates a user, all the properties of
// AppUser except "UserName" are automatically discarded
var result = await UserManager.CreateAsync(new AppUser
{
UserRealName = model.UserRealName,
UserName = model.UserName,
Password = model.Password
}, model.Password);
if (result.Succeeded)
{
// So we need to re-get the new user
user = AppUser.GetByName(model.UserName);
await SignInAsync(user, false); // otherwise here we will add null values ...
return RedirectToAction("Index", "Home");
}
AddErrors(result);
}
return View(model);
}
private async Task SignInAsync(AppUser user, Boolean isPersistent)
{
AuthenticationManager.SignOut(DefaultAuthenticationTypes.ApplicationCookie);
var identity = await UserManager.CreateIdentityAsync(user,
DefaultAuthenticationTypes.ApplicationCookie);
AuthenticationManager.SignIn(new AuthenticationProperties // ... into the list of
// claims for all AppUser properties except UserName
{ IsPersistent = isPersistent }, identity);
}

Resources