Search code examples
c#outlookmicrosoft-graph-apiazure-identity

How to get the user taken to a website to complete authentication and not the console?


I am trying to add authentication to my app for Microsoft Graph and I have made some progress with this. I followed this tutorial and I have it working.

But, this is how it is asking the user to authenticate at the moment:

void InitializeGraph(Settings settings)
{
    GraphHelper.InitializeGraphForUserAuth(settings,
        (info, cancel) =>
        {
            // Display the device code message to
            // the user. This tells them
            // where to go to sign in and provides the
            // code to use.
            Console.WriteLine(info.Message);
            return Task.FromResult(0);
        });
}

It works and I can follow it through, but it is directing the user to the console, eg:

To sign in, use a web browser to open the page https://microsoft.com/devicelogin and enter the code RB2RUD56D to authenticate.

How can I get it to take them to the website instead for the user to proceed? The InitializeGraphForUserAuth function is:

public static void InitializeGraphForUserAuth(Settings settings,
    Func<DeviceCodeInfo, CancellationToken, Task> deviceCodePrompt)
{
    _settings = settings;

    var options = new DeviceCodeCredentialOptions
    {
        ClientId = settings.ClientId,
        TenantId = settings.TenantId,
        DeviceCodeCallback = deviceCodePrompt,
    };

    _deviceCodeCredential = new DeviceCodeCredential(options);

    _userClient = new GraphServiceClient(_deviceCodeCredential, settings.GraphUserScopes);
}

Solution

  • The first thing that I noticed that that the DeviceCodeInfo info variable has a VerificationUri property which was what I needed to use:

    Verification URL where the user must navigate to authenticate using the device code and credentials.

    Next, I needed a way open opening this URL in the users browser. .NET8 was quirky in this regard but I found the solution in one of the answers to this question:

    Next, I needed to tackle the issue of the user code. This is also available in the DeviceCodeInfo as a property. The best solution I came up with was to copy it to the clipboard, as it is not possible to automatically populate the form by adding parameters to the URL. As expected, .NET8 was again a bit quirky concerning clipboard management. An answer to this question helped:

    I ended up implementing a PowerShell solution for copying to the clipboard:

    static class WindowsClipboard
    {
        public static void SetText(string text)
        {
            var powershell = new Process
            {
                StartInfo = new ProcessStartInfo
                {
                    FileName = "powershell",
                    Arguments = $"-command \"Set-Clipboard -Value \\\"{text}\\\"\""
                }
            };
            powershell.Start();
            powershell.WaitForExit();
        }
    
        public static string GetText()
        {
            var powershell = new Process
            {
                StartInfo = new ProcessStartInfo
                {
                    RedirectStandardOutput = true,
                    FileName = "powershell",
                    Arguments = "-command \"Get-Clipboard\""
                }
            };
    
            powershell.Start();
            string text = powershell.StandardOutput.ReadToEnd();
            powershell.StandardOutput.Close();
            powershell.WaitForExit();
            return text.TrimEnd();
        }
    }
    

    Putting it all together:

    public static async Task InitializeGraph(GraphSettings settings)
    {
            await GraphHelper.InitializeGraphForUserAuth(settings,
                 (info, cancel)=>
                {
                    WindowsClipboard.SetText(info.UserCode);
                    System.Diagnostics.Process.Start("explorer", info.VerificationUri.ToString());
    
                    return Task.FromResult(0);
                });
    }