Access sharepoint search rest api - c#

I have a problem. I use Azure AD to authenticate my asp.net app. Authentication works fine. Then I from this app trying to access OneDrive for Business using sharepoint search rest api. But the server always receives a response with a 401 error. I understand that the problem is in the access token which I use (Now I use the token received from Azure AD). But I never found the normal description of how to obtain an access token for the sharepoint search rest api.
Thanks in advance

Answer
You need to give your ASP.NET Application permission to use your OneDrive for Business application.
Here is an overview of how to do this using the Azure Management Portal. (Note that your OneDrive for Business account is a type of Office 365 SharePoint Online account.)
Go to manage.windowsazure.com > Active Directory > Your Tenant. If your tenant has an associated OneDrive for Business account, then its list of applications will include Office 365 SharePoint Online.
If your tenant's list of application does include Office 365 SharePoint Online, then your next step is to give your ASP.NET Web Application permission to access it.
Open up your Web Application's page in the Azure Active Directory area. Then choose CONFIGURE > Add Application. Add the Office 365 SharePoint Online application. Give it all necessary permissions and save.
The following screenshot is for a Native Client Application, because that is what my demo code is using. You can do a similar thing for a Web Application, though you will need to use an X509 Certificate for authentication instead of a username/password.
Your access token will now work with your Office 365 for Business account. Hooray!
Demo
Here is some sample code that works on my machine with a Native Client App. You can do the same thing with a Web Application, though you will need to use an X509 Certificate instead of a username/password.
using Microsoft.IdentityModel.Clients.ActiveDirectory;
using System.Net;
namespace AAD_SharePointOnlineApp
{
class Program
{
static void Main(string[] args)
{
var authContext =
new AuthenticationContext(Constants.AUTHORITY);
var userCredential =
new UserCredential(Constants.USER_NAME, Constants.USER_PASSWORD);
var result = authContext
.AcquireTokenAsync(Constants.RESOURCE, Constants.CLIENT_ID_NATIVE, userCredential)
.Result;
var token = result.AccessToken;
var url = "https://mvp0.sharepoint.com/_api/search/query?querytext=%27timesheets%27";
var request = WebRequest.Create(url);
request.Headers.Add(HttpRequestHeader.Authorization, "Bearer " + token);
var response = request.GetResponse() as HttpWebResponse;
}
}
class Constants
{
public const string AUTHORITY =
"https://login.microsoftonline.com/mvp0.onmicrosoft.com/";
public const string RESOURCE =
"https://mvp0.sharepoint.com";
public const string CLIENT_ID_NATIVE =
"xxxxx-xxxx-xxxxx-xxxx-xxxxx-xxxx";
public const string USER_NAME =
"MY_USER#mvp0.onmicrosoft.com";
public const string USER_PASSWORD =
"MY_PASSWORD";
}
}
Comments
If you are trying to do the above with a Web Application instead of a Native Client Application, then you will need to use an X509 Certificate, otherwise you will receive the following error.
Unsupported app only token.
See also: http://blogs.msdn.com/b/richard_dizeregas_blog/archive/2015/05/03/performing-app-only-operations-on-sharepoint-online-through-azure-ad.aspx

Related

What is the proper way to work with the OneDrive API?

I want to interact with OneDrive in my WinForms application. Sadly, the Azure quick start samples do not include WinForms, just UWD.
The flow on what I have to do is consistent, namely given my Client ID, I have to obtain an Authentication Code. Given the authentication code, I can then obtain an Access Code, which will allow me to interact in a RESTful way with the OneDrive API. My plan is to have the authentication piece go in a .Net Framework Library and the file IO calls will go in another library that has no user interface access, as it will go in a Windows Service. I would pass the Access Token to the service.
AADSTS50059: No tenant-identifying information found in either the request or implied by any provided credentials.
This error corresponds to the following code fragment that I lifted from the sample .Net Core daemon quick start code.
Note: I was playing around with Scopes as I kept receiving scope errors and I saw one article, whose link I should have kept, which stated to use the API and default scope.
public bool GetRestAuthenticationToken(out string tokenAuthentication)
{
tokenAuthentication = null;
try
{
IConfidentialClientApplication app;
app = ConfidentialClientApplicationBuilder.Create(Authenticate.AppClientId)
.WithClientSecret(Authenticate.AppClientSecret)
.WithAuthority(new Uri(#"https://login.microsoftonline.com/common/oauth2/nativeclient"))
.Build();
string scope = $"onedrive.readwrite offline_access";
System.Collections.Generic.List<string> enumScopes = new System.Collections.Generic.List<string>();
enumScopes.Add("api://<GUID>/.default");
//enumScopes.Add(Authenticate.Scopes[1]);
var result = Task.Run(async () => await app.AcquireTokenForClient(enumScopes).ExecuteAsync()).Result;
...
}
...
}
I believe that I have my application configured properly now on Azure, but am not 100% positive.
API Permissions:
Authentication:
Desktop Applications: https://login.microsoftonline.com/common/oauth2/nativeclient
Desktop Applications: https://login.live.com/oauth20_desktop.srf
Implicit Grants: Access tokens & ID tokens
Live SDK support (Yes)
Default client type (Yes)
Others:
I do have a client secret and kept note of all the Overview GUIDs
Microsoft Doc 1
I tried several different URLs, but only the one not commented out works with the fragment above, but throws the referenced error.
//string redirect_uri = #"https://www.myapp.com/auth";
//string redirect_uri = "https://login.live.com/oauth20_desktop.srf";
string url = #"https://login.microsoftonline.com/common/oauth2/nativeclient";
//string url = $"https://login.live.com/oauth20_authorize.srf?client_id={appClientId}&scope={scope}&response_type=code&redirect_uri={redirect_uri}";
//string url = $"https://login.microsoftonline.com/common/oauth2/v2.0/authorize?" +
// $"client_id={Authenticate.AppClientId}&" +
// $"scope={scope}&" +
// $"response_type=token&" +
// $"redirect_uri={redirect_uri}";
The goal is the same, namely to obtain an access token that I can use with RESTful calls to work with files and/or directories on OneDrive, e.g.
System.Net.Http.HttpClient client = new System.Net.Http.HttpClient();
client.GetAsync(...);
You are trying to implement Client credentials grant type to get the access token.
Based on MSAL initialization, Authority is
(Optional) The STS endpoint for user to authenticate. Usually
https://login.microsoftonline.com/{tenant} for public cloud, where
{tenant} is the name of your tenant or your tenant Id.
We assume that your tenant is "myTenent.onmicrosoft.com", then you should set it as https://login.microsoftonline.com/myTenent.onmicrosoft.com here.
I notice that you specify a scope "onedrive.readwrite" in your code. But it's not a valid permission of Microsoft Graph. The default scope of Microsoft Graph is https://graph.microsoft.com/.default.

Authenticating EWS using Oauth with full_access_as_user

I'm trying to get an application using EWS to work against O365 with OAuth. I've managed to get it working in the case where I register the app in AAD, and grant it full_access_as_app (Use Exchange Web Services with full access to all mailboxes) in the portal. The code looks like this:
string tokenUri = "https://login.microsoftonline.com/TENANTID/oauth2/v2.0/token";
string resource = "https://outlook.office365.com";
string appId = "APPID_FROM_AAD";
var context = new AuthenticationContext(tokenUri);
X509Certificate2 cert = GetLocalCertificate(THUMBPRINT);
ClientAssertionCertificate c = new ClientAssertionCertificate(appId, cert);
AuthenticationResult authenticationResult = context.AcquireTokenAsync(resource, c).ConfigureAwait(false).GetAwaiter().GetResult();
m_exchangeService.Credentials = new OAuthCredentials(authenticationResult.AccessToken);
//all the autodiscover URIs for O365 are the same
m_exchangeService.Url = new Uri(#"https://outlook.office365.com/EWS/Exchange.asmx");
m_exchangeService.ImpersonatedUserId = new ImpersonatedUserId(ConnectingIdType.SmtpAddress, m_emailAddress);
This seemed a little extreme though. The application is a back end service, and only needs access to one mailbox (the impersonated user id), so I wanted to change it over to full_access_as_user permissions (Access mailboxes as the signed-in user via Exchange Web Services). I added the permission to the app, and then added the application to the user, but I got a 401 anytime I tried to operate on the mailbox after impersonation. I checked the JWT, and while with the original permission I had "full_access_as_app" in the roles, I didn't have any roles assigned this time.
Is there a way I can modify this so that I can have an admin add EWS access to one or more mailboxes in a tenant, or is the only way to get this to work to give an app access to every mailbox on the service?
Yes, OAuth authentication for EWS is only available in Exchange as part of Office 365. EWS applications require the "Full access to user's mailbox" permission.
Reference - Authenticate an EWS application by using OAuth

Outlook REST API: trying to call API using AZURE AD authentication

Using the office 365 Outlook REST API (version 2), I have a web application managing outlook subscriptions to specific mail boxes. I've been able to use the examples to obtain a token and call the API using the authorization code flow, successfully.
But now, I want to use a client credential flow and get a token using Azure AD authentication via delegate permissions (I gave the application all possible delegate permissions under office 365 exchange online). Similar to what I've seen here: Get Office 365 API access token without user interaction
I've registered my application and gotten my tenant ID, client ID & secret. I've been able to get a token but when I try to use it, I get 401, unauthorized back.
Here's how I'm getting the token:
AuthenticationContext authContext = new AuthenticationContext($"{authority}{tenantId}");
clientCredential = new ClientCredential(client_Id, secret);
authResult = await authContext.AcquireTokenAsync(resource, clientCredential);
authResult.AccessToken;
And here's how I'm trying to use the API (trying to delete the subscription using REST sharp in this code):
var token = await GetOtherToken(account);
rc = new RestClient("https://outlook.office.com/api/v2.0");
rc.AddDefaultHeader("Authorization", $"Bearer {token}");
request = new RestRequest($"me/subscriptions('{restSubId}')", Method.DELETE);
request.AddHeader("Content-Length", "0");
request.AddHeader("Content-Type", "multipart/form-data");
Looks like this is not possible. Please, someone, drop some knowledge. Thanks for reading.
When using client credential flow to acquire a token for resource , you are using application identity instead of as a user's identity. So you should assign application permission for your app(not delegate permissions) . In addition , since you are using app identity instead of user's identity , api can't recognize me(https://outlook.office.com/api/v2.0/me) . Please click here for how to build service and daemon apps in Office 365 .

Consume Office 365 REST API Without UI

I need to push calendar entries in to a client's Outlook account. This is fairly straight forward with Exchange. You just authenticate with a user that has access, and then you can push entries in to other user's accounts. It seems to be completely different in Office 365.
I tried to follow the instructions here:
https://dev.outlook.com/restapi/getstarted
I created the app and got the app's client ID. But, all of the documentation is around oAuth. Generally speaking, oAuth is designed for scenarios when a user needs to enter their credentials in through a browser window that will then confirm with the user which credentials they are willing to allow the app to have.
This does not match my scenario. I need to be able to push the calendar entries in to the account without any UI. This is back end integration. It just needs to do its job silently.
I looked at this sample app:
https://github.com/OfficeDev/O365-Win-Snippets
But, this is a front end app. When it needs to authenticate, it pops up a window to force the user to enter their credentials.
When I try to call the REST API that is mentioned in the getting started page, it returns HTML. This is the Url it mentions:
https://login.microsoftonline.com/common/oauth2/v2.0/authorize?client_id=&redirect_uri=http%3A%2F%2Flocalhost%2Fmyapp%2F&response_type=code&scope=https%3A%2F%2Foutlook.office.com%2Fmail.read
I've tried a few permutations of this Url with my client ID. I've tried passing in my Office 365 credentials through basic http authentication.
I'm stuck.
The answer is simple. Use the Exchange API - not Office 365 API.
I was confused because I assumed that Office 365 was a different entity to Exchange, but the Office 365 email server just is one giant Exchange server. Here's some sample code for good measure. This is an example of logging in to Office 365's Exchange server and sending off a calendar entry to an email address. Simple.
I made a wild guess about the exchange Url and it was correct:
https://outlook.office365.com/ews/exchange.asmx
//Connect to exchange
var ewsProxy = new ExchangeService(ExchangeVersion.Exchange2013);
ewsProxy.Url = new Uri("https://outlook.office365.com/ews/exchange.asmx");
//Create the meeting
var meeting = new Appointment(ewsProxy);
ewsProxy.Credentials = new NetworkCredential(_Username, _Password);
meeting.RequiredAttendees.Add(_Recipient);
// Set the properties on the meeting object to create the meeting.
meeting.Subject = "Meeting";
meeting.Body = "Please go to the meeting.";
meeting.Start = DateTime.Now.AddHours(1);
meeting.End = DateTime.Now.AddHours(2);
meeting.Location = "Location";
meeting.ReminderMinutesBeforeStart = 60;
// Save the meeting to the Calendar folder and send the meeting request.
meeting.Save(SendInvitationsMode.SendToAllAndSaveCopy);
My understanding is that this is possible, but the authentication looks quite complicated. For starters, any application that requires Office 365 integration must also integrate with the associated Azure AD. You can register your application for specific users so that it has the permissions required for whatever operations you need to perform. See here for a good summary of this component: https://msdn.microsoft.com/en-us/office/office365/howto/connect-your-app-to-o365-app-launcher?f=255&MSPPError=-2147217396#section_2
For authentication, you require a daemon/server application model. I've not attempted this yet, but it's documented here and looks like it should meet your needs (see the Daemon or Server Application to Web API section): https://azure.microsoft.com/en-us/documentation/articles/active-directory-authentication-scenarios/#daemon-or-server-application-to-web-api
In order to call the Office 365 REST API, the app requires an access token from Azure Active Directory, that's why you need (mandatory) to register app in Microsoft Azure Active Directory (Azure AD). Your Office 365 account in turn needs to be associated with Azure AD. This answer summarizes on how to register app in Azure AD in order to consume Office 365 API.
Basic authentication scheme
Regrading Basic authentication, currently it is enabled for API version 1.0, the following example demonstrates how to consume Outlook Calendar REST API in .NET application.
Prerequisites:
domain: https://outlook.office365.com/
API version: v1.0
Here is an example that gets my calendars and prints its names
private static async Task ReadCalendars()
{
var handler = new HttpClientHandler();
handler.Credentials = new NetworkCredential()
{
UserName = ConfigurationManager.AppSettings["UserName"],
Password = ConfigurationManager.AppSettings["Password"]
};
using (var client = new HttpClient(handler))
{
var url = "https://outlook.office365.com/api/v1.0/me/calendars";
var result = await client.GetStringAsync(url);
var data = JObject.Parse(result);
foreach (var item in data["value"])
{
Console.WriteLine(item["Name"]);
}
}
}

Google Drive simple API Access authorization fail

I'm trying to authorize in google using .NET SDK provided, but it failes with "Invalid credentials" error. I found this answer
Google Calendar V3 2 Legged authentication fails
but I still don't see where is the mistake. Looks like everything is done as described.
Here is my code
const string CONSUMER_KEY = "mytestdomain.com";
const string CONSUMER_SECRET = "my_consumer_secret";
const string TARGET_USER = "user";
const string SERVICE_KEY = "some_api_key";
var auth = new OAuth2LeggedAuthenticator(CONSUMER_KEY, CONSUMER_SECRET, TARGET_USER, CONSUMER_KEY);
var service = new DriveService(auth) { Key = SERVICE_KEY };
var results = service.Files.List().Fetch();
Console.WriteLine(results.Items.Count);
and here are screenshots from google control panel. I just replaced domain name, consumer key and api key.
Screenshot from Manage API client access page
Screenshot from Manage OAuth key and secret for this domain page
Screenshot from API Access page in Google API console
OAuth 1.0 has been deprecated and 2-Legged OAuth 1.0 with it. Although it is still supported for the deprecation period if I were you I would use Service Accounts with OAuth 2.0 to perform domain-wide delegation of authority.
This is very well documented on the Perform Google Apps Domain-wide Delegation of Authority page of the Google Drive SDK documentation.

Categories