25

I'm trying to authenticate up against Sharepoint so that it's possible for me to upload files onto a specific Sharepoint site.

I'm trying to use an X.509 certificate to retrieve the access token, but I keep getting (401): Unauthorized.

Here's the way I try to retrieve the access token with the certificate:

string authority = SettingsHelper.Authority;
string clientID = SettingsHelper.ClientId;
string serverName = SettingsHelper.SharepointServerName;
//Retreive the certificate path
string certFile = Server.MapPath(SettingsHelper.CertificatePath);
string certPassword = SettingsHelper.CertificatePassword;

AuthenticationResult authenticationResult = null;
AuthenticationContext authenticationContext = new AuthenticationContext(authority);

//Create the certificate file, using the path (certFile), password (certPassword) and the MachineKeySet
X509Certificate2 cert = new X509Certificate2(certFile, certPassword, X509KeyStorageFlags.MachineKeySet);

//Create the ClientAssertionCertificate using the clientID and the actual certificate
ClientAssertionCertificate cac = new ClientAssertionCertificate(clientID, cert);

//Retreive the access token using the serverName and client assertion
authenticationResult = authenticationContext.AcquireToken(serverName, cac);

And here's how I try to upload a specific file onto a specific Sharepoint list:

WebRequest request = null;
HttpWebResponse response = null;
byte[] bytesToUpload = bytes;
var returnValue = "";

string requestUriString = string.Format("{0}/_api/web/GetFolderByServerRelativeUrl(@sru)/Files/Add(url=@fn,overwrite=true)?@sru='{1}'&@fn='{2}'", url, HttpUtility.UrlEncode(serverRelativeUrl), HttpUtility.UrlEncode(fileName));

request = (HttpWebRequest)HttpWebRequest.Create(requestUriString);

request.Method = "POST";
(request as HttpWebRequest).Accept = "*/*";
request.ContentType = "application/json;odata=verbose";
request.Headers.Add("Authorization", String.Format("Bearer {0}", authenticationResult.AccessToken));
request.ContentLength = bytesToUpload.Length;


// Write the local file to the remote system
using (Stream requestStream = request.GetRequestStream())
{
    BinaryWriter writer = new BinaryWriter(requestStream);
    writer.Write(bytesToUpload, 0, bytesToUpload.Length);
    writer.Close();
}
// Get a web response back
response = (HttpWebResponse)request.GetResponse();

using (StreamReader sr = new StreamReader(response.GetResponseStream(), Encoding.Default))
{
    returnValue = sr.ReadToEnd();
    sr.Close();
}

if (request.RequestUri.ToString().Contains("GetFolderByServerRelativeUrl") == true)
{
    returnValue = "";
}

Some of the variables comes from the parameters:

 UploadEmail(System.IO.File.ReadAllBytes(emlFilePath), "https://(blablabla).sharepoint.com", "sites/(bla)/(bla)/Emails", email.Subject + ".msg");

I'm not sure what's wrong, and I'm definitely not sure how to fix this.

NOTE: Please don't tell me to use NetworkCredentials, I'd rather use the certificate or something else, but not NetworkCredentials

EDIT

Managed to debug the code and find this in the response header of the WebRequest:

enter image description here

halfer
  • 19,824
  • 17
  • 99
  • 186
Detilium
  • 2,868
  • 9
  • 30
  • 65
  • Have you configured IIS client to user mapping? https://technet.microsoft.com/en-us/library/cc732996(v=ws.10).aspx – x0n Nov 12 '15 at 15:11
  • What version of SharePoint are you using? On Premise? etc. Also is this going to be lots of different users or a single proxy user. – Adam Carr May 20 '16 at 23:41
  • Sharepoint 2013 is Claims Based Authentication so I don't think you will get much joy with x0n's approach. Most people suggest using ADFS. – Adam Carr May 20 '16 at 23:44
  • 2
    Have you considered using the SharePoint Client Side Object Model? You have an API that can do most of the work you're trying to achieve here. – hbulens Jun 27 '16 at 09:34
  • request.Credentials = CredentialCache.DefaultCredentials; – David Soler Nov 25 '16 at 12:16
  • I noticed you're hitting https://login.windows.net/common/.... I believe that endpoint is outdated. What happens if you replace common with your Azure AD Tenant ID? – Kevin R. Jan 12 '17 at 18:12

1 Answers1

1

The better approach would be using the SharePoint Client Side Object Model (as hbulens suggested in comments). Here's the code that uploads the file to the library in O365 (just replace the string literals with your own details):

string username = "YOUR_USERNAME";
string password = "YOUR_PASSWORD";
string siteUrl = "https://XXX.sharepoint.com";

ClientContext context = new ClientContext(siteUrl);

SecureString pass = new SecureString();
foreach (char c in password.ToCharArray()) pass.AppendChar(c);
context.Credentials = new SharePointOnlineCredentials(username, pass);

Site site = context.Site;
context.Load(site);
context.ExecuteQuery();

Web web = site.OpenWeb("YOUR_SUBSITE"); 
context.Load(web);
context.ExecuteQuery();

List docLib = web.Lists.GetByTitle("YOUR_LIBRARY");
context.Load(docLib);

FileCreationInformation newFile = new FileCreationInformation();
string filePath = @"YOUR_LOCAL_FILE";

newFile.Content = System.IO.File.ReadAllBytes(filePath);
newFile.Url = System.IO.Path.GetFileName(filePath);

Microsoft.SharePoint.Client.File uploadFile = docLib.RootFolder.Files.Add(newFile);
context.Load(uploadFile);
context.ExecuteQuery();

You can run it in console application. Two dll's that you need to reference are:

  • Microsoft.SharePoint.Client.dll
  • Microsoft.SharePoint.Client.Runtime.dll
Damjan Tomic
  • 390
  • 2
  • 11