Friday 28 July 2017

C# CSOM Sharepoint Bearer - Azure Active Directory Authentication

I recently had to get our implementation of CSOM working with Authorization bearer token, this was not as stright forward as I would have liked. I searched many links and documents on how to configure it but I was still missing one final piece of information. I am sure microsoft will change this implementation but for today I have a working example and understand it a bit more.

1. You will first need an azure subscription, once created click the More Services link at the bottom right, then search for Azure Active Directory, select this.


2. You will be given a tenant name otherwise known as a domain name, this can be found under the Domain names item as shown below. In this case you can see I have my default domain that azure created for me andrew.onmicrosoft.com. You will need to have this available, so copy it to a text file for later use.


3. Now find the App registrations item and click the New application registration. You need to do this to create a mobile access point. This is what the code will register with and then knows the required permissions for SharePoint.


4. Enter a name for the app, then select Native as this will be a WCF client that connects. Also enter a Redirect URI, this doesn't have to be a real URL it is just an identifier. I just use the name in this case after the http://


5. You will now see that you have a new native app registered, click on this to go to the details about the new registered app.


6. Here you will need to click on the Properties which will display the Application ID, make a note of this guid as you will need it later on.


7. Because we need SharePoint you will also need to click the Required permissions item in the API Access section. Find the SharePoint one and give the require permissions. I don't show this as I don't have SharePoint on my test azure environment, but the company I did this for did have an SharePoint 365 access. You will most probably have azure active directory read turned on as below, so this will be fine as the first test.


8. After realizing I didn't have SharePoint on my local azure, you basically need to have an web app that given access by an app registration. (This bit can be ignored if you have a SharePoint site linked to the azure active directory). So to simulate we will register a new app this time making it a Web app / API.


9. Open up the newly created web app / api and change the App ID URI, this is the resource that you want to get permission to. I removed the guid and just put a name at the end, copy this for use later on.


10. You will now want to go back to the Native app and add the newly created web app as shown below, select it and add it to the Required permissions. Make sure to give access via the checkbox(es) after selecting.



11. Now for the code, I created a new WCF project in visual studio, this is the code behind the MainWindow. The important thing to note is that we get all the azure details from the app.config file, this is shown next.

public partial class MainWindow : Window
    {

        private static string aadInstance = ConfigurationManager.AppSettings["ida:AADInstance"];
        private static string tenant = ConfigurationManager.AppSettings["ida:Tenant"];
        private static string applicationId = ConfigurationManager.AppSettings["ida:ApplicationId"];
        Uri redirectUri = new Uri(ConfigurationManager.AppSettings["ida:RedirectUri"]);
        private static string resourceUrl = ConfigurationManager.AppSettings["ida.ResourceUrl"];

        private static string authority = String.Format(CultureInfo.InvariantCulture, aadInstance, tenant);

        private AuthenticationContext authContext = null;

        public MainWindow()
        {
            InitializeComponent();

            authContext = new AuthenticationContext(authority);
        }

        public async void CheckForCachedToken(PromptBehavior propmptBehavior)
        {
            //
            // As the application starts, try to get an access token without prompting the user.  If one exists, populate the To Do list.  If not, continue.
            //
            AuthenticationResult result = null;
            try
            {
                result = await authContext.AcquireTokenAsync(resourceUrl, applicationId, redirectUri, new PlatformParameters(propmptBehavior));
                TokenTextBox.Text = result.AccessToken;
                // A valid token is in the cache - get the To Do list.
                GetTokenButton.Content = "Clear Cache";
            }
            catch (AdalException ex)
            {
                if (ex.ErrorCode == "user_interaction_required")
                {
                    // There are no tokens in the cache.  Proceed without calling the To Do list service.
                }
                else
                {
                    // An unexpected error occurred.
                    string message = ex.Message;
                    if (ex.InnerException != null)
                    {
                        message += "Inner Exception : " + ex.InnerException.Message;
                    }
                    MessageBox.Show(message);
                }
                return;
            }
        }

        private void GetTokenButton_Click(object sender, RoutedEventArgs e)
        {
            // If there is already a token in the cache, clear the cache and update the label on the button.
            if (GetTokenButton.Content.ToString() == "Clear Cache")
            {
                TokenTextBox.Clear();
                authContext.TokenCache.Clear();
                // Also clear cookies from the browser control.
                ClearCookies();
                GetTokenButton.Content = "Get Token";
            }
            CheckForCachedToken(PromptBehavior.Always);
        }

        // This function clears cookies from the browser control used by ADAL.
        private void ClearCookies()
        {
            const int INTERNET_OPTION_END_BROWSER_SESSION = 42;
            InternetSetOption(IntPtr.Zero, INTERNET_OPTION_END_BROWSER_SESSION, IntPtr.Zero, 0);
        }

        [DllImport("wininet.dll", SetLastError = true)]
        private static extern bool InternetSetOption(IntPtr hInternet, int dwOption, IntPtr lpBuffer, int lpdwBufferLength);

    }


The App.config file value

<appSettings>

    <add key="ida:AADInstance" value="https://login.microsoftonline.com/{0}" />
    <add key="ida:Tenant" value="andrew.onmicrosoft.com" />
    <add key="ida:ApplicationId" value="fefeb838-0000-0000-0000-000000000" /> <!--The Application ID also called the Client ID-->
    <add key="ida:RedirectUri" value="http://Azure-Test-Bearer" />
    <add key="ida.ResourceUrl" value="https://andrew.onmicrosoft.com/Azure-Test-Bearer-Web" /> <!--sharepoints location not the APP ID URI-->
   
  </appSettings>


12. The important thing to note in all of this is that the ResourceUrl, in this case is just a test app to make sure we have access, but if you changed this URL to your SharePoint site and as long as it's linked via the active directory, you will get a token back.

With this token you should now be able to make http request with the following header

Authorization: bearer <TOKEN>


Sunday 23 July 2017

GPX to FIT (Garmin) converter

I recently tried to understand why a gpx file I dropped onto my garmin fenix 3 crashed. I investigated what garmin uses, when trying to navigate on a fenix 3 course.

I found out it was a .fit file that contained the data. I presume when someone drops a .gpx file in the NEWFILES folder on the device, it converts it to a .fit file, but was wondering if this had errors. I therefore created my own gpx to fit converter. I can happily say it works and have done a few routes now using the fit file instead of the gpx one.

This uses the ANT fit SDK https://www.thisisant.com/resources/fit/. I wrote this in C# using .net 4.5, I've also proudly unit tested it.

I came across someone else who sounded like they did a similar thing, but think mine looks a bit better 😆, it was useful to read though http://runningbadger.blogspot.co.uk/2015/08/a-tool-to-help-with-navigation-on.html

You can download the program herehttps://drive.google.com/file/d/0B87Dcr-CTz1dZ0I3S0U2dlRqdHc/view?usp=sharing


If anyone wants to know some of the math I may be able to help having been through this already, e.g. altitude calculation or lat long to semicircle values. which is what the fit file requires.