0

I'm trying to use Microsoft.Identity.Client and Microsoft.SharePoint.Client libraries to authenticate to an On-premise SharePoint server and then query it.

I obtain the Azure AD access token from which SharePoint server is a part of like following:

private readonly string[] m_scopes = { "user.read", "https://sql.azuresynapse-dogfood.net/user_impersonation" };

var publicAppBuilder = PublicClientApplicationBuilder.Create("MyClientId").WithAuthority("https://login.microsoftonline.com/a******com.onmicrosoft.com"); 
 
publicAppBuilder.WithRedirectUri("https://login.microsoftonline.com/common/oauth2/nativeclient"); 

var app = publicAppBuilder.Build();

AuthenticationResult result = null;

result = app.AcquireTokenInteractive(m_scopes).ExecuteAsync().GetAwaiter().GetResult();
if (result != null)
{
    m_mediator.AccessToken = result.AccessToken;
}

When I get the access token I put it in the request header as follows:

args.WebRequestExecutor.RequestHeaders["Authorization"] = "Bearer " + ar.AccessToken;

Which is inside the ClientContext.ExecutingWebRequest subscribed method:

clientContext.ExecutingWebRequest += (sender, args) =>

which is triggered by

context.ExecuteQuery();

and I get the following result:

The remote server returned an error: (401) Unauthorized.

or

The remote server returned an error: (403) Forbidden.

How can I establish the connection? I want to avoid using app-only registration, I want to authenticate using Azure AD MFA (Interactive) method.

Please note that I have all the permissions needed and I am an admin on both Azure AD where SharePoint is joined, as well on the SharePoint server itself. I authenticate through the browser just fine.

I've tried multiple things so far:

I tried creating a separate request where I forward the previously acquired accessToken as Authorization: Bearer token

I tried reading the FedAuth from the authentication connection window, so I can forward it in my HTTP request but with no success

I tried creating a "Web browser" using a WebBrowser C# class and reading the cookies that are on a browser level like the following: cookieContainer = webBrowser1.Document.Cookie; but I had no success.

I'm expecting to Authenticate via Azure AD and then connect to SharePoint in order to query it

Querying On-premise SharePoint using Azure AD MFA through C# app

1 Answer 1

0

I've found a solution.

I basically iterate through all cookies whenever a browser navigates through a new page and parse all the cookies until I get the fedAuth cookie:

  1. I created a web browser from System.Windows.Forms.WebBrowser

  2. In the WebBrowserNavigatedEventHandler for Navigated I do the following:

    if (webBrowser1.Url.AbsoluteUri == "about:blank") { return; }

    var cookieData = GetWebBrowserCookie.GetCookieInternal(webBrowser1.Url, false);

    if (string.IsNullOrEmpty(cookieData) == false) { var dict = ParseCookieData(cookieData); if (dict.ContainsKey("FedAuth") && !string.IsNullOrEmpty(dict["FedAuth"])) { m_mediator.FedAuthCookie = dict["FedAuth"]; if (dict.ContainsKey("rtFa") && !string.IsNullOrEmpty(dict["rtFa"])) { m_mediator.RtFaCookie = dict["rtFa"]; } m_mediator.UpdateConfiguration(); this.Close(); } }

The ParseCookieData method looks like this:

private IDictionary<string, string> ParseCookieData(string cookieData)
{
  var cookieDictionary = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);

  if (string.IsNullOrEmpty(cookieData))
  {
    return cookieDictionary;
  }

  var values = cookieData.TrimEnd(';').Split(';');
  foreach (var parts in values.Select(c => c.Split(new[] { '=' }, 2)))
  {
    var cookieName = parts[0].Trim();

    var cookieValue = parts.Length == 1 ? string.Empty : parts[1];

    cookieDictionary[cookieName] = cookieValue;
  }

  return cookieDictionary;
}

and GetWebBrowserCookie class looks like this:

[SecurityCritical]
public static string GetCookieInternal(Uri uri, bool throwIfNoCookie)
{
  uint pchCookieData = 0;
  string url = UriToString(uri);
  uint flag = (uint)NativeMethods.InternetFlags.INTERNET_COOKIE_HTTPONLY;

  //Gets the size of the string builder   
  if (NativeMethods.InternetGetCookieEx(url, null, null, ref pchCookieData, flag, IntPtr.Zero))
  {
    pchCookieData++;
    StringBuilder cookieData = new StringBuilder((int)pchCookieData);

    //Read the cookie   
    if (NativeMethods.InternetGetCookieEx(url, null, cookieData, ref pchCookieData, flag, IntPtr.Zero))
    {
      DemandWebPermission(uri);
      return cookieData.ToString();
    }
  }

  int lastErrorCode = Marshal.GetLastWin32Error();

  if (throwIfNoCookie || (lastErrorCode != (int)NativeMethods.ErrorFlags.ERROR_NO_MORE_ITEMS))
  {
    throw new Win32Exception(lastErrorCode);
  }

  return null;
}

private static void DemandWebPermission(Uri uri)
{
  string uriString = UriToString(uri);

  if (uri.IsFile)
  {
    string localPath = uri.LocalPath;
    new FileIOPermission(FileIOPermissionAccess.Read, localPath).Demand();
  }
  else
  {
    new WebPermission(NetworkAccess.Connect, uriString).Demand();
  }
}

private static string UriToString(Uri uri)
{
  if (uri == null)
  {
    return string.Empty;
  }

  UriComponents components = (uri.IsAbsoluteUri ? UriComponents.AbsoluteUri : UriComponents.SerializationInfoString);
  return new StringBuilder(uri.GetComponents(components, UriFormat.SafeUnescaped), 2083).ToString();
}

This way we open up a pop-up C# web browser, authenticate the user through the web using MFA and then close the browser when we acquire an authentication cookie so we can continue working with HTTP requests towards the Sharepoint server.

Source: https://github.com/OceanAirdrop/SharePointOnlineGetFedAuthAndRtfaCookie

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.