Microsoft Graph API - Updating password - microsoft-graph-api

I am using the Microsoft Graph API sample project. I am able to login fine.
I am trying to update the password of the user that is logged in using the following code:
public async Task<bool> UpdatePassword(GraphServiceClient graphClient, string newPassword)
{
User me = await graphClient.Me.Request().UpdateAsync(new User
{
PasswordProfile = new PasswordProfile
{
Password = newPassword,
ForceChangePasswordNextSignIn = false
},
});
return true;
}
When I execute the code, I get the following error:
{
Status: 500
Message: "No offeractions were provided for validating consent."
Internal error: "empty offerActions array."
}
Any idea what I might be doing incorrectly?
I gave access to everything "Users" related via the App Registration Portal at https://apps.dev.microsoft.com
Thank you!

There is a baked-in ChangePassword() function that you'll want to use for this:
await graphClient.Me.ChangePassword("current-pwd, "new-pwd").Request().PostAsync();

Related

.Net Core Graph API Token Issue - "Access token has expired or is not yet valid"

I am developing a .Net 6 application, hosted in an Azure App Service and using Azure AD Authentication.
When viewing a Request page, I would like to check if the user belongs to an Azure Ad Group. This works sometimes, but users will periodically get an error when trying to view the page: "Access token has expired or is not yet valid."
I assume the token is being expired as if the user clears their cookies, AAD will re-authenticate them creating a new token and all is fine again, but but I haven't been able to find anything around refreshing tokens and am not sure where to go from here.
Has anyone experienced this behaviour and found a solution for it?
Here are some relevant sections of the code
Startup.cs File:
string[] initialScopes = Configuration.GetValue<string>("GraphAPI:Scopes")?.Split(' ');
services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
.AddMicrosoftIdentityWebApp(Configuration.GetSection("AzureAd"))
.EnableTokenAcquisitionToCallDownstreamApi(options =>
{
Configuration.Bind("AzureAd", options);
}, initialScopes)
.AddInMemoryTokenCaches()
.AddMicrosoftGraph(options =>
{
options.Scopes = String.Join(' ', initialScopes);
});
AADGroupFunctions.cs
AADGroupFunctions.cs
private readonly GraphServiceClient _graphServiceClient;
public AADGroupFunctions(GraphServiceClient graphServiceClient)
{
_graphServiceClient = graphServiceClient;
}
public async Task<List<IADLookupModel>> FindUsersInGroup(string groupId)
{
var listOfUsers = new List<IADLookupModel>();
var filterString = $"startswith(mail, '{groupId}')";
var groups = await _graphServiceClient.Groups
.Request()
.Header("ConsistencyLevel", "eventual")
.Filter(filterString)
.Expand("members")
.Top(1)
.GetAsync();
if (groups.Any())
{
if (groups.First().Members.Any())
{
foreach (Microsoft.Graph.User user in groups.First().Members)
{
try
{
var mail = "";
if (user.Mail != null)
{
mail = user.Mail.ToLower();
listOfUsers.Add(new UserModel()
{
DisplayName = user.DisplayName,
UPN = user.UserPrincipalName.ToLower(),
Email = mail,
Description = user.JobTitle ?? ""
});
}
}
catch (Exception)
{
}
}
}
}
return listOfUsers;
}
Error Message when trying to call the FindUsersInGroup() function:
An unhandled exception occurred while processing the request.
ServiceException: Code: InvalidAuthenticationToken Message: Access token has expired or is not yet valid. Inner error: AdditionalData: date: 2022-02-21T17:37:46 request-id: [removed] client-request-id: [removed] ClientRequestld: [removed] Microsoft.Graph.HttpProvider.SendAsync(HttpRequestMessage request, HttpCompletionOption completionOption, CancellationToken cancellationToken)
Routing
The access token have a short lifetime, sometimes like an hour or even shorter. So you need to use a refresh token to ask AzureAd for a new access token when the current one is about to expire.
see this link https://learn.microsoft.com/en-us/azure/active-directory/develop/refresh-tokens

How can I access a Teams user's email address?

I'm trying to build a Microsoft Teams integration for an app, but I'm having some trouble getting a user's email address.
I used the Microsoft Teams extension for VS Code to scaffold a basic app. I'm using the BotFramework v4 (NodeJS) on my server. I'm able to receive requests from Teams and respond to them as well.
To get an user's email address, I am using the TeamsInfo.getMember(context, id) method, where the id is obtained from context.activity.from object. Unfortunately, calling this method results in a RestError: Unknown with a status code of 400.
I'm not sure what I'm missing here. My app is registered with the Azure Active Directory and has the User.Read.All permission. Am I missing something here?
Any help would be appreciated!
For some context, I'm trying to build a Messaging Extension Action Command.
Code:
import {
TurnContext,
TeamsActivityHandler,
CardFactory,
MessagingExtensionAction,
TeamsInfo,
} from 'botbuilder';
export default class TeamsMessagingExtensionsActionBot extends TeamsActivityHandler {
constructor() {
super();
}
// #ts-ignore
handleTeamsMessagingExtensionSubmitAction(
context: TurnContext,
action: MessagingExtensionAction,
) {
switch (action.commandId) {
case 'someCommand':
return handleCommand(context, action);
default:
throw new Error('NotImplemented');
}
}
}
async function handleCommand(
context: TurnContext,
action: MessagingExtensionAction,
) {
const card = CardFactory.heroCard(
'Some Command',
'We have received your command!',
);
const user = await TeamsInfo.getMember(context, context.activity.from.id);
console.log('User:', user);
const attachment = {
contentType: card.contentType,
content: card.content,
preview: card,
};
return {
composeExtension: {
type: 'result',
attachmentLayout: 'list',
attachments: [attachment],
},
};
}
This is the error I get when calling TeamsInfo.getMember(): JSON

Microsoft.Graph.GraphServiceClient allows me to create a user but does not allow me to change password

I have a system comprised of an Angular SPA hosted in Azure and some Azure Functions for the APIs. In an administrative app, I created an application that allows admin users to create new user accounts including specifying a password. These new accounts are able to log into the line of business app that I created as well. There is a requirement where we need to allow the same people who created the account to reset a password. For some reason, the code that I wrote to set the password does not work. It seems odd that a user can create an account, including setting the password, but for some reason the same user can't set the password independent of creating the user account. FYI, there are no emails, these are user accounts, so giving the ability to request a password reset is not an option.
Here is the error:
{
"error": {
"code": "Authorization_RequestDenied",
"message": "Access to change password operation is denied.",
"innerError": {
"date": "2021-01-19T21:58:35",
"request-id": "a1bc5b50-83e9-47ae-97c7-bda4f524fa0e",
"client-request-id": "a1bc5b50-83e9-47ae-97c7-bda4f524fa0e"
}
}
}
Here is my code:
//this is the method that works
public async Task<Microsoft.Graph.User> CreateUserAsync(string givenName,
string surname, string displayName, string userPrincipalName, string issuer,
string signInType, string initialPassword, GraphServiceClient graphClient)
{
var user = new Microsoft.Graph.User {
AccountEnabled = true,
GivenName = givenName,
Surname = surname,
DisplayName = displayName,
Identities = new List<ObjectIdentity>() {
new ObjectIdentity {
Issuer = issuer,
IssuerAssignedId = userPrincipalName,
SignInType = signInType
}
},
PasswordProfile = new PasswordProfile {
ForceChangePasswordNextSignIn = false,
Password = initialPassword
}
};
return await graphClient.Users
.Request()
.AddAsync(user);
}
//This one does not work, returns: Access to change password operation is denied.
public async Task<Microsoft.Graph.User> SetPasswordAsync(
string userName, string currentPassword, string newPassword, GraphServiceClient graphClient)
{
await graphClient.Users[userName].ChangePassword(currentPassword, newPassword).Request().PostAsync();
return something here;
}
Seems this is a permission issue here. The function:graphClient.Users[].ChangePassword()is based on reset password rest API,as the official doc indicated, only delegated permission works here and permission :UserAuthenticationMethod.ReadWrite.All is needed.
After I granted this permission to my app:
it works perfectly for me:
Let me know if you have any further questions.

Get 404 "The resource could not be found" when call /beta/informationprotection/policy/labels

according to documentation we may use the following endpoints for fetching sensitivity labels:
/me/informationProtection/policy/labels (using delegated permissions)
/informationProtection/policy/labels (using application permission. App should have InformationProtectionPolicy.Read.All permission to use this end point)
The following C# code uses app permissions and it works on tenant1:
static void Main(string[] args)
{
string accessToken = getTokenImpl().Result;
using (var client = new HttpClient())
{
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
client.DefaultRequestHeaders.Add("Accept", "application/json");
client.DefaultRequestHeaders.Add("User-Agent", "PostmanRuntime/7.24.1");
using (var response = client.GetAsync($"https://graph.microsoft.com/beta/informationprotection/policy/labels").Result)
{
using (var content = response.Content)
{
string result = content.ReadAsStringAsync().Result;
if (response.IsSuccessStatusCode)
{
Console.WriteLine(result);
}
}
}
}
}
private static async Task<string> getTokenImpl()
{
string clientId = "...";
string clientSecret = "...";
string tenant = "{...}.onmicrosoft.com";
string authority = string.Format("https://login.microsoftonline.com/{0}", tenant);
var authContext = new AuthenticationContext(authority);
var creds = new ClientCredential(clientId, clientSecret);
var authResult = await authContext.AcquireTokenAsync("https://graph.microsoft.com/", creds);
return authResult.AccessToken;
}
But it doesn't work on another tenant2 - there it always returns 404 "The resource could not be found" with the following inner exception "User not found to have labels, policy is empty". Here is full response:
{
"error": {
"code": "itemNotFound",
"message": "The resource could not be found.",
"innerError": {
"code": "notFound",
"message": "User not found to have labels, policy is empty",
"target": "userId",
"exception": null,
"date": "2020-11-18T09:29:20",
"request-id": "657ad51c-9cab-49f2-a242-50929cdc6950",
"client-request-id": "657ad51c-9cab-49f2-a242-50929cdc6950"
}
}
}
Interesting that attempt to call endpoint /me/informationProtection/policy/labels with delegated permissions on the same tenant2 gives the same error, but on tenant1 it also works. Did anybody face with this problem or have idea why it may happen? Need to mention that on tenant2 earlier we created and published several sensitivity labels for specific user - this user doesn't have neither O365 license nor Azure subscription. I.e. when you try to login to SPO/Azure and create site/group - sensitivity labels were not shown at all for this user. We tried to remove these sensitivity labels and their policies with audience targeting to this user, but both end points still return error.
PS. AAD app is Ok on tenant2 - it has InformationProtectionPolicy.Read.All permission and admin consent is granted:
Update 2020-11-25: behavior has been changed on both tenants without any change from our side: now on both tenants we get 502 Bad Gateway. Does MS rolls out this functionality globally now? Here is response which we get now from /beta/me/informationProtection/policy/labels:
{
"error":{
"code":"UnknownError",
"message":"<html>\r\n<head><title>502 Bad Gateway</title></head>\r\n<body>\r\n<center><h1>502 Bad Gateway</h1></center>\r\n<hr><center>Microsoft-Azure-Application-Gateway/v2</center>\r\n</body>\r\n</html>\r\n",
"innerError":{
"date":"2020-11-25T12:59:51",
"request-id":"93557ae1-b0d9-44a9-bbea-871f18e379ea",
"client-request-id":"93557ae1-b0d9-44a9-bbea-871f18e379ea"
}
}
}
Update 2020-12-07: it started to work by its own. I.e. MS has fixed that on backend side somehow for the tenant when this issue was reproduced.

Azure Mobile Services LoginAsync method not working with Microsoft Auth Token

I have successfully been able to get an access_token (or authenticationToken for Microsoft tokens) using the client side authentication in my Xamarin forms App. I am able to get further user information (email, name, etc.) using the same access token. Now, when I try to pass that token to my Azure Mobile Service backend, I get a 401 error.
Here is my code:
private async System.Threading.Tasks.Task<string> MSGetUserInfo(Account account)
{
// Reference: http://graph.microsoft.io/en-us/docs/overview/call_api
// Note that Microsoft don't recognize the access_token header entry, but rely instead on an Authorization header entry
var client = new HttpClient();
var userInfoRequest = new HttpRequestMessage()
{
RequestUri = new Uri("https://graph.microsoft.com/v1.0/me"),
Method = HttpMethod.Get,
};
// Add acccess Bearer
userInfoRequest.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", account.Properties["access_token"]);
using (var response = await client.SendAsync(userInfoRequest).ConfigureAwait(false))
{
if (response.IsSuccessStatusCode)
{
Models.User user = new Models.User();
var responseString = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
var jobject = JObject.Parse(responseString);
var userName = (string)jobject["userPrincipalName"];
// Check username is valid
if (String.IsNullOrEmpty(userName))
{
throw new Exception("Username was not set for authenticated user");
}
else
user.ProviderLoginId = userName;
var userDisplayName = (string)jobject["displayName"];
// Replace display name if invalid
if (String.IsNullOrWhiteSpace(userDisplayName))
{
userDisplayName = userName;
}
else
user.Name = userDisplayName;
var userEmail = (string)jobject["mail"];
// Replace email if invalid
if (String.IsNullOrWhiteSpace(userEmail))
{
userEmail = userName;
}
else
user.Email = userEmail;
Valufy.App.currentUser = user;
}
else
{
throw new Exception("OAuth2 request failed: " + await response.Content.ReadAsStringAsync().ConfigureAwait(false));
}
}
return "success";
}
The above code snippet works in getting my user details. Now when I try to use the same token in the subsequent call, I get a 404:
public async Task<bool> Authenticate(string token)
{
string message = string.Empty;
var success = false;
JObject objToken = new JObject();
//objToken.Add("access_token", token); //for facebook and google
objToken.Add("authenticationToken", token); //for microsoft
try
{
// Sign in with Facebook login using a server-managed flow.
if (user == null)
{
//ProviderAuth("MICROSOFT");
user = await syncMgr.CurrentClient
.LoginAsync(MobileServiceAuthenticationProvider.MicrosoftAccount, objToken);
if (user != null)
{
success = true;
message = string.Format("You are now signed-in as {0}.", user.UserId);
}
}
}
catch (Exception ex)
{
message = string.Format("Authentication Failed: {0}", ex.Message);
}
// Display the success or failure message.
// await new MessageDialog(message, "Sign-in result").ShowAsync();
return success;
}
Is there something that I am doing wrong? Any and all assistance is appreciated.
According to your description, I followed this Git sample about Microsoft Graph Connect Sample for UWP (REST). I could get the access_token and it could work as expected with Microsoft Graph API (e.g. Get a user). But when I use this access_token as the authenticationToken token object for MobileServiceClient.LoginAsync, I could also get 401 Unauthorized.
Then I checked the managed client for Azure Mobile Apps about Authenticate users. For Client-managed authentication flow, I found that the official code sample about using Microsoft Account is working with Live SDK as follows:
// Request the authentication token from the Live authentication service.
// The wl.basic scope should always be requested. Other scopes can be added
LiveLoginResult result = await liveIdClient.LoginAsync(new string[] { "wl.basic" });
if (result.Status == LiveConnectSessionStatus.Connected)
{
session = result.Session;
// Get information about the logged-in user.
LiveConnectClient client = new LiveConnectClient(session);
LiveOperationResult meResult = await client.GetAsync("me");
// Use the Microsoft account auth token to sign in to App Service.
MobileServiceUser loginResult = await App.MobileService
.LoginWithMicrosoftAccountAsync(result.Session.AuthenticationToken);
}
Note: As LiveConnectSession states about AuthenticationToken:
The authentication token for a signed-in and connected user.
While check the authentication with Microsoft Graph, I could only find the access_token instead of AuthenticationToken.
UPDATE:
I have checked LiveLogin for WP8 and Microsoft Account Authentication for Mobile Apps via Fiddler to capture the authorize requests. I found that MS account authentication has the similar authorize request as Live SDK.
I assumed that you need to leverage Live SDK to authenticate the user when using client side authentication with Microsoft account. I found the Live SDK download page is not exist, you could follow the Live SDK for WP8 to get started with Live SDK.
UPDATE2:
For the client-flow authentication (Microsoft Account), you could leverage MobileServiceClient.LoginWithMicrosoftAccountAsync("{Live-SDK-session-authentication-token}"), also you could use LoginAsync with the token parameter of the value {"access_token":"{the_access_token}"} or {"authenticationToken":"{Live-SDK-session-authentication-token}"}. I have tested LoginAsync with the access_token from MSA and retrieve the logged info as follows:

Resources