Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

GraphServiceClient returns only user email and name. #2550

Closed
DmitryBorodiy opened this issue Jun 14, 2024 · 5 comments
Closed

GraphServiceClient returns only user email and name. #2550

DmitryBorodiy opened this issue Jun 14, 2024 · 5 comments

Comments

@DmitryBorodiy
Copy link

DmitryBorodiy commented Jun 14, 2024

using Azure.Core;
using Azure.Identity;
using Microsoft.Graph;
using SampleWpf;
using System.Net.Http.Headers;
using System.Net.Http;
using System.Threading.Tasks;
using System.Diagnostics;

namespace QuickFind.Services.Account
{
    public static class MsalUserProvider
    {
        #region Consts
        private const string MsalClientId = "...";
        private const string MsalTenantId = "...";
        #endregion

        #region Props
        public static GraphServiceClient UserClient { get; set; }
        private static InteractiveBrowserCredential _deviceCodeCredential;
        private static InteractiveBrowserCredential _interactiveBrowserCredential;
        private const string CacheFile = "Properties/temp.bin";

        private static string[] Scopes =>
        [
            "User.Read",
            "User.Read.All",
            "Mail.ReadBasic",
            "Mail.Read",
            "Tasks.Read",
            "Tasks.ReadWrite",
            "Tasks.Read.Shared",
            "Tasks.ReadWrite.Shared",
        ];
        #endregion

        #region Methods

        public static async Task InitializeGraphForUserAuth()
        {
            var options = new DeviceCodeCredentialOptions
            {
                AuthorityHost = AzureAuthorityHosts.AzurePublicCloud,
                ClientId = MsalClientId,
                TenantId = MsalTenantId,
                DeviceCodeCallback = (code, cancellation) =>
                {
                    Console.WriteLine(code.Message);
                    return Task.FromResult(0);
                },
            };

            var deviceCodeCredential = new DeviceCodeCredential(options);
            UserClient = new GraphServiceClient(deviceCodeCredential, Scopes);

            if(UserClient != null)
            {
                var user = await UserClient.Me.GetAsync();
                var todo = await UserClient.Me.Todo.Lists.GetAsync();

                Debug.WriteLine(user);
            }
        }

        public static async void InitializeGraph()
        {
            await InitializeGraphForUserAuth();
        }

        #endregion
    }
}

If I try to get something else, like inbox or todo, but it throws an ODataError.
This new version of code works us expected because I using PublicClientApplicationBuilder and removed Tenant id.

using Microsoft.Graph;
using Microsoft.Identity.Client;
using QuickFind.Model;
using QuickFind.Properties;
using QuickFind.Security;
using System;
using System.Diagnostics;
using System.Linq;
using System.Net.Http.Headers;
using System.Net.Http;
using System.Threading.Tasks;
using Microsoft.Graph.Models;
using System.IO;

namespace QuickFind.Services.Account
{
    public static class MsalUserProvider
    {
        #region Consts
        private const string MsalClientId = "...";
        #endregion

        #region Props
        public static GraphServiceClient Client { get; private set; }
        public static IPublicClientApplication ClientApp { get; set; } = null;
        private static AuthenticationResult AuthResult { get; set; }
        private static JwtCaching Jwt { get; set; } = new JwtCaching();
        #endregion

        #region Methods

        public static void InitService()
        {
            if(ClientApp == null)
               ClientApp = PublicClientApplicationBuilder.Create(MsalClientId)
                     .WithDefaultRedirectUri()
                     .Build();
        }

        public static async Task<bool> AuthAsync()
        {
            try
            {
                var accounts = await ClientApp.GetAccountsAsync();
                var firstAccount = accounts.FirstOrDefault();

                try
                {
                    AuthResult = await ClientApp.AcquireTokenSilent(Permissions.Scopes, firstAccount)
                        .ExecuteAsync();
                }
                catch(MsalUiRequiredException)
                {
                    AuthResult = await ClientApp.AcquireTokenInteractive(Permissions.Scopes)
                                .WithAccount(accounts.FirstOrDefault())
                                .WithPrompt(Microsoft.Identity.Client.Prompt.SelectAccount)
                                .ExecuteAsync();
                }

                if(AuthResult != null)
                {
                    Client = GetGraphServiceClient(AuthResult.AccessToken);
                    Jwt.WriteJwt(AuthResult.AccessToken);

                    return true;
                }
                else return false;
            }
            catch(Exception ex)
            {
                Debug.WriteLine(ex.Message + ex.Source);
                Settings.IsSignedIn = false;
                return false;
            }
        }

        public static GraphServiceClient GetGraphServiceClient(string token)
        {
            HttpClient client = new HttpClient();
            client.DefaultRequestHeaders.Authorization
                = new AuthenticationHeaderValue("Bearer", token);

            return new GraphServiceClient(httpClient: client);
        }

        public static async Task<bool> SignInAsync()
        {
            try
            {
                if (!Jwt.IsExists)
                    await AuthAsync();
                else
                {
                    string jwt = Jwt.GetJwtToken();

                    if(!string.IsNullOrEmpty(jwt))
                       Client = GetGraphServiceClient(Jwt.GetJwtToken());
                    else
                       await AuthAsync();
                }

                if(Client != null)
                {
                    Settings.IsSignedIn = true;
                    return await App.CurrentUser.InitFromGraphAsync(Client);
                }
                else
                {
                    Settings.IsSignedIn = false;
                    return false;
                }
            }
            catch(Exception ex)
            {
                Debug.WriteLine(ex.Message + ex.Source);
                Settings.IsSignedIn = false;

                return false;
            }
        }


        #endregion
    }
}
@DmitryBorodiy
Copy link
Author

There is another problem: GetAccountsAsync() never returns accounts. I would like to be able to login using ClientApp.AcquireTokenSilent but it forgets the account I'm logged in with after closing the Avalonia or WPF application.

@DmitryBorodiy
Copy link
Author

Although in a UWP application this approach works great.

var accounts = await ClientApp.GetAccountsAsync();
var firstAccount = accounts.FirstOrDefault();

try
{
    AuthResult = await ClientApp.AcquireTokenSilent(Permissions.Scopes, firstAccount)
        .ExecuteAsync();
}
catch(MsalUiRequiredException)
{
    AuthResult = await ClientApp.AcquireTokenInteractive(Permissions.Scopes)
                .WithAccount(accounts.FirstOrDefault())
                .WithPrompt(Microsoft.Identity.Client.Prompt.SelectAccount)
                .ExecuteAsync();
}

@DmitryBorodiy
Copy link
Author

Although in a UWP application this approach works great.

var accounts = await ClientApp.GetAccountsAsync();
var firstAccount = accounts.FirstOrDefault();

try
{
    AuthResult = await ClientApp.AcquireTokenSilent(Permissions.Scopes, firstAccount)
        .ExecuteAsync();
}
catch(MsalUiRequiredException)
{
    AuthResult = await ClientApp.AcquireTokenInteractive(Permissions.Scopes)
                .WithAccount(accounts.FirstOrDefault())
                .WithPrompt(Microsoft.Identity.Client.Prompt.SelectAccount)
                .ExecuteAsync();
}

In Avalonia or WPF app this code doesn't work. It's just do AcquireTokenInteractive when I restarting application.

@DmitryBorodiy
Copy link
Author

I did as stated here https://learn.microsoft.com/en-us/azure/developer/mobile-apps/azure-mobile-apps/quickstarts/avalonia/authentication. But this does not work in the case of a desktop application.

@DmitryBorodiy
Copy link
Author

Untitled.video.-.Made.with.Clipchamp.mp4

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant