I would like to allow a user to change their password in my app (not reset, actually change) by supplying the current password and new password.
Looking at the documentation I tried it, and the call doesn't fail, but then neither the old nor the new password end up working and I had to delete the user. For reference, I'll post my code:
var graphClient = GetGraphServiceClient();
var identity = new ObjectIdentity
{
Issuer = Globals.Tenant,
IssuerAssignedId = userEmail,
SignInType = "emailAddress"
};
var user = new User
{
PasswordProfile = new PasswordProfile
{
Password = newPassword
},
Identities = new ObjectIdentity[] { identity }
};
await graphClient.Users[userId]
.Request()
.UpdateAsync(user);
I surmised from documentation and looking around that this is not the correct way to do this as I would need to do it on behalf of the signed in user, and not the application. However, for AD B2C, under portal, I can't grant delegate permissions other than open_id or offline_access to my registered app.
After coming across this, I figured that I need to actually try this call with signed-in user's access token, and not the application's access token. So I assign the user's token code to a claim so I can access it later:
//in startup.auth.cs
private async Task OnAuthorizationCodeReceived(AuthorizationCodeReceivedNotification notification)
{
try
{
notification.AuthenticationTicket.Identity.AddClaim(new Claim("access_token", notification.Code));
...
//in changePassword() method:
//retrieve user access code from the claim stored previously when logging them in.
var userToken = ClaimsPrincipal.Current.FindFirst("access_token").Value;
using (var client = new HttpClient())
{
client.BaseAddress = new Uri(Globals.GraphApiEndpoint);
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", userToken);
try
{
JObject o = JObject.FromObject(new
{
currentPassword,
newPassword
});
await client.PostAsJsonAsync($"v1.0/me/changePassword/", o.ToString());
}
catch(Exception ex)
{
//never any exception. always succeeds!
}
}
The above always succeeds no matter what passwords I pass in. But the password obviously doesn't change at all. What am I doing wrong? Is there another way to do this?
Edit: seems like my first approach works if I do the following:
var user = new User
{
PasswordProfile = new PasswordProfile
{
Password = newPassword,
ForceChangePasswordNextSignIn = false
},
Identities = new ObjectIdentity[] { identity }
};
var userInfo = await graphClient.Users[userId]
.Request()
.UpdateAsync(user);
However, the issue now is that I can't first validate this using the user's old password, since it only takes in a new password and updates it. Any ideas?
Edit 2: after trying via postman and getting "x5t is invalid" when trying to use the second approach of fetching the user token and request it at "v1.0/me/changePassword/" endpoint, I was able to find this response here:
"Unfortunately, B2C tenant doesn't support this method to reset the password as there is no UserAuthenticationMethod.ReadWrite.all permission included in Graph API for B2C tenant. The only delegated permissions available in B2C tenant are offline_access and openID.The methods available to reset the password in B2C tenant are either admin performing password reset via Azure Portal or by using Password Reset Policy.
Password Reset via Graph API is only supported in Standard Azure AD tenants as of now. You can post an idea at our Feedback Portal regarding this feature in B2C tenant."
So I am guessing that the fact that I was somehow able to use graph api to change the password profile above is not supposed to happen?