3

Is it possible to only log in once using MS Graph? Currently whenever I call the graphServiceClient it will ask me to sign in or to choose the signed in user. Is there any way that the process of choosing the signed in user can be avoided? Thanks in advance.

Currently this is how I initialize the graphService.

    public static GraphServiceClient GetAuthenticatedClient()
    {
        GraphServiceClient graphClient = new GraphServiceClient(
            new DelegateAuthenticationProvider(
                async (requestMessage) =>
                {
                    string appID = ConfigurationManager.AppSettings["ida:AppId"];

                    PublicClientApplication PublicClientApp = new PublicClientApplication(appID);
                    string[] _scopes = new string[] { "Calendars.read", "Calendars.readwrite" };

                    AuthenticationResult authResult = null;
                    authResult = await PublicClientApp.AcquireTokenAsync(_scopes); //Opens Microsoft Login Screen    

                    // Append the access token to the request.
                    requestMessage.Headers.Authorization = new AuthenticationHeaderValue("bearer", authResult.AccessToken);
                }));
        return graphClient;
    }

Now whenever I use the standard SDK functionality it will ask me to sign in:

await graphClient.Me.Events[testID].Request().UpdateAsync(x);

And then it will ask me again when I use another function:

await graphClient.Me.Events.Request().AddAsync(newEvent);

2 Answers2

3

Yes it is possible. AcquireTokenAsync does always trigger logon. Instead, you need to implement a token cache and use AcquireTokenSilentAsync. https://learn.microsoft.com/en-us/outlook/rest/dotnet-tutorial has a web app example.

Jason Johnston
  • 17,194
  • 2
  • 20
  • 34
  • Hi Jason, thank you for your response, will this work with a windows form application as well? – William Botha Jun 29 '17 at 15:15
  • @WilliamBotha How did you get on following this tutorial about using token cache? – Andrew Truckle Sep 04 '17 at 12:46
  • 1
    Hi @AndrewTruckle. What we did was make use of the Registry. We saved the token on login. That way we weren't constantly prompted to login. If you have any questions, let me know. – William Botha Sep 04 '17 at 13:23
  • @WilliamBotha Would you mind showing me please? I know how to store a value to the registry. But the basic concept of using the registry value at login time etc. would be a great help. I have a similar question here that you might be able to flesh out with an answer? https://stackoverflow.com/questions/46037019/why-does-my-application-always-end-up-calling-program-publicclientapp-acquiretok?noredirect=1#comment79034753_46037019 – Andrew Truckle Sep 04 '17 at 13:30
  • @AndrewTruckle would you like it in a email or what? – William Botha Sep 04 '17 at 13:36
  • @WilliamBotha Email is fine. Or maybe pastebin.com? – Andrew Truckle Sep 04 '17 at 13:39
  • @WilliamBotha Thanks. This helps. However, I have two questions about the code. Is it possible to email you or something? – Andrew Truckle Sep 04 '17 at 15:09
  • @WilliamBotha The line of code just before `// set value of "abc" to "efd" ` that examines a `Login` key. What is this? It is always null and never created in the code. Also, in the exception examines the wording but in my case the wording did not match the text you used. Finally, what if they are not using English windows, will the error be a different language? – Andrew Truckle Sep 04 '17 at 15:14
  • @WilliamBotha In the `if (regKey == null || string.IsNullOrEmpty(token))` clause you need to set `token = authResult.AccessToken`. I have it working. – Andrew Truckle Sep 04 '17 at 15:57
  • @WilliamBotha I adjusted the code to first parse the expire value (if exists). If it is older than `DateTime.Now` then I know we need to show the prompt. It is all working good. Do you mind me putting the code I am using as an answer to my own question? – Andrew Truckle Sep 05 '17 at 09:50
  • @AndrewTruckle great to hear. Will do. – William Botha Sep 06 '17 at 07:16
0

I'm not sure we are in the same situation, but I have followed this tutorial: https://learn.microsoft.com/en-us/graph/tutorials/dotnet?tabs=aad&tutorial-step=3

I struggled to find a way to save user authentication tokens, so the users don't have to authenticate every time my app starts.

Then I found out that DeviceCodeCredentialOptions has TokenCachePersistenceOptions. The document clearly says:

Specifies the TokenCachePersistenceOptions to be used by the credential. If not options are specified, the token cache will not be persisted to disk.

There is an example in the document for using TokenCachePersistenceOptions. However, if you using Microsoft Graph, you need to tweak the code a little bit. Here is the code that works for me:

public static readonly string[] GraphUserScopes = new[]
{
    "user.read",
    "mail.read",
    "mail.send",
    "Files.ReadWrite.All",
};

public static async Task SampleRunAsync()
{
    var deviceCodeCredentialOptions = new DeviceCodeCredentialOptions()
    {
        ClientId = ApplicationClientId,
        TenantId = DirectoryTenantId,
        DeviceCodeCallback = (info, cancle) =>
        {
            // 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);
        },
        TokenCachePersistenceOptions = new TokenCachePersistenceOptions() {Name = TokenName}
    };
    DeviceCodeCredential deviceCodeCredential;
    if (File.Exists(TokenFp))
    {
        using var fileStream = new FileStream(TokenFp, FileMode.Open, FileAccess.Read);
        deviceCodeCredentialOptions.AuthenticationRecord = await AuthenticationRecord.DeserializeAsync(fileStream).ConfigureAwait(Program.DebugMode);
        deviceCodeCredential = new DeviceCodeCredential(deviceCodeCredentialOptions);
    }
    else
    {
        deviceCodeCredential = new DeviceCodeCredential(deviceCodeCredentialOptions);
        var authenticationRecord = await deviceCodeCredential.AuthenticateAsync(new TokenRequestContext(GraphUserScopes)).ConfigureAwait(Program.DebugMode);
        //
        using var fileStream1 = new FileStream(TokenFp, FileMode.Create, FileAccess.Write);
        await authenticationRecord.SerializeAsync(fileStream1).ConfigureAwait(Program.DebugMode);

    }

    var graphServiceClient = new GraphServiceClient(deviceCodeCredential, GraphUserScopes);

    var user = await graphServiceClient.Me.GetAsync((config) =>
    {
        // Only request specific properties
        config.QueryParameters.Select = new[] {"displayName", "mail", "userPrincipalName"};
    }).ConfigureAwait(false);

    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 ?? ""}");
}
    
Hieu
  • 7,138
  • 2
  • 42
  • 34