If you write Windows Applications or PowerShell scripts to connect to multiple tenants you have probably experienced the “That didn’t work” dialog where the web based login tries to log you in with some other tenant credential as cookies are persisted from an Internet Explorer or Edge session. This was a typical problem for the SharePoint Query Tool, and I managed to add some funky code to ensure cookies were ignored when the login window popped up.
However, recently I’ve had a couple of issues where people have domain/aad joined pc’s with single sign-on to Office 365. With this in place cookies doesn’t matter. The login dialog will always try to log you in with your current user when using IE/Edge, which happens to be the browser control available when programming in Windows. Hence, using the query tool was hard to use in these scenarios.
At the moment the Query Tool uses web browser login and a FedAuth cookie for authorization. The obvious workaround is to use an ADAL app and a Bearer token instead. This is quite easy to implement, but having people register a custom ADAL app per tenant to support this login flow seemed too cumbersome. And for those who know me, I always try to find an easy way out, and a clever work around came to mind.
When using the good old SharePoint Online Management Shell, it prompt for credentials when you login.
This works fine as they have an ADAL application automatically registered in all tenants. What if we could just re-use and piggyback on this application? And you can! Monitoring some network traffic this application has a client id of
9bc3ab49-b65d-410a-85ad-de819febfddc
and the return URL of
https://oauth.spops.microsoft.com/
And this is all you need to get started. The permissions scopes for this app are listed as:
- AllProfiles.Manage
- Sites.FullControl.All
- User.Read.All
..and it’s already admin consented for you, ripe for the picking.
Below is the code of a sample C# console application using the Microsoft.Identity.Clients.ActiveDirectory nuget package for ADAL login. Look out for an updated query tool using this shortly, and you should of course improve the code to try to acquire a token silently first etc. to re-use the refresh token, something the Query Tool code will have.
using System; using System.Diagnostics; using System.Net.Http; using System.Net.Http.Headers; using System.Threading.Tasks; using ADAL = Microsoft.IdentityModel.Clients.ActiveDirectory; namespace O365LoginTest { class Program { static void Main(string[] args) { RunAsync().Wait(); } static async Task RunAsync() { // The O365 login URL string authorityUri = "https://login.windows.net/common/oauth2/authorize"; // App id for Microsoft SharePoint Online Management Shell const string clientId = "9bc3ab49-b65d-410a-85ad-de819febfddc"; // Replu URL for the Microsoft SharePoint Online Management Shell const string redirectUri = "https://oauth.spops.microsoft.com/"; // The DOMAIN you want to do API calls against string resourceUri = "https://techmikael.sharepoint.com"; IntPtr handle = Process.GetCurrentProcess().MainWindowHandle; var authCtx = new ADAL.AuthenticationContext(authorityUri); // Always prompt to allow login to more than one tenant var authParam = new ADAL.PlatformParameters(ADAL.PromptBehavior.Always, handle); var authenticationResult = await authCtx.AcquireTokenAsync(resourceUri, clientId, new Uri(redirectUri), authParam); // Sample search query using the returned Bearer token string queryUrl = resourceUri + "/_api/search/query?querytext='*'&clienttype='ContentSearchRegular'"; HttpClient client = new HttpClient(); client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", authenticationResult.AccessToken); var result = await client.GetStringAsync(queryUrl); Console.WriteLine(result); } } }