Thursday, August 24, 2017

Piggybacking on MSDYN365 PluginRegistrationTools ADAL implementation (plagiarizing Mikael Svenson)

My brilliant colleague, Mikael Svenson, wrote a cool blog post on piggybacking on the SharePoint Online Management Shell ADAL application
Inspired by this (relatively) dirty hack I tried to figure out which applications Microsoft has given to us that may support OAuth OOTB.
Turns out, the PluginRegistrationTool does!
I fired up the PluginRegistrationTool from the SDK, hooked fiddler on to it and hit the "create new connection" button in the tool.
Checked the query string in the initial authorize request and Voila!

Splitting up this query string we get the following two values:
client_id=2ad88395-b77d-4561-9441-d0e40824f9bc
redirect_uri=app%3A%2F%2F5d3e90d6-aa8e-48a8-8f2c-58b45cc67315%2F

Cleaning up the redirect_uri gives us this nice app id redirect uri we can use:
app://5d3e90d6-aa8e-48a8-8f2c-58b45cc67315/


This allows us to piggyback on Microsoft's own app registration, which doesn't require approval for users when you distribute an application. Example taken from my blog series on using the new admin api in PowerShell

using Microsoft.IdentityModel.Clients.ActiveDirectory;
using System;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading;
using System.Threading.Tasks;

namespace MSDYN365AdminApiAndMore.Helpers
{
    public class AuthenticationHelper
    {
        private static string _clientId = "2ad88395-b77d-4561-9441-d0e40824f9bc";
        private static string _redirectUrl = "app://5d3e90d6-aa8e-48a8-8f2c-58b45cc67315/";

        private Uri _endpoint = null;
        private string _resource = null;
        private string _authority = null;
        private AuthenticationContext _authContext = null;
        private AuthenticationResult _authResult = null;

        public AuthenticationHelper(Uri endpoint)
        {
            _endpoint = endpoint;
        }

        public string Authority
        {
            get
            {
                if (_authority == null)
                {
                    DiscoverAuthority(_endpoint);
                }
                return _authority;
            }
        }

        public AuthenticationContext AuthContext
        {
            get
            {
                if (_authContext == null)
                {
                    _authContext = new AuthenticationContext(Authority, false);
                }
                return _authContext;
            }
        }

        public AuthenticationResult AuthResult
        {
            get
            {
                Authorize();
                return _authResult;
            }
        }

        public HttpMessageHandler Handler
        {
            get
            {
                return new OAuthMessageHandler(this, new HttpClientHandler());
            }
        }

        private void DiscoverAuthority(Uri discoveryUrl)
        {
            try
            {
                Task.Run(async () =>
                {
                    var ap = await AuthenticationParameters.CreateFromResourceUrlAsync(discoveryUrl);
                    _resource = ap.Resource;
                    _authority = ap.Authority;
                }).Wait();
            }
            catch (Exception e)
            {
                throw e;
            }
        }

        private void Authorize()
        {
            if (_authResult == null || _authResult.ExpiresOn.AddMinutes(-30) < DateTime.Now)
            {
                Task.Run(async () =>
                {
                    _authResult = await AuthContext.AcquireTokenAsync(_resource, _clientId, new Uri(_redirectUrl),
                    new PlatformParameters(PromptBehavior.Always));
                }).Wait();
            }
        }

        class OAuthMessageHandler : DelegatingHandler
        {
            AuthenticationHelper _auth = null;
            public OAuthMessageHandler(AuthenticationHelper auth, HttpMessageHandler innerHandler) : base(innerHandler)
            {
                _auth = auth;
            }
            protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
            {
                request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", _auth.AuthResult.AccessToken);
                return base.SendAsync(request, cancellationToken);
            }
        }
    }
}

Final thoughts:

Should you use this? No, probably not. They might change it at any time, or they could introduce connection string inputs for the tool which requires you to register your own app.
Will I be using this? Great question!

1 comment:

  1. Thanks Marius for this wonderful article. I was going through the Dynmaics 365 plan and deploy documents which has this information. Configure Windows Server 2012 R2 for Dynamics 365 applications that use OAuth / Register the client apps section in it --> Microsoft Dynamics 365 developer tools.
    Add-AdfsClient -ClientId 2ad88395-b77d-4561-9441-d0e40824f9bc -Name "Dynamics 365 Development Tools" RedirectUri app://5d3e90d6-aa8e-48a8-8f2c-58b45cc67315/


    ReplyDelete