SharePoint Online REST/OData Endpunkt via Konsolenanwendung

​​In diesem Blogbeitrag möchte ich beschreiben, wie die Authentifizierung gegen einen SharePoint Online Server von einer Konsolenanwendung aus durchgeführt wird. Ziel dieses Blogbeitrages ist es, am Ende erfolgreich eine Anfrage gegen den REST/OData Endpunkt durchzuführen. Das folgende Sequenz Diagramm zeigt einen Überblick über die geplanten Schritte.

i1.png

SAML Anfrage senden​

Um das Binary Security Token zu erhalten muss eine SOAP Anfrage via HTTP POST gegen folgenden Endpunkt ausgeführt werden: https://login.microsoftonline.com/extSTS.srf . Das SOAP XML benötigt drei Parameter:

  • Benutzername
  • Passwort
  • Tenant

Wie in der nachfolgenden GetSoapXml Methode zu sehen, werden die drei Parameter über die String.Format Methode eingebunden.

    
        private static string GetSoapXml(string username, string password, string tenant)
        {
        ​     return string.Format(@"
        <s:Envelope xmlns:s='http://www.w3.org/2003/05/soap-envelope'
              xmlns:a='http://www.w3.org/2005/08/addressing'
              xmlns:u='http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd'>
          <s:Header>
            <a:Action s:mustUnderstand='1'>http://schemas.xmlsoap.org/ws/2005/02/trust/RST/Issue</a:Action>
            <a:ReplyTo>
              <a:Address>http://www.w3.org/2005/08/addressing/anonymous</a:Address>
            </a:ReplyTo>
            <a:To s:mustUnderstand='1'>https://login.microsoftonline.com/extSTS.srf</a:To>
            <o:Security s:mustUnderstand='1'
                  xmlns:o='http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd'>
              <o:UsernameToken>
                <o:Username>{0}</o:Username>
                <o:Password>{1}</o:Password>
              </o:UsernameToken>
            </o:Security>
          </s:Header>
          <s:Body>
            <t:RequestSecurityToken xmlns:t='http://schemas.xmlsoap.org/ws/2005/02/trust'>
              <wsp:AppliesTo xmlns:wsp='http://schemas.xmlsoap.org/ws/2004/09/policy'>
                <a:EndpointReference>
                  <a:Address>{2}</a:Address>
                </a:EndpointReference>
              </wsp:AppliesTo>
              <t:KeyType>http://schemas.xmlsoap.org/ws/2005/05/identity/NoProofKey</t:KeyType>
              <t:RequestType>http://schemas.xmlsoap.org/ws/2005/02/trust/Issue</t:RequestType>
              <t:TokenType>urn:oasis:names:tc:SAML:1.0:assertion</t:TokenType>
            </t:RequestSecurityToken>
          </s:Body>
        </s:Envelope>", username, password, tenant);
        }​
    

Um die SOAP Anfrage abzusenden verwende ich die WebClient Klasse. Diese ermöglicht es, einfach HTTP POST Anfragen gegen einen Server zu senden.​

SAML Antwort mit Binary Security Token

Nachdem die Anfrage abgesendet wurde, sollte ein SOAP XML nach folgendem Schema empfangen werden:

i2.png

In dem BinarySecurityToken Tag ist unser Binary Security Token enthalten. Um das SOAP XML zu verarbeiten, verwende ich die XDocument Klasse. Über diese Klasse lassen sich einfach Knoten selektieren. Nachfolgend nun die Methode, welche die Anfrage absendet und anschießend den „BinarySecurityToken" Knoten selektiert:

    
        private static string GetBinarySecurityToken(string username, string password, stringtenant)
        {
            using (var webClient = new WebClient())
            {
                var soapXml = GetSoapXml(username, password, tenant);
                var soapResponse = webClient.UploadString("https://login.microsoftonline.com/extSTS.srf", soapXml);

                var document = XDocument.Parse(soapResponse);
                var binarySecurityTokenElement = document.Descendants().FirstOrDefault(d => d.Name.LocalName == "BinarySecurityToken");
                if (binarySecurityTokenElement == null)
                {
                    return null;
                }
                return binarySecurityTokenElement.Value;
            }
        }
    

Binary Security Token an SharePoint Online senden

​​Das erhaltene Binary Security Token kann anschließend verwendet werden, um sich am SharePoint Online Server zu authentifizieren. Hierzu wird folgender Endpunkt benötigt: https://tenant.sharepoint.com/_forms/default.aspx?wa=wsignin1.0

Die Anfrage muss wieder mit dem HTTP POST Verb verschickt werden. Zusätzlich muss als Content Type der application/x-www-form-urlencoded Header mit versendet werden. Um die Anfrage zu versenden habe ich mich diesmal für die WebRequest Klasse entschieden, da diese eine Eigenschaft AllowAutoRedirect enthält, welche wir auf false setzten müssen. Die Methode, um die Anfrage zu versenden, sieht wie folgt aus:​

    
        private static WebResponse GetWebResponse(string url, string method, string data, stringcontentType)
        {
            var webRequest = WebRequest.Create(url) as HttpWebRequest;
            webRequest.Method = method;
            if (!string.IsNullOrEmpty(contentType))
            {
                webRequest.ContentType = contentType;
            }

            webRequest.AllowAutoRedirect = false;

            if (!string.IsNullOrEmpty(data))
            {
                var bytes = Encoding.ASCII.GetBytes(data);
                webRequest.ContentLength = bytes.Length;
                var requestStream = webRequest.GetRequestStream();
                requestStream.Write(bytes, 0, bytes.Length);
                requestStream.Close();
            }

            return webRequest.GetResponse();
        }
    

Der Aufruf der Methode GetBinarySecurityToken und der GetWebResponse Methode könnte dann wie folgt aussehen:

    
        var binarySecurityToken = GetBinarySecurityToken("username@tenant.de", "mypassword","http://tenant.sharepoint.com");
        var webResponse = GetWebResponse("https://tenant.sharepoint.com/_forms/default.aspx?wa=wsignin1.0", "POST", binarySecurityToken, "application/x-www-form-urlencoded");
    

Authentifizierung-Cookies empfangen

Der SharePoint Online Server sendet als Cookie die zwei benötigten Cookies mit:

  • FedAuth
  • RtFa

Das folgende Bild zeigt eine Beispiel Antwort, welche mit Fiddler aufgezeichnet wurde.

i3.png

Um diese beiden Informationen zu speichern, habe ich eine Klasse AccessToken erstellt.​

    
        private class AccessToken
        {
            public string RtFa { get; set; }

            public string FedAuth { get; set; }
        }​
    

Um das RtFa und FedAuth Cookie auszulesen habe ich eine Methode erstellt, welche die Cookies ausliest und eine neue Instanz der Klasse AccessToken zurück gibt.

    
        private static AccessToken GetAccessToken(string binarySecurityToken)
        {
            var webResponse = GetWebResponse("https://tenant.sharepoint.com/_forms/default.aspx?wa=wsignin1.0", "POST", binarySecurityToken, "application/x-www-form-urlencoded");

            if (webResponse.Headers.AllKeys.Contains("Set-Cookie"))
            {
                var values = webResponse.Headers.GetValues("Set-Cookie");
                ​var rtFaCookieValue = values.FirstOrDefault(v => v.StartsWith("rtFa"));
                ​var rtFa = string.Empty;
                ​var fetAuth = string.Empty;
                ​if (!string.IsNullOrEmpty(rtFaCookieValue))
                ​{
                ​​    rtFa = rtFaCookieValue.Split(';').FirstOrDefault();
                ​}
                ​​var fedAuthCookieValue = values.FirstOrDefault(v => v.StartsWith("FedAuth"));
                if (!string.IsNullOrEmpty(fedAuthCookieValue))
                ​{
                ​    fetAuth = fedAuthCookieValue.Split(';').FirstOrDefault();
                ​}

                ​return new AccessToken
                    ​{
                        FedAuth = fetAuth,
                        RtFa = rtFa
                    ​};
            }

            return null;
        }​
    

REST/OData Anfrage senden

Mit den empfangen RtFa und FedAuth Cookies können wir jetzt Anfragen gegen den REST/OData Endpunkt absenden. Diese beiden Cookies müssen wir bei jeder Anfrage anhängen, damit wir uns gegenüber dem SharePoint Online Server authentifizieren können. Hierzu habe ich die Methode ExecuteGet vorbereitet, welche als Argument einn Endpunkt URL und eine Sammlung von Cookies entgegennimmt.

    
        private static string ExecuteGet(string url, IEnumerable<string> cookies)
        {
            using (var webClient = new WebClient())
            {
                ​if (cookies != null)
                {
                    webClient.Headers[System.Net.HttpRequestHeader.Cookie] = string.Join(";", cookies);
                }

                return webClient.DownloadString(url);
            }
        }
    

Um zu demonstrieren, dass nun erfolgreich Abfragen gegen den REST/OData Endpunkt abgesendet werden können, habe ich mich für folgende Methode entschieden: https://tenant.sharepoint.com/_api/web/lists?$select=Title

Diese Anfrage gibt die Listen-Titel des Webs zurück. Mehr Informationen über die REST/OData Schnittstelle finden Sie hier.

Die Main Methode, welche alle Methoden in der korrekten Reihenfolge ausführt, könnte wie folgt aussehen:

    
        private static void Main(string[] args)
        {
            var binarySecurityToken = GetBinarySecurityToken("username@tenant.de", "password","http://tenant.sharepoint.com");
            var accessToken = GetAccessToken(binarySecurityToken);
            var listTitleXmlResponse = ExecuteGet("https://tenant.sharepoint.com/_api/web/lists?$select=Title", new string[]
                {
                    accessToken.FedAuth,
                    accessToken.RtFa
                });
        }
    

Die Variable listTitleXmlResponse sollte nach dem Ausführen der Operation die Listen-Titel auf Web Ebene enthalten.

i4.png

Comments