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

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

Related

What permissions are needed to read the Azure AD users' calendar via Graph API

I need to access the calendars of Azure AD users. I do it with the code below:
var scopes = new[] { "https://graph.microsoft.com/.default" };
var tenantId = "MY_TENANT_ID";
var clientId = "APP_ID";
var clientSecret = "APP_SECRET";
var options = new TokenCredentialOptions
{
AuthorityHost = AzureAuthorityHosts.AzurePublicCloud
};
var clientSecretCredential = new ClientSecretCredential(
tenantId, clientId, clientSecret, options);
var graphClient = new GraphServiceClient(clientSecretCredential, scopes);
var user = await graphClient.Users["USER_ID"]
.Calendar.Events
.Request()
.GetAsync();
As a result, I get the following error:
Status Code: Unauthorized
Microsoft.Graph.ServiceException: Code: NoPermissionsInAccessToken
Message: The token contains no permissions, or permissions can not be understood.
In Azure AD->App Registrations->API permissions I have got Calendars.Read permission, but don't have User.Read.All permission. So the question is can I get data from calendars without User.Read.All permission and if I can what am I doing wrong?
According to your code snippet, you used client credential flow to obtain the authentication and call graph api to list all calendar events for a specific user.
So you need to make your azure ad application to get Calendar.Read application permission. Without the permission then you can't call the api successfully
How to add application permission:

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();
}
}

Change password using Graph API (Azure AD B2C)

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.

Resource not found for the segment 'me'

i'm using Graph API to retrieve profile information of user who's currently logged in from the Azure AD, unfortunately i'm receiving the following error message :
{"odata.error":{"code":"Request_ResourceNotFound","message":{"lang":"en","value":"Resource not found for the segment 'me'."}}}
Below is my code :
Uri serviceRoot = new Uri(serviceRootURL);
ActiveDirectoryClient adClient = new ActiveDirectoryClient(
serviceRoot,
async () => await GetAppTokenAsync());
var user = (User)await adClient.Me
.Expand(x => x.Manager)
.ExecuteAsync();
And below is my code for GetAppTokenAsync() :
private static async Task<string> GetAppTokenAsync()
{
// Instantiate an AuthenticationContext for my directory (see authString above).
AuthenticationContext authenticationContext = new AuthenticationContext(authString, false);
// Create a ClientCredential that will be used for authentication.
// This is where the Client ID and Key/Secret from the Azure Management Portal is used.
ClientCredential clientCred = new ClientCredential(clientID, clientSecret);
// Acquire an access token from Azure AD to access the Azure AD Graph (the resource)
// using the Client ID and Key/Secret as credentials.
AuthenticationResult authenticationResult = await authenticationContext.AcquireTokenAsync(resAzureGraphAPI, clientCred);
// Return the access token.
return authenticationResult.AccessToken;
}
From your code "await GetAppTokenAsync()" , you are getting an app-only token , which using application identity, instead of as a user's identity .
The "(User)await adClient.Me" won't work if that token is not associated with a user .
To use app token to get user manager information ,you need to specify the user you want to query , code below is for your reference :
try
{
User manager = (User)await adClient.Users.GetByObjectId("5eba8883-c258-45d0-8add-a286a1ec1e91").Manager.ExecuteAsync();
}
catch (Exception ex)
{
throw;
}
Update
You could use authorization code flow for delegated permissions(user's identity) . If you want a client library code sample , you could refer to this code sample . After user sign in , you could use below code to get manager of current login user :
ActiveDirectoryClient client = AuthenticationHelper.GetActiveDirectoryClient();
User manager = (User)await client.Me.Manager.ExecuteAsync();
I used an application identity with the legacy Azure Active Directory api and the 'Application.ReadWrite.OwnedBy' permission to work around the Resource not found for the segment 'me' error. The same permission exists in the Microsoft Graph api, but the behavior is not identical. More information here.

Application access to SharePoint Online using Azure AD Token

How can I get an application token to query SharePoint with application credentials (= without user impersonation) using Azure AD?
The following code works perfectly for querying data as a user but we need to fetch information without impersonation like listing all sites in the collection regardless of user permissions etc.
Exception thrown:
An exception of type
'Microsoft.IdentityModel.Clients.ActiveDirectory.AdalServiceException'
occurred in mscorlib.dll but was not handled in user code
Additional information: AADSTS70001: Application with identifier 'xxx'
was not found in the directory sharepoint.com
Code to get token:
internal static async Task<string> GetSharePointAccessToken(string url, string userAccessTokenForImpersonation)
{
string clientID = #"<not posted on stack overflow>";
string clientSecret = #"<not posted on stack overflow>";
var appCred = new ClientCredential(clientID, clientSecret);
var authContext = new Microsoft.IdentityModel.Clients.ActiveDirectory.AuthenticationContext("https://login.windows.net/common");
// Use user assetion if provided, otherwise use principal account
AuthenticationResult authResult = null;
if (string.IsNullOrEmpty(userAccessTokenForImpersonation))
{
authResult = await authContext.AcquireTokenAsync(new Uri(url).GetLeftPart(UriPartial.Authority), appCred);
}
else
{
authResult = await authContext.AcquireTokenAsync(new Uri(url).GetLeftPart(UriPartial.Authority), appCred, new UserAssertion(userAccessTokenForImpersonation));
}
return authResult.AccessToken;
}
Test code:
// Auth token from Bearer https://xxx.azurewebsites.net/.auth/me
string authHeader = #"<valid jwt bearer token from azure auth>";
var sharePointUrl = #"https://xxx.sharepoint.com/sites/testsite/";
string sharePrincipalToken = await GetSharePointAccessToken(sharePointUrl, null); // <-- doesn't work
string sharePointUserToken = await GetSharePointAccessToken(sharePointUrl, authHeader); // <-- works
Permissions in Azure AD:
The error message you are getting implies that you are signing in with a user that is pointing our token service to get a token in the context of "sharepoint.com"
This is because you are using the "common" endpoint. Read more about that here.
Instead try using a fixed endpoint, where the tenant is the same as where the application is registered and see if that solves your issue.
If your plan is to make this application accessible by multiple tenants, make sure that you have explicitly set your application to be multi-tenant, and then make sure you have a user from the external tenant try and sign into the application before you try doing service to service calls.
Let me know if this helps.

Resources