Automated log-in via AccessToken | Microsoft Graph | C#

Viewed 49

I have a problem, I can log in through my program, however I am always sent to another website. (deviceauth) Every time I start the program, I am asked to enter a code on this website. I want to be logged in directly without having to go through the previous step.

Is there a way to log in directly with the AccessToken? If yes, how?

My Goal is to log in once and everytime I start the program, I should be directly logged in and be able to look at my Mails.

My Code:

using Microsoft.Graph;
Console.WriteLine(".NET Graph Tutorial\n");

//var settings = Settings.LoadSettings("appsettings.json");
var settings = Settings.LoadSettings("appsettingsSLSTracer.json");

// Initialize Graph
InitializeGraph(settings);

// Greet the user by name
await GreetUserAsync();

int choice = -1;

while (choice != 0)
{
  Console.WriteLine("Please choose one of the following options:");
  Console.WriteLine("0. Exit");
  Console.WriteLine("1. Display access token");
  Console.WriteLine("2. List my inbox");
  Console.WriteLine("3. Send mail");
  Console.WriteLine("4. Make a Graph call");

  try
  {
    choice = int.Parse(Console.ReadLine() ?? string.Empty);
  }
  catch (System.FormatException)
  {
    // Set to invalid value
    choice = -1;
  }

  switch (choice)
  {
    case 0:
      // Exit the program
      Console.WriteLine("Goodbye...");
      break;
    case 1:
      // Display access token
      await DisplayAccessTokenAsync();
      break;
    case 2:
      // List emails from user's inbox
      await ListInboxAsync();
      break;
    case 3:
      // Send an email message
      await SendMailAsync();
      break;
    case 4:
      // Run any Graph code
      await MakeGraphCallAsync();
      break;
    default:
      Console.WriteLine("Invalid choice! Please try again.");
      break;
  }
}
// </ProgramSnippet>

// <InitializeGraphSnippet>
void InitializeGraph(Settings settings)
{
  //if (System.IO.File.Exists(@"C:\windows\temp\msaccesstokenSLSTracer.txt"))
  //{
    //_userClient = System.IO.File.ReadAllText("C:\\temp\\msaccesstoken.txt");
    //return;
  //}
  //else
  {
    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);
    });
  }
}
// </InitializeGraphSnippet>

// <GreetUserSnippet>
async Task GreetUserAsync()
{
  try
  {
    var user = await GraphHelper.GetUserAsync();
    Console.WriteLine($"Hello, {user?.DisplayName}!");
    // For Work/school accounts, email is in Mail property
    // Personal accounts, email is in UserPrincipalName
    Console.WriteLine($"Email: {user?.Mail ?? user?.UserPrincipalName ?? ""}");
  }
  catch (Exception ex)
  {
    Console.WriteLine($"Error getting user: {ex.Message}");
  }
}
// </GreetUserSnippet>

// <DisplayAccessTokenSnippet>
async Task DisplayAccessTokenAsync()
{
  try
  {
    var userToken = await GraphHelper.GetUserTokenAsync();
    Console.WriteLine($"User token: {userToken}");
  }
  catch (Exception ex)
  {
    Console.WriteLine($"Error getting user access token: {ex.Message}");
  }
}
// </DisplayAccessTokenSnippet>

// <ListInboxSnippet>
async Task ListInboxAsync()
{
  try
  {
    var messagePage = await GraphHelper.GetInboxAsync();

    // Output each message's details
    foreach (var message in messagePage.CurrentPage)
    {
      Console.WriteLine($"Message: {message.Subject ?? "NO SUBJECT"}");
      Console.WriteLine($"  From: {message.From?.EmailAddress?.Name}");
      Console.WriteLine($"  Status: {(message.IsRead!.Value ? "Read" : "Unread")}");
      //Console.WriteLine($"  BodyPreview: {message.Body}");
      Console.WriteLine($"  Attachments: {message.Attachments?.Count ?? 0}");
      Console.WriteLine($"  Received: {message.ReceivedDateTime?.ToLocalTime().ToString()}\n");
    }

    // If NextPageRequest is not null, there are more messages
    // available on the server
    // Access the next page like:
    // messagePage.NextPageRequest.GetAsync();
    var moreAvailable = messagePage.NextPageRequest != null;

    Console.WriteLine($"\nMore messages available? {moreAvailable}");
  }
  catch (Exception ex)
  {
    Console.WriteLine($"Error getting user's inbox: {ex.Message}");
  }
}
// </ListInboxSnippet>

// <SendMailSnippet>
async Task SendMailAsync()
{
  try
  {
    // Send mail to the signed-in user
    // Get the user for their email address
    var user = await GraphHelper.GetUserAsync();

    var userEmail = user?.Mail ?? user?.UserPrincipalName;

    if (string.IsNullOrEmpty(userEmail))
    {
      Console.WriteLine("Couldn't get your email address, canceling...");
      return;
    }

    await GraphHelper.SendMailAsync("Test des Programmes",
        "Es hat geklappt", "Testobjekt1919@outlook.com");  //userEmail

    Console.WriteLine("Mail sent.");
  }
  catch (Exception ex)
  {
    Console.WriteLine($"Error sending mail: {ex.Message}");
  }
}
// </SendMailSnippet>

// <MakeGraphCallSnippet>
async Task MakeGraphCallAsync()
{
  await GraphHelper.MakeGraphCallAsync();
}
// </MakeGraphCallSnippet>

Other Code:

// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.

using Azure.Core;
using Azure.Identity;
using Microsoft.Graph;

class GraphHelper
{
    #region User-auth
    // <UserAuthConfigSnippet>
    // Settings object
    private static Settings? _settings;
    // User auth token credential
    private static DeviceCodeCredential? _deviceCodeCredential;
    // Client configured with user authentication
    private static GraphServiceClient? _userClient;

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

    _deviceCodeCredential = new DeviceCodeCredential(deviceCodePrompt,
        settings.AuthTenant, settings.ClientId);

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

    /// Todo Idee, hier prüfen ob wir nicht den Token mitsenden können
    //if (System.IO.File.Exists(@"C:\windows\temp\msaccesstokenSLSTracer.txt"))
    //{  }
    //_userClient.Token = System.IO.File.ReadAllText("C:\\temp\\msaccesstoken.txt");
  }
  // </UserAuthConfigSnippet>

  // <GetUserTokenSnippet>
  public static async Task<string> GetUserTokenAsync()
  {
    // Ensure credential isn't null
    _ = _deviceCodeCredential ??
        throw new System.NullReferenceException("Graph has not been initialized for user auth");

    // Ensure scopes isn't null
    _ = _settings?.GraphUserScopes ?? throw new System.ArgumentNullException("Argument 'scopes' cannot be null");

    // Request token with given scopes
    var context = new TokenRequestContext(_settings.GraphUserScopes);
    var response = await _deviceCodeCredential.GetTokenAsync(context);

    System.IO.File.WriteAllText( @"C:\windows\temp\msaccesstokenSLSTracer.txt", response.Token);
    return response.Token;
  }
  // </GetUserTokenSnippet>

  // <GetUserSnippet>
  public static Task<User> GetUserAsync()
  {
    // Ensure client isn't null
    _ = _userClient ??
        throw new System.NullReferenceException("Graph has not been initialized for user auth");

    return _userClient.Me
        .Request()
        .Select(u => new
        {
          // Only request specific properties
          u.DisplayName,
          u.Mail,
          u.UserPrincipalName
        })
        .GetAsync();
  }
    // </GetUserSnippet>

    // <GetInboxSnippet>
    public static Task<IMailFolderMessagesCollectionPage> GetInboxAsync()
    {
        // Ensure client isn't null
        _ = _userClient ??
            throw new System.NullReferenceException("Graph has not been initialized for user auth");

        return _userClient.Me
            // Only messages from Inbox folder
            .MailFolders["Inbox"]
            .Messages
            .Request()
            .Select(m => new
            {
                // Only request specific properties
                m.From,
                m.IsRead,
                m.ReceivedDateTime,
                m.Subject,
                m.Attachments
                //m.BodyPreview,
                //m.Body
            })
            // Get at most 25 results
            .Top(15)
            // Sort by received time, newest at the top
            .OrderBy("ReceivedDateTime DESC")
            .GetAsync();
    }
    // </GetInboxSnippet>

    // <SendMailSnippet>
    public static async Task SendMailAsync(string subject, string body, string recipient)
    {
        // Ensure client isn't null
        _ = _userClient ??
            throw new System.NullReferenceException("Graph has not been initialized for user auth");

        // Create a new message
        var message = new Message
        {
            Subject = subject,
            Body = new ItemBody
            {
                Content = body,
                ContentType = BodyType.Text
            },
            ToRecipients = new Recipient[]
            {
                new Recipient
                {
                    EmailAddress = new EmailAddress
                    {
                        Address = recipient
                    }
                }
            }
        };

        // Send the message
        await _userClient.Me
            .SendMail(message)
            .Request()
            .PostAsync();
    }
    // </SendMailSnippet>
    #endregion

    #pragma warning disable CS1998
    // <MakeGraphCallSnippet>
    // This function serves as a playground for testing Graph snippets
    // or other code
    public async static Task MakeGraphCallAsync()
    {
        // INSERT YOUR CODE HERE
        // Note: if using _appClient, be sure to call EnsureGraphForAppOnlyAuth
        // before using it.
        // EnsureGraphForAppOnlyAuth();
    }
    // </MakeGraphCallSnippet>
}
1 Answers

At the moment your code is using the device code oAuth flow https://learn.microsoft.com/en-us/azure/active-directory/develop/v2-oauth2-device-code so you will always be redirected/prompt that way. For unattended apps you generally want to use the client credentials flow https://learn.microsoft.com/en-us/azure/active-directory/develop/v2-oauth2-client-creds-grant-flow and application permissions.

If you still want to use delegate access (eg user creds) then you can look at using a persistent cache https://pnp.github.io/pnpcore/demos/Demo.PersistentTokenCache/README.html in that case you should be using the authorization code flow https://learn.microsoft.com/en-us/azure/active-directory/develop/v2-oauth2-auth-code-flow eg https://learn.microsoft.com/en-us/azure/active-directory/develop/scenario-desktop-acquire-token-interactive?tabs=dotnet a very simplified implementation would look like

        GraphServiceClient graphServiceClient = new GraphServiceClient(new DelegateAuthenticationProvider(async (requestMessage) =>
        {
            var authResult = publicClientApplication.AcquireTokenInteractive(new[] { "https://graph.microsoft.com/Mail.Read" })
                                  .ExecuteAsync().GetAwaiter().GetResult();
            requestMessage.Headers.Authorization =
                new AuthenticationHeaderValue("Bearer", authResult.AccessToken);
        }), httpProvider
         );

but you need a more complex implementation to use caching

Related