I am trying to update a user via Microsoft Graph API, I am able to update the DisplayName but the PasswordProfile I get an error:
Insufficient privileges to complete the operation.
Here are the roles associated to the token when I decoded the JWT token at http://jwt.io :
"roles": [
"User.ReadWrite.All",
"Directory.ReadWrite.All",
"Group.ReadWrite.All"
],
Based on the documentation it seems these permissions should suffice.
Here is my code (taken from a console app), I was able to figure out the call is failing via Fiddler, the UpdateAsync does not throw an exception.
try
{
var userId = "9a5413cd-85ff-4ad1-ab2f-b443941abd8e";
var token = GetToken().Result;
System.Console.Write($"Token: {token}");
var newPassword = "TwDx5zgHxe51DZZ";
GraphServiceClient graphClient = GetAuthenticatedClient(token);
// This works -- Updating Display name
graphClient.Users[userId].Request().UpdateAsync(new User
{
DisplayName = "NewDisplayName"
});
// This does not work - Updating password
graphClient.Users[userId].Request().UpdateAsync(new User
{
PasswordProfile = new PasswordProfile
{
Password = newPassword,
ForceChangePasswordNextSignIn = true
}
});
System.Console.WriteLine("---Update Complete---");
}
catch (Exception e)
{
System.Console.WriteLine(e);
}
Method for getting token:
public async Task<string> GetToken()
{
// Constants
var tenant = "dev-mytenantmydomaincom";
var resource = "https://graph.microsoft.com/";
var clientID = "XXXXXXXX-87ef-494d-b921-cf8956006b0e";
var secret = "zgkzas2THJLiD5XXXXXX";
// Ceremony
var authority = $"https://login.microsoftonline.com/{tenant}";
var authContext = new AuthenticationContext(authority);
var credentials = new ClientCredential(clientID, secret);
var authResult = await authContext.AcquireTokenAsync(resource, credentials);
return authResult.AccessToken;
}
Here is the full response via Fiddler:
HTTP/1.1 403 Forbidden
Cache-Control: private
Transfer-Encoding: chunked
Content-Type: application/json
request-id: 6edcf194-7705-4cd7-8144-767925cc9ee4
client-request-id: 6edcf194-7705-4cd7-8144-767925cc9ee4
x-ms-ags-diagnostic: {"ServerInfo":{"DataCenter":"East US","Slice":"SliceB","ScaleUnit":"001","Host":"AGSFE_IN_27","ADSiteName":"EST"}}
Duration: 69.2849
Date: Thu, 31 Aug 2017 13:15:34 GMT
{
"error": {
"code": "Authorization_RequestDenied",
"message": "Insufficient privileges to complete the operation.",
"innerError": {
"request-id": "6edcf194-7705-4cd7-8144-767925cc9ee4",
"date": "2017-08-31T13:15:34"
}
}
}
Passwords are a particularly sensitive data set and therefore have some unique permissions to them. From the documentation:
When updating the passwordProfile property, the following scope is required: Directory.AccessAsUser.All.
The Directory.AccessAsUser.All is a Delegated Permission that requires an Admin. In other words, it allows someone a Global Administrator to change other another user's passwordProfile.
If you're looking to allow the end user to change their password themselves, there is also a baked in ChangePassword method in the SDK:
await graphClient.Me.ChangePassword("current-pwd, "new-pwd").Request().PostAsync();
Note: that this also requires that Admin Consent be granted for DirectoryAccessAsUser.All before a user can execute it)
Keep in mind that DirectoryAccessAsUser.All is a "Delegated" rather than an "Application" permission scope. This means it is only supported by the Authorization Code and Implicit flows; it will not work for daemon/service scenarios using the Client Credentials flow.
If you consider the potential exploits that could be achieved by a non-interactive application having the ability to change user's passwords at will, the reason for this restriction is pretty clear.
An easy solution we found out is to add the application principal to the "Helpdesk administrator" role.
Go to Azure Active Directory
On the left click on Roles and administrators
Search for the Helpdesk administrator role and click on it
Click on Add assignments and paste your application object id
Wait 5 minutes or so for azure to take the changes into account
For anyone arriving at this Q&A in 2021 - there is a password reset endpoint in Graph:
POST /users/{id | userPrincipalName}/authentication/passwordMethods/{id}/resetPassword
You will have to retrieve the id of the password authentication method first.
The permission needed for this operation is UserAuthenticationMethod.ReadWrite.All which can be granted as an Application type permission.
See the documentation: https://learn.microsoft.com/en-us/graph/api/passwordauthenticationmethod-resetpassword
Update
Even though the permission can be granted as an Application type permission, the call will not succeed in Application Context.
See https://learn.microsoft.com/en-us/answers/questions/246207/34upn-from-claims-with-value-null-is-not-a-valid-u.html
Related
I'm using this code
var app = ConfidentialClientApplicationBuilder.Create(AzureAdApplicationId)
.WithTenantId("organizations")
.WithRedirectUri(AzureAdRedirectUrl)
.WithClientSecret(AzureAdSecretKey)
.Build();
azureAdScopes = new List<string>() { "email" };
var signInRequest = app.GetAuthorizationRequestUrl(azureAdScopes);
var uri = await signInRequest.ExecuteAsync();
which produces the url
https://login.microsoftonline.com/organizations/oauth2/v2.0/authorize?scope=email+openid+profile+offline_access&...
All I need is the user's username and I don't need offline access to the user's account. How can I remove them from the scope?
You could request the url without offline_access, but Azure AD v2.0 OAuth2 Account Consent Page automatically lists "Access your data anytime" even though offline_access is not specified in scope. This is an issue related.
The Note shows in the document:
At this time, the offline_access ("Maintain access to data you have
given it access to") and user.read ("Sign you in and read your
profile") permissions are automatically included in the initial
consent to an application.
We are trying to query shifts in the Microsoft Graph API using a C# app, now that StaffHub got deprecated , in the past we were getting an Unknown Error which looked like a permissions issue.
In the docs I noticed permissions for Schedule.ReadAll and Schedule.ReadWriteAll so I added them to the application permissions in our App Registration in Azure.
Now when we send the request to https://graph.microsoft.com/beta/teams/{teamid}/schedule we get this error:
Microsoft.Graph.ServiceException: 'Code: Forbidden Message: {"error":{"code":"Forbidden","message":"MS-APP-ACTS-AS header needs to be set for application context requests.","details":[],"innererror":{"code":"MissingUserIdHeaderInAppContext"}}}
The documentation says the Schedule permissions are in private preview, are these required for querying a schedule & shifts, and if so, is it possible to request access to the private preview?
I'm in the same situation. It's possible to request private preview access (we have), but I'm guessing that it's primarily granted to Microsoft partners or at least have a connection at Microsoft.
The workaround for me has been getting access on behalf of a user. It does however require the user to enter username and password in order to get an access token, so it might not be a perfect solution for you. But it works. You need to add (and, I believe, grant admin consent for) delegated permissions for this to work, either Group.Read.All or Group.ReadWrite.All.
Edit:
I've got it working now. We have private preview access, so I'm not sure this will help you unless you do too, but as I understand it will be available eventually. Given your question, I presume you already have an access token.
Add MS-APP-ACT-AS as a header with the user ID of the user you want the Graph client to act as.
If you're using the Graph SDK for .NET Core you can just add a header to the authentication provider:
public IAuthenticationProvider GetAuthenticationProviderForActingAsUser(string userId, string accessToken)
{
return new DelegateAuthenticationProvider(
requestMessage =>
{
requestMessage.Headers.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
// Get event times in the current time zone.
requestMessage.Headers.Add("Prefer", "outlook.timezone=\"" + TimeZoneInfo.Local.Id + "\"");
requestMessage.Headers.Add("MS-APP-ACTS-AS", userId);
return Task.CompletedTask;
});
}
Then you call the graph service client:
var authenticationProvider = GetAuthenticationProviderForActingAsUser(userId, accessToken);
var graphClient = new GraphServiceClient(authenticationProvider);
You should then be able to fetch the shifts:
var shifts = await graphClient.Teams[teamId].Schedule.Shifts
.Request()
.AddAsync(shift);
I created an app in Azure and set it up to use Access and ID tokens.
I want to connect to different tenants and read SharePoint sites. Here are the permissions I've requested and received Admin Consent for:
For now, I have set up an App Secret but I do plan to move to a certificate later.
I have this code to get the access token and I do get an access token back:
const params = new URLSearchParams();
params.append("grant_type", "client_credentials");
params.append("scope", "https://graph.microsoft.com/.default");
params.append("client_id", process.env.client_id);
params.append("client_secret", process.env.client_secret);
var url = `https://login.microsoftonline.com/${tenant}/oauth2/v2.0/token`;
const response = await fetch(url,
{
method: 'POST',
body: params,
headers: { 'Content-Type': 'application/x-www-form-urlencoded' }
}
);
However when I try to read the root site below
var url = "https://graph.microsoft.com/v1.0/sites?search=*";
const response = await fetch(url,
{
method: 'GET',
headers: { 'Authorization': `Bearer ${access_token}` }
}
);
I get this error:
error: {
code: 'AccessDenied',
message: 'Either scp or roles claim need to be present in the token.',
innerError: {
'request-id': 'ec47913f-2624-4d1c-9b27-5baf05ccebfd',
date: '2019-08-16T14: 15: 37'
}
}
I checked the token at https://jwt.io/ and indeed I do not see any entry for roles or scp.
It looks like I missed a step but I cannot figure out which step.
I am getting the token like this:
https://login.microsoftonline.com/${tenant}/oauth2/v2.0/token
What am I doing incorrectly?
The first thing to understand is that you cannot receive both Application and Delegated permissions in the same token, it is an either/or scenario. Which type you receive depends entirely on which OAuth Grant you used to request the token:
Authorization Code and Implicit return Delegated tokens with an scp property
Client Credentials return Application tokens with a roles property
The second thing is that you've requested scopes to two different APIs. Based on what you've selected, you won't have access to SharePoint through the Microsoft Graph because you've only requested access to the legacy SharePoint API. More importantly, you've only requested the Delegated User.Read scope for Graph so when you use Client Credentials to obtain the token, that token won't have any permissions.
In order to obtain an Application token for reading SharePoint sites, you'll need Sites.Read.All Microsoft Graph Application permission selected.
I am trying to use Microsoft Graph API to update another user in Active Directory.
I have the following permissions set for both user and application at https://apps.dev.microsoft.com/
I've requested the following scopes:
Directory.ReadWrite.All
User.ReadWrite.All
Group.ReadWrite.All
I am able to get a listing of all users in the directory, but when trying to update (in this case, the city) it fails:
GraphServiceClient graphClient = SDKHelper.GetAuthenticatedClient ();
var usersResponse = graphClient.Users.Request ().GetAsync ();
var users = usersResponse.Result;
// hard coding user id for now
var userId = "9a5b83cd-85ff-4ad1-ab2f-b443941a518e";
var user = users.FirstOrDefault (m => m.Id == userId);
if (user != null) {
user.City = "New York";
await graphClient.Me.Request ().UpdateAsync (user);
}
I get:
{
Code : Authorization_RequestDenied
Message : Insufficient privileges to complete the operation.
Inner error
}
The user I am logged in as is a Global Administrator of the directory.
I took the JWT token, headed over to https://jwt.io and these are the roles I am seeing:
Directory.Read.All
Directory.ReadWrite.All
Files.ReadWrite
Group.ReadWrite.All
Mail.Send
User.Read
User.Read.All
User.ReadWrite.All
Do I need other permissions to make this happen?
At the end of the day, I'd like to create a console app (not web app) that I can update other user information in the directory. But I figured using this sample app provided by Microsoft is a good start.
The reason you're seeing this is because you're passing the complete user object rather than only the city property. In other words, you're attempting to update every property in that user record, including several that are read-only.
This is one of those cases where having an SDK that wraps a REST API can be result in some confusing errors. As REST API, it is stateless so passing in the entire user property set is telling the API you want to PATCH all of those values.
You're also passing in a different user object into the me object (i.e. you're replacing all of your property values with this other user's property values):
await graphClient.Me.Request().UpdateAsync(user);
Instead, try this:
GraphServiceClient graphClient = SDKHelper.GetAuthenticatedClient ();
// hard coding user id for now
var userId = "9a5b83cd-85ff-4ad1-ab2f-b443941a518e";
await graphClient.Users[userId].Request ().UpdateAsync(new User
{
City = "New York"
});
I have a Angular 4 site that I’m trying to use Microsoft Graph implicit flow to authenticate users then use token to call our APIs at another endpoint, so I use msal.js to get the access token.
After I bring the access token to my API endpoint and try to valid it, the token cannot be valid. I got a SignatureVerificationFailedException.
My understanding is that the access token is for Microsoft Graph API, not for my APIs, so I cannot valid it. (I can use it to call Graph API without problem)
How can I get a access token(not id token) using msal.js that can be used for my APIs but not Microsoft Graph? Thanks!
The reason I'm sending access token instead of id token to the API endpoint is that I want to get the puid claim from the token, which is not available for id token.
Here is what I was trying to valid the access token I got from client which is using msal.js
const string authority = "https://login.microsoftonline.com/common";
const string audience = "https://graph.microsoft.com";
string issuer = null;
string stsDiscoveryEndpoint = $"{authority}/v2.0/.well-known/openid-configuration";
List<SecurityToken> signingTokens = null;
var configManager = new ConfigurationManager<OpenIdConnectConfiguration>(stsDiscoveryEndpoint);
var config = await configManager.GetConfigurationAsync();
issuer = config.Issuer;
signingTokens = config.SigningTokens.ToList();
var tokenHandler = new JwtSecurityTokenHandler();
var validationParameters = new TokenValidationParameters
{
ValidAudience = audience,
ValidIssuer = issuer,
ValidateIssuer = false,
IssuerSigningTokens = signingTokens,
CertificateValidator = X509CertificateValidator.None
};
try
{
// Validate token.
SecurityToken validatedToken = new JwtSecurityToken();
var claimsPrincipal = tokenHandler.ValidateToken(jwtToken, validationParameters, out validatedToken);
var claimsIdentity = claimsPrincipal.Identity as ClaimsIdentity;
return ExtractAuthenticatedUserFromClaimsIdentity(claimsIdentity);
}
catch (SignatureVerificationFailedException)
{
throw;
}
Thanks,
If you want to get an access token for your API rather than the Microsoft Graph API, you must specify your API as the resource in the token request.
Make sure that:
Your Web API has configured OAuth2Permission Scopes. See here. Configuring a resource application to expose web APIs
Your Client Application has selected permissions to those exposed APIs. Configuring a client application to access web APIs
Finally, make sure you use your Web API's App ID URI or App ID GUID as the resource value in your token request.
Let me know if this helps!