What am I doing wrong? I want to list the files in the root of my OneDrive. But I always get a 401 Unauthorized.
I used Fiddler to track the requests and requesting the OAuth token seems to work fine. But when I try to request https://graph.microsoft.com/v1.0/me/drive/root/children I get Unauthorized as response with the code UnknownError
private static GraphServiceClient GetAuthenticatedGraphClient()
{
List<string> scopes = new List<string>
{
"https://graph.microsoft.com/.default",
};
var cca = ConfidentialClientApplicationBuilder.Create(CLIENT_ID)
.WithAuthority(AadAuthorityAudience.PersonalMicrosoftAccount)
.WithClientSecret(SECRET)
.Build();
GraphServiceClient graphServiceClient =
new GraphServiceClient(new DelegateAuthenticationProvider(async (requestMessage) =>
{
// Retrieve an access token for Microsoft Graph (gets a fresh token if needed).
var authResult = await cca.AcquireTokenForClient(scopes).ExecuteAsync();
// Add the access token in the Authorization header of the API
requestMessage.Headers.Authorization =
new AuthenticationHeaderValue("Bearer", authResult.AccessToken);
})
);
return graphServiceClient;
}
var drive = GraphClient.Me.Drive.Root.Children.
Request().
GetAsync();
FYI: I am using .NET MVC 5 and I want to access my personal onedrive without user interaction. I seem to be a bit lost with what flow I should use for this.
You are calling /me/drive/root/children endpoint, so you should use a user token rather than application token.
You are using auth code flow with:
var cca = ConfidentialClientApplicationBuilder.Create(CLIENT_ID)
.WithAuthority(AadAuthorityAudience.PersonalMicrosoftAccount)
.WithClientSecret(SECRET)
.Build();
Here you need to add .WithRedirectUri(redirectUri). See sample here.
And you should not use AcquireTokenForClient method here because it is requiring an application token with client credential flow.
If you are trying to call Microsoft Graph in a .net core MVC, please refer to this sample.
Acquire the access token:
string token = await _tokenAcquisition
.GetAccessTokenForUserAsync(GraphConstants.Scopes);
If your application is .net MVC, please refer to this document.
var idClient = ConfidentialClientApplicationBuilder.Create(appId)
.WithRedirectUri(redirectUri)
.WithClientSecret(appSecret)
.Build();
string message;
string debug;
try
{
string[] scopes = graphScopes.Split(' ');
var result = await idClient.AcquireTokenByAuthorizationCode(
scopes, notification.Code).ExecuteAsync();
message = "Access token retrieved.";
debug = result.AccessToken;
}
UPDATE:
There are 2 scenes that we could connect to OneDrive without any further human interaction.
Use client credential flow, which allow us to call Microsoft Graph with an application token. You need to add Application Permission into your Azure AD app. You should choose Client credentials provider and use GraphClient.Users["{userId or UPN}"].Drive.Root.Children instead of GraphClient.Me.Drive.Root.Children because there is no user(/me) in this case.
Corresponding code:
IConfidentialClientApplication confidentialClientApplication = ConfidentialClientApplicationBuilder
.Create(clientId)
.WithTenantId(tenantID)
.WithClientSecret(clientSecret)
.Build();
ClientCredentialProvider authProvider = new ClientCredentialProvider(confidentialClientApplication);
GraphServiceClient graphClient = new GraphServiceClient(authProvider);
var children = await graphClient.Users["{userId or UPN}"].Drive.Root.Children
.Request()
.GetAsync();
If you want to use GraphClient.Me.Drive.Root.Children but don't want sign-in interactively, you could choose Username/password provider, which uses OAuth 2.0 Resource Owner Password Credentials. This scene also uses user token rather than application token.
Please note that:
Microsoft recommends you do not use the ROPC flow. In most scenarios,
more secure alternatives are available and recommended. This flow
requires a very high degree of trust in the application, and carries
risks which are not present in other flows. You should only use this
flow when other more secure flows can't be used.
You need to add Delegated Permission in this case.
Corresponding code:
IPublicClientApplication publicClientApplication = PublicClientApplicationBuilder
.Create(clientId)
.WithTenantId(tenantID)
.Build();
var email = "{your username}";
var str = "{your password}";
var password = new SecureString();
foreach (char c in str) password.AppendChar(c);
UsernamePasswordProvider authProvider = new UsernamePasswordProvider(publicClientApplication, scopes);
GraphServiceClient graphClient = new GraphServiceClient(authProvider);
var children= await graphClient.Me.Drive.Root.Children.Request()
.WithUsernamePassword(email, password)
.GetAsync();
UPDATE 2:
Unfortunately, both Client Credential flow and ROPC(Resource Owner Password Credentials) flow don't support personal accounts. For personal accounts, you have to use auth code flow I mentioned at the beginning and it requires you to sign-in interactively. In summary, it's impossible to access personal Onedrive without any further human interaction.
Related
I am trying to manipulate microsoft planner tasks (end goal is to create a task in a certain Scope and bucket).
I am already failing at listing a Plan or the buckets for a plan. I want to make this connection from a background service (daemon) so no interactive user login should take place. (with interactive login credentials i can make it work, but that's not what i need/want).
So i Created a new App Registration in Azure with the Api Permissions:
Group.Read.All (Delegated)
Group.ReadWrite.All (Delegated)
Tasks.Read (Delegated)
Tasks.Read.Shared (Delegated)
Tasks.ReadWrite (Delegated)
Tasks.ReadWrite.Shared (Delegated)
User.Read (Delegated)
Group.ReadWrite.All (Application)
Tasks.ReadWrite.All (Application)
User.ManageIdentities.All (Application)
User.ReadWrite.All (Application)
I also checked the "Allow public client flows" setting on the App registration Authentication tab.
I started by adding the ones prescribed on the official microsoft doc website about this topic. And then started adding some because i was still receiving Access Denied messages. Thus reaching this list. It should be enough according to microsoft.
Then i have this code to authenticate with Microsoft graph, giving me a graphclient instance which is successfully initialized:
private GraphServiceClient initializeTeamsGraphConnection(string TenantId, string ApplicationId, string ClientSecret)
{
// The client credentials flow requires that you request the
// /.default scope, and preconfigure your permissions on the
// app registration in Azure. An administrator must grant consent
// to those permissions beforehand.
var scopes = new[] { ScopeGraph };
// Multi-tenant apps can use "common",
// single-tenant apps must use the tenant ID from the Azure portal
var tenantId = TenantId;
// Values from app registration
var clientId = ApplicationId;
var clientSecret = ClientSecret;
// using Azure.Identity;
var options = new TokenCredentialOptions
{
AuthorityHost = AzureAuthorityHosts.AzurePublicCloud
};
// https://docs.microsoft.com/dotnet/api/azure.identity.clientsecretcredential
var clientSecretCredential = new ClientSecretCredential(
tenantId, clientId, clientSecret, options);
var graphClient = new GraphServiceClient(clientSecretCredential, scopes);
return graphClient;
}
So authentication seems to be succesful, but when i then try to list a plan using the code below:
private void CreateTask(GraphServiceClient client)
{
var graphTask = client.Planner.Plans["Sdonp-JNB0aInPxDcxMowZgACZ59"]
.Request()
.GetAsync();
while (!graphTask.IsCompleted)
{
graphTask.Wait(10000);
}
var plans = graphTask.Result;
I get following error:
403 - Forbidden: Access is denied.
You do not have permission to view this directory or page using the credentials that you supplied.
Access Permissions should be well above what is needed to do this. Any idea on what I am doing wrong?
Again this code is working because when i change authentication to some sort of interactive login type, i get this plan info no problem
Planner API currently supports only delegated permissions that's the reason why it returns 403 for daemon (background service).
According to this announcement, support for application permissions is coming soon.
I'm working on a windows application wherein, I'm using modern authentication to Exchange online and using EWS API for OAuth 2.0 authentication. To retrieve the access token I'm using Microsoft.Identity.Client library.
IConfidentialClientApplication cca = ConfidentialClientApplicationBuilder
.Create(ClientId)
.WithClientSecret(ClientSecret)
.WithTenantId(TenantId)
.Build();
var ewsScopes = new string[] { "https://outlook.office365.com/.default" };
AuthenticationResult authResult = await cca.AcquireTokenForClient(ewsScopes).ExecuteAsync();
string accessToken = authResult.AccessToken;
service.Credentials = new OAuthCredentials(accessToken);
My problem is, the access token expires after 60 mins. After that the service object throws "The remote server returned an error: (401) Unauthorized" error. Is it possible to modify the lifetime of access token or can we set the access token to never expire?
I did not get any refresh token with access token. So not sure how to continue to process without going through UI login window again and again every hour.
I haven't tried with a Confidential Client Application, but a Public Client Application object can created like this:
var pca = PublicClientApplicationBuilder
.Create(YourAppId)
.WithAuthority(YourAuthority)
.WithRedirectUri(YourRedirectUri)
.Build();
That object can be used to request a token, and it can be kept in memory and used to access the TokenCache:
AuthenticationResult authResult;
var accounts = await pca.GetAccountsAsync();
if (accounts.Any())
{
authResult = pca.AcquireTokenSilent(scopes, accounts.First()).ExecuteAsync().Wait());
}
else
{
authResult = pca.AcquireTokenInteractive(scopes).ExecuteAsync().Wait());
}
If the Public Client Application object goes out of scope, the TokenCache goes with it.
The Public Client Application object has BeforeAccess and AfterAccess callbacks which can be used to persist the TokenCache.
On this page https://learn.microsoft.com/en-us/graph/sdks/choose-authentication-providers?tabs=CS#IntegratedWindowsProvider it is said "The interactive flow is used by mobile applications (Xamarin and UWP) and desktops applications to call Microsoft Graph in the name of a user."
So I developed a C# console app to login and query some data:
var clientId = "<APP GUID GOES HERE>";
var tenantId = "<APP TENANT GUID GOES HERE>";
var scopes = new[] {"user.read","Calendars.Read"};
var clientApplication = PublicClientApplicationBuilder
.Create(clientId)
.Build();
var authProvider = new InteractiveAuthenticationProvider(clientApplication, scopes);
var graphClient = new GraphServiceClient(authProvider);
User me = graphClient.Me.Request()
.GetAsync()
.Result;
During running the console app a login "page" comes out, I entered my credentials, but at the end the pagse says error "AADSTS500113: No reply address is registered for the application.", and the code got "user cancelled the login"
BTW: I dont want to login manually each time, I added my password to the code:
var scopes = new[] {"offline_access","user.read","Calendars.Read"};
var clientApplication = PublicClientApplicationBuilder
.Create(clientId)
.Build();
var authProvider = new UsernamePasswordProvider(clientApplication, scopes);
var graphClient = new GraphServiceClient(authProvider);
var pwd = ConvertToSecureString("<MYPASSWORD GOES HERE>");
User me = graphClient.Me.Request()
.WithUsernamePassword("<MY EMAIL GOES HERE>", pwd)
.GetAsync()
.Result;
In this case no login page shows up (good), but an exception raises: "The grant type is not supported over the /common or /consumers endpoints. Please use the /organizations or tenant-specific endpoint."
Then I added a WithTenantId(...) to the Build(), now I got different exception: "MsalUiRequiredException: AADSTS50076: Due to a configuration change made by your administrator, or because you moved to a new location, you must use multi-factor authentication to access '00000003-0000-0000-c000-000000000000'." but the multi-factor auth request does not come to my phone.
What goes wrong? What should I do to get this app work?
What I want is to execute this c# console app regularly on my desktop computer, without any interactions (logins) as my user to query some data using graph api. How to do that correctly?
Thanks in advance!
This error AADSTS500113: No reply address is registered for the application indicates that the reply URL is not available and AAD does not know where to send the token. To fix this, you need to add a valid redirect URI in your app registration in AAD.
The next error : MsalUiRequiredException in your case happens because the user needs to perform multiple factor authentication based your Azure AD policies. To do this, you need to change your flow from the current username/password provider to interactive authentication provider since in the former case, users who need to do MFA won't be able to sign-in (as there is no interaction).
This would look something like this:
IPublicClientApplication publicClientApplication = PublicClientApplicationBuilder
.Create(clientId)
.Build();
InteractiveAuthenticationProvider authenticationProvider = new InteractiveAuthenticationProvider(publicClientApplication, scopes);
You can then acquire the token interactively :
string[] scopes = new string[] {"user.read"};
var app = PublicClientApplicationBuilder.Create(clientId).Build();
var accounts = await app.GetAccountsAsync();
AuthenticationResult result;
try
{
result = await app.AcquireTokenSilent(scopes, accounts.FirstOrDefault())
.ExecuteAsync();
}
catch(MsalUiRequiredException)
{
result = await app.AcquireTokenInteractive(scopes)
.ExecuteAsync();
}
To authenticate without the user, your app can implement client credentials acquisition methods - these suppose that the app has previously registered a secret (application password or certificate) with Azure AD, which it then shares with this call. Please note that no user interactions means you can't use delegated permissions.
Let me know if this helps and if you have further questions.
I'm trying to create MS Teams Meeting using Microsoft.Graph.Beta SDK in .NET core application. I'm using the following code to create the meeting.
static void Main(string[] args)
{
var clientId = "<Enter you Client ID here>";
var tenantId = "<Enter your tenand ID here>";
var clientSecret = "<Enter your client secret here>";
var scopes = new string[] { "https://graph.microsoft.com/.default" };
///The client credential flow enables service applications to run without user interaction.
///Access is based on the identity of the application.
IConfidentialClientApplication confidentialClientApplication = ConfidentialClientApplicationBuilder
.Create(clientId)
.WithTenantId(tenantId)
.WithClientSecret(clientSecret)
.Build();
ClientCredentialProvider authProvider = new ClientCredentialProvider(confidentialClientApplication);
var onlinemeeting = CreateTeamsMeeting(authProvider).GetAwaiter().GetResult();
Console.ReadLine();
}
public static async Task<OnlineMeeting> CreateTeamsMeeting(IAuthenticationProvider authProvider)
{
GraphServiceClient graphClient = new GraphServiceClient(authProvider);
var onlineMeeting = new OnlineMeeting
{
StartDateTime = DateTimeOffset.Parse("2020-11-12T21:30:34.2444915+00:00"),
EndDateTime = DateTimeOffset.Parse("2020-11-12T22:00:34.2464912+00:00"),
Subject = "User Token Meeting",
};
return await graphClient.Me.OnlineMeetings
.Request()
.AddAsync(onlineMeeting);
}
However, upon running the above code, I receive the following error.
Server error: User lookup by user id failed in AAD. Client exception:
Processing of the HTTP request resulted in an exception. Please see
the HTTP response returned by the 'Response' property of this
exception for details.
Inner error: AdditionalData: date: 2020-11-09T12:19:53 request-id:
02cd2168-d0d8-437a-9eee-117f1924a387 client-request-id:
02cd2168-d0d8-437a-9eee-117f1924a387 ClientRequestId:
02cd2168-d0d8-437a-9eee-117f1924a387
How do we fix this error??
#juusnas's answer is correct. I just organize my comments here for easy reference.
You are using client credential flow based on Client credentials provider. So you should use graphClient.Users["{user id/upn}"].OnlineMeetings.Request().AddAsync(onlineMeeting) rather than graphClient.Me.OnlineMeetings.Request().AddAsync(onlineMeeting).
And if you want to implement Get the token as a user, you should choose Authorization code provider or Username/password provider. Then you could keep using graphClient.Me.OnlineMeetings.Request().AddAsync(onlineMeeting).
For permission error, you need to add Application permission (not delegated permission) OnlineMeetings.ReadWrite.All into your App registration.
And based on the Important tip here, you also must create an application access policy to allow applications to access online meetings on behalf of a user.
You have acquired a token as the application, with client credentials flow.
This does not make sense:
graphClient.Me.
Who is "me"?
Normally that is the user on behalf of who the app is making the API call.
But because you acquired the token as the app, there isn't a user.
You need to either:
Get the token as a user (don't use client credentials)
Use an API that is not dependent on user identity (not sure if Teams has those)
I'm using the Microsoft Graph SDK to get an access token for my application (not a user) in order to read from sharepoint. I've been following this document, as well as posted this SO question. The code in the linked SO is the same. I was able to add application permissions as well as grant them (by pressing the button) in azure portal. The problem is, the token that comes back to be used does not contain any roles / scp claims in it. Therefore when using the token, I get the "Either scp or roles claim need to be present in the token" message.
Just to be certain, the only value for my scope that I pass when getting the access token is: https://graph.microsoft.com/.default. I don't pass anything else like Sites.ReadWrite.All (I get an exception if I add that scope anyway). I'm not sure how to continue troubleshooting and any help would be appreciated.
Edit: added code using the graph SDK shown below:
var client = new ConfidentialClientApplication(id, uri, cred, null, new SessionTokenCache());
var authResult = await client.AcquireTokenForClientAsync(new[] {"https://graph.microsoft.com/.default"});
var token = authResult.AccessToken;
var graphServiceClient = new GraphServiceClient(new DelegateAuthenticationProvider(async request => {request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token)}));
var drives = await graphServiceClient.Sites[<sharepoint_host>].SiteWithPath(<known_path>).Drives.Request().GetAsync();
Seems like doing the app initialization in a different way is the solution. Instead of this:
var client = new ConfidentialClientApplication(id, uri, cred, null, new SessionTokenCache());
do this:
var app = new ConfidentialClientApplication(ClientId, Authority, RedirectUri, credentials, null, new TokenCache());
The problem is, the token that comes back to be used does not contain
any roles / scp claims in it.
If you can not find any roles/scp claims in the decoded access token. You need to check the permission in Azure portal again.
The decoded access token should contain the roles you granted.
Login Azure portal->click Azure Active Directory->click App registrations(preview)->find your application.
Click your application->API permissions->check if you have grant admin consent for your application. If not, click 'Grant admin consent'.
The code for getting access token. You can find more details here.
//authority=https://login.microsoftonline.com/{tenant}/
ClientCredential clientCredentials;
clientCredentials = new ClientCredential("{clientSecret}");
var app = new ConfidentialClientApplication("{clientId}", "{authority}", "{redirecturl}",
clientCredentials, null, new TokenCache());
string[] scopes = new string[] { "https://graph.microsoft.com/.default" };
AuthenticationResult result = null;
result = app.AcquireTokenForClientAsync(scopes).Result;
Console.WriteLine(result.AccessToken);