Change password using Graph API (Azure AD B2C) - azure

From angular front-end and webapi as back-end, I'm trying to consume Graph API change password function, but I get following error:
{"odata.error":{"code":"Authorization_RequestDenied","message":{"lang":"en","value":"Access to change password operation is denied."}}}
Below is my code:
private async void ChangePasswordPostRequest(ChangePasswordModel changePasswordModel){
AuthenticationResult result = await authContext.AcquireTokenAsync(ApplicationConstants.aadGraphResourceId, credential);
HttpClient http = new HttpClient();
string url = ApplicationConstants.aadGraphEndpoint + tenant + "/users/" + "c55f7d4d-f81d-4338-bec7-145225366565" + "/changePassword?" + ApplicationConstants.aadGraphVersion;
HttpRequestMessage request = new HttpRequestMessage(new HttpMethod("POST"), url);
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", result.AccessToken);
request.Content = new StringContent(JsonConvert.SerializeObject(new ChangePasswordPostModel() { currentPassword = changePasswordModel.CurrentPassword, newPassword = changePasswordModel.NewPassword }), Encoding.UTF8, "application/json");
HttpResponseMessage response = await http.SendAsync(request);
if (!response.IsSuccessStatusCode)
{
string error = await response.Content.ReadAsStringAsync();
object formatted = JsonConvert.DeserializeObject(error);
}
}
I'm stuck at this, any help will be appreciated. Thanks in advance.

The change password operation can only be called on behalf of the
signed-in user. An application can change the password for a user
using the reset password operation. The application must be assigned
to the user account administrator role to change the password for the
user. #Chris Padgett
With the beta endpoint of the Graph API it's now possible to get it done without PowerShell!
//Get App ObjectId
https://graph.microsoft.com/beta/servicePrincipals?$filter=appId eq '{appId}'
//Get roleId User Account Administrator role
GET: https://graph.microsoft.com/v1.0/directoryRoles?$filter=roleTemplateId eq 'fe930be7-5e62-47db-91af-98c3a49a38b1'
//If not found //Activate
POST: https://graph.microsoft.com/v1.0/directoryRoles
{
"displayName": "User Account Administrator",
"roleTemplateId": "fe930be7-5e62-47db-91af-98c3a49a38b1"
}
//Add member
POST: https://graph.microsoft.com/beta/directoryRoles/{Id}/members/$ref
{
"#odata.id": "https://graph.microsoft.com/beta/servicePrincipals/{Id returned in first request}"
}

The change password operation can only be called on behalf of the signed-in user.
An application can change the password for a user using the reset password operation.
The application must be assigned to the user account administrator role to change the password for the user.

Related

Unable to generate token for One-Drive using c# .net 6

I am facing issue while trying to generated token for One-Drive access. As I have requirement where user can get all the files from there One Drive using my application.
I tried below code but I am getting error.
{"error":"invalid_grant","error_description":"AADSTS65001: The user or administrator has not consented to use the application with ID. Send an interactive authorization request for this user and resource.\r\nTrace ID: 33a0dd6a-6984-4c0a-8f74-6fbcd9c54301\r\nCorrelation ID: 265ca054-ab98-450c-8281-851ef6b0fdc3\r\nTimestamp: 2022-11-24 15:56:04Z","error_codes":[65001],"timestamp":"2022-11-24 15:56:04Z","trace_id":"33a0dd6a-6984-4c0a-8f74-6fbcd9c54301","correlation_id":"265ca054-ab98-450c-8281-851ef6b0fdc3","suberror":"consent_required"}
Find my code that I am trying.
public async Task GetTokenAsync(string tenant, string clientId, string
clientSecret, string username, string password)
{
HttpResponseMessage resp;
string token;
using (var httpClient = new HttpClient())
{
httpClient.DefaultRequestHeaders.Accept.Add(
new ("application/x-www-form-
urlencoded"));
var req = new HttpRequestMessage(HttpMethod.Post, $"https://login.microsoftonline.com/{tenant}/oauth2/token/");
req.Content = new FormUrlEncodedContent(new Dictionary<string, string>
{
{"grant_type", "password"},
{"client_id", clientId},
{"client_secret", clientSecret},
{"resource", "https://graph.microsoft.com/"},
{"username", username},
{"password", password}
});
resp = await httpClient.SendAsync(req);
string content = await resp.Content.ReadAsStringAsync();
var jsonObj = System.Text.Json.JsonSerializer.Deserialize<dynamic>(content);
token = jsonObj["access_token"];
}
return token;
}
Nothing
I tried to reproduce the same in my environment and got the same error as below:
The error usually occurs if you have not consented Admin Consent to the API Permissions you have granted like below:
To resolve the error, I created the Azure AD Application and granted Admin consent to the API Permissions:
To generate the access token, I used to the below parameters:
GET https://login.microsoftonline.com/TenantId/oauth2/token
grant_type:password
client_id:53f9d6e0-f9e8-4620-9e45-XXXXX
client_secret:Msd8Q~q~-2gb4sooQVGDIQQAI92gXXXXX
resource:https://graph.microsoft.com/
username:username
password:Password
I am able to generate the access token successfully as below:
If still the issue persists, try updating the below settings in Azure Enterprise Application like below:
Go to Azure Active Directory -> Enterprise applications -> User settings -> Consent and permissions
Otherwise, you can Grant admin consent by using below Endpoint, Login as Admin and Accept:
https://login.microsoftonline.com/TenantId/adminconsent?client_id=ClientID

How to query MS Graph API in User Context?

I'm trying to change a user's password using MS Graph API. I was checking earlier questions like this and this where the answer were always similar: register an AAD application, because changing the password requires Delegated
UserAuthenticationMethod.ReadWrite.All permissions, and you cannot set that in a B2C application as a B2C app supports only offline_access and openid for Delegated.
So the answers were always suggesting creating an AAD app, and using this app I could query the Graph API on behalf of the user. The question is, how to achieve this? If I check the documentation from Microsoft: Get access on behalf of a user, it is saying that first you need to get authorization, only then you can proceed to get your access token.
But as part of the authorization process, there is a user consent screen. If I'm calling my ASP.NET Core Web API endpoint to change my password on behalf of my user, how will it work on the server? The client won't be able to consent, if I'm doing these calls on the server, right?
Also, I'm using Microsoft.Graph and Microsoft.Graph.Auth Nuget packages and it's not clear how to perform these calls on behalf of the user. I was trying to do this:
var client = new GraphServiceClient(new SimpleAuthProvider(authToken));
await client.Users[myUserId]
.ChangePassword(currentPassword, newPassword)
.Request()
.PostAsync();
Where SimpleAuthProvider is just a dummy IAuthProvider implementation.
Any ideas how to make this work?
OK, got it:
static void ChangePasswordOfAUser()
{
var myAzureId = "65e328e8-5017-4966-93f0-b651d5261e2c"; // id of B2C user
var currentPassword = "my_old_pwd";
var newPassword = "newPassword!";
using (var client = new HttpClient())
{
var passwordTokenRequest = new PasswordTokenRequest
{
Address = $"https://login.microsoftonline.com/{tenant}/oauth2/v2.0/token",
ClientId = clientId, // client ID of AAD app - not the B2C app!
ClientSecret = clientSecret,
UserName = $"{myAzureId}#contoso.onmicrosoft.com",
Password = currentPassword,
Scope = "https://graph.microsoft.com/.default" // you need to have delegate access
};
var response = client.RequestPasswordTokenAsync(passwordTokenRequest).Result;
var userAccessToken = response.AccessToken;
client.DefaultRequestHeaders.Add("Authorization", $"Bearer {userAccessToken}");
var json = System.Text.Json.JsonSerializer.Serialize(new
{
currentPassword = currentPassword,
newPassword = newPassword
});
var changePasswordResponse = client.PostAsync(
$"https://graph.microsoft.com/v1.0/users/{myAzureId}/changePassword",
new StringContent(json, Encoding.UTF8, "application/json"))
.Result;
changePasswordResponse.EnsureSuccessStatusCode();
}
}

Obtaining a valid access token for Microsoft Graph API

I am working on an ASP.NET MVC5 Web App that uses Azure ADAL libraries to authenticate users, it works fine, however, when I manually send requests to graph, ex: GET https://graph.microsoft.com/v1.0/me or GET https://graph.microsoft.com/v1.0/groups?$filter=from/displayName eq 'whatever'.
I have tried updating the App Registration in Azure as to add the required Graph permissions, and I have also tried creating new app registrations, no matter what I do my requests will always respond 401 Unauthorized, is there anything I am missing?
EDIT: Example response from Postman
{
"error": {
"code": "InvalidAuthenticationToken",
"message": "Access token validation failure.",
"innerError": {
"request-id": "a142576b-acce-4e59-8a8d-adede61aaf59",
"date": "2017-04-05T13:27:36"
}
}
}
EDIT: C# Request Example
public async Task<GroupGraph> GetGroupIdByDisplayName(string displayName)
{
var accessToken = await authenticationService.GetTokenUserOnly();
GroupGraph groupGraphResponse = null;
using (var client = new HttpClient())
{
using (var request = new HttpRequestMessage(HttpMethod.Get, $"https://graph.microsoft.com/v1.0/groups?$filter=from/displayName eq '{displayName}'"))
{
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
using (var response = client.SendAsync(request).Result)
{
if (response.IsSuccessStatusCode)
{
using (var content = response.Content)
{
var result = await content.ReadAsStringAsync();
groupGraphResponse = JsonConvert.DeserializeObject<GroupGraph>(result);
}
}
}
}
}
return groupGraphResponse;
}
EDIT: The way I obtain the token
public async Task<string> GetTokenUserOnly()
{
string signedInUserID = ClaimsPrincipal.Current.FindFirst(ClaimTypes.NameIdentifier).Value;
string tenantID = ClaimsPrincipal.Current.FindFirst("http://schemas.microsoft.com/identity/claims/tenantid").Value;
string userObjectID = ClaimsPrincipal.Current.FindFirst("http://schemas.microsoft.com/identity/claims/objectidentifier").Value;
// get a token for the Graph without triggering any user interaction (from the cache, via multi-resource refresh token, etc)
ClientCredential clientcred = new ClientCredential(clientId, appKey);
// initialize AuthenticationContext with the token cache of the currently signed in user, as kept in the app's database
AuthenticationContext authenticationContext = new AuthenticationContext(aadInstance + tenantID, new TableTokenCache(signedInUserID));
//AuthenticationResult authenticationResult = await authenticationContext.AcquireTokenSilentAsync(graphResourceID, clientcred, new UserIdentifier(userObjectID, UserIdentifierType.UniqueId));
AuthenticationResult authenticationResult = authenticationContext.AcquireToken(graphResourceID, clientcred);
return authenticationResult.AccessToken;
}
You can't use ADAL to get tokens for graph.microsoft.com. ADAL is for graph.windows.net.
In order to get tokens for the Graph library (graph.windows.com) look into the Nuget Package Microsoft.Graph. Microsoft also has some documentation on how to pull user info using Graph.
Be forewarned though, using Graph Libraries and ADAL libraries side by side can lead to some weird side effects, such as the credential cache being cleared.
It seems you are using the client credential grant flow to acquire the access token for graph api(graphResourceID is https://graph.microsoft.com ?) :
AuthenticationResult authenticationResult = authenticationContext.AcquireToken(graphResourceID, clientcred);
So you need to grant app permission in azure ad portal :
For error "Access token validation failure" , you could use online tool like http://jwt.calebb.net/ to decode your access token , check the audience or lifetime of the access token .
To obtain a valid token for Microsoft Graph API you can use Azure.Identity.
To use any implementation of TokenCredential we need to build our own IAuthenticationProvider.
public class TokenCredentialAuthenticationProvider : IAuthenticationProvider
{
private readonly TokenCredential _tokenCredential;
public TokenCredentialAuthenticationProvider(TokenCredential tokenCredential)
{
_tokenCredential = tokenCredential;
}
public async Task AuthenticateRequestAsync(HttpRequestMessage request)
{
var accessToken = await _tokenCredential.GetTokenAsync(new TokenRequestContext(new[] { "https://graph.microsoft.com" }), CancellationToken.None);
request.Headers.Authorization = new AuthenticationHeaderValue("bearer", accessToken.Token);
}
}
Now we can for instance use AzureCliCredential to acquire an access token.
Open Powershell and type in az login in order to login with your Azure AD account.
In Azure you could also use Managed Identity to get a token based on a Azure resource e.g. Azure App Service. Here need to use ManagedIdentityToken.
Usage:
var client = new GraphServiceClient(new TokenCredentialAuthenticationProvider(new AzureCliCredential()));
var user = await client.Me.Request().GetAsync();

Access token validation failure when creating Microsoft Graph webhook using the "Web API on-behalf-of flow"

What I am trying to do is to use the "Web API on-behalf-of flow" scenario Microsoft described in this article to create a web hook.
So I started with the Microsoft github example and made sure that I can successfully get the users profile via the Graph API.
Then I modified the code where it gets the users profile to create the web hook, so the code looks like this:
// Authentication and get the access token on behalf of a WPF desktop app.
// This part is unmodified from the sample project except for readability.
const string authority = "https://login.microsoftonline.com/mycompany.com";
const string resource = "https://graph.windows.net";
const string clientId = "my_client_id";
const string clientSecret = "my_client_secret";
const string assertionType = "urn:ietf:params:oauth:grant-type:jwt-bearer";
var user = ClaimsPrincipal.Current.FindFirst(ClaimTypes.NameIdentifier).Value;
var authenticationContext = new AuthenticationContext(authority,new DbTokenCache(user));
var assertion = ((BootstrapContext) ClaimsPrincipal.Current.Identities.First().BootstrapContext).Token;
var userName = ClaimsPrincipal.Current.FindFirst(ClaimTypes.Upn) != null
? ClaimsPrincipal.Current.FindFirst(ClaimTypes.Upn).Value
: ClaimsPrincipal.Current.FindFirst(ClaimTypes.Email).Value;
var result = await authenticationContext.AcquireTokenAsync(resource,new ClientCredential(clientId,clientSecret),new UserAssertion(assertion,assertionType,userName));
var accessToken = result.AccessToken;
// After getting the access token on behalf of the desktop WPF app,
// subscribes to get notifications when the user receives an email.
// This is the part that I put in.
var subscription = new Subscription
{
Resource = "me/mailFolders('Inbox')/messages",
ChangeType = "created",
NotificationUrl = "https://mycompany.com/subscription/listen",
ClientState = Guid.NewGuid().ToString(),
ExpirationDateTime = DateTime.UtcNow + new TimeSpan(0, 0, 4230, 0)
};
const string subscriptionsEndpoint = "https://graph.microsoft.com/v1.0/subscriptions/";
var request = new HttpRequestMessage(HttpMethod.Post, subscriptionsEndpoint);
var contentString = JsonConvert.SerializeObject(subscription, new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore });
request.Content = new StringContent(contentString, System.Text.Encoding.UTF8, "application/json");
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
var response = await new HttpClient().SendAsync(request);
if (response.IsSuccessStatusCode)
{
// Parse the JSON response.
var stringResult = await response.Content.ReadAsStringAsync();
subscription = JsonConvert.DeserializeObject<Subscription>(stringResult);
}
The error I get from the response is:
{
"error":
{
"code": "InvalidAuthenticationToken",
"message": "Access token validation failure.",
"innerError":
{
"request-id": "f64537e7-6663-49e1-8256-6e054b5a3fc2",
"date": "2017-03-27T02:36:04"
}
}
}
The webhook creation code was taken straight from the ASP.NET webhook github sample project, which, I have also made sure that I can run successfully.
The same access token code works with the original user profile reading code:
// Call the Graph API and retrieve the user's profile.
const string requestUrl = "https://graph.windows.net/mycompany.com/me?api-version=2013-11-08";
request = new HttpRequestMessage(HttpMethod.Get, requestUrl);
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
var response = await new HttpClient().SendAsync(request);
So I want to find out:
Is creating a webhook via the graph API using the on-behalf-of flow even supported? Not sure if this SO question is what I'm looking for here.
If it is supported, what am I missing here?
If it is not supported, is there an alternative to achieve it? E.g. is there anything from the existing Office 365 API that I can use?
"message": "Access token validation failure.",
The error means you got incorrect access token for the resource . According to your code ,you get the access token for resource :https://graph.windows.net( Azure AD Graph API) , But then you used that access token to access Microsoft Graph API(https://graph.microsoft.com) ,so access token validation failed .

Graph API is not working via project code but same URL is working via AD Graph Explorer

I have download a MVC application from Git for AAD graph API. I ran this application but each time i am not getting expected result.
To find the error i run same api using postman and generated token below was the response.
{
"odata.error": {
"code": "Request_ResourceNotFound",
"message": {
"lang": "en",
"value": "Resource not found for the segment 'me'."
}
}
}
I am using below Get URL-
https://graph.windows.net/XXXXX/me?api-version=1.6
Also, to verify is it working with AAD Grapg api explorer. After log in everything is working fine.
Below is my code to call Grapg API-
// Get the access token from the cache
string userObjectID =
ClaimsPrincipal.Current.FindFirst("http://schemas.microsoft.com/identity/claims/objectidentifier")
.Value;
string authority = String.Format(CultureInfo.InvariantCulture, aadInstance, tenant);
//AuthenticationContext authContext = new AuthenticationContext(authority,
// new NaiveSessionCache(userObjectID));
ClientCredential credential = new ClientCredential(clientId, appKey);
AuthenticationContext authContext = new AuthenticationContext(authority, true);
result = await authContext.AcquireTokenAsync(graphResourceId.ToString(), credential);
var Token = result.AccessToken;
//// AcquireTokenSilentAsync
//result = await authContext.AcquireTokenSilentAsync(graphResourceId, credential,
// new UserIdentifier(userObjectID, UserIdentifierType.UniqueId));
// Call the Graph API manually and retrieve the user's profile.
string requestUrl = String.Format(
CultureInfo.InvariantCulture,
graphUserUrl,
HttpUtility.UrlEncode(tenantId));
HttpClient client = new HttpClient();
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, requestUrl);
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", result.AccessToken);
HttpResponseMessage response = await client.SendAsync(request);
// Return the user's profile in the view.
if (response.IsSuccessStatusCode)
{
string responseString = await response.Content.ReadAsStringAsync();
profile = JsonConvert.DeserializeObject<UserProfile>(responseString);
}
Could you guys please tell me what is the problem with my code. Why it is working on AAD explorer not with Localhost.
To request the me endpoint of Azure AD Graph REST, we need to use the delegate token which represents the sign-in user.
The code above you acquire token using the Client Credential flow is request the access token which represents for the application which doesn't contain the info of sign-in user.
To achieve this in the MVC application, we need to acquire the token after you get the authorization code when users login. Next time, we can acquire the token from the token cache based on the login user.
Here is the code for your reference( code sample from here):
result = await authContext.AcquireTokenSilentAsync(graphResourceId, credential,
new UserIdentifier(userObjectID, UserIdentifierType.UniqueId));

Resources