0

I have developed a custom tool to send an email using token and office365.This tool has been integrated with Tibco Spotfire. It works well when I use it in Spotfire client(desktop) when I deploy to web , it does not acquire the token silently, I need to retrieve the cached token in web player since Spotfire does not allow pop-up and interactive mode in web is not an option.
I will select an account in interactive mode in desktop app and use it in web that is my requirement. Here is code:

private async Task AccessTokenAsync() {

        AuthenticationResult authToken = null;
      
       
        // It's recommended to create a separate PublicClient Application for each tenant
        // but only one CacheHelper object
        var pca = CreatePublicClient("https://login.microsoftonline.com/organizations");
        var cacheHelper = await CreateCacheHelperAsync().ConfigureAwait(false);
        cacheHelper.RegisterCache(pca.UserTokenCache);
        //To delete the accounts as requested by user from cached 
        if (!UseToken)
        { 
            var accounts2Delete = await pca.GetAccountsAsync().ConfigureAwait(false);
            foreach (var a2d in accounts2Delete)
            {
                // Console.WriteLine($"Removing account for {acc.Username}");
                await pca.RemoveAsync(a2d).ConfigureAwait(false);
            }
        }
        
        // IMPORTANT: you should ALWAYS try to get a token silently first
        var accounts = await pca.GetAccountsAsync().ConfigureAwait(false);
        if (accounts.Any()& (UseToken))
        {
            authToken = await pca.AcquireTokenSilent(Config.Scopes, accounts.FirstOrDefault())
                .ExecuteAsync()
                .ConfigureAwait(false);

        }
        else
        {
            UseToken = true;
            authToken = await pca.AcquireTokenInteractive(Config.Scopes)
                                .ExecuteAsync()
                                .ConfigureAwait(false);
            
        }

        
        var oauth2 = new SaslMechanismOAuth2(authToken.Account.Username, authToken.AccessToken);
        using (var client = new SmtpClient())
        {
            await client.ConnectAsync("outlook.office365.com", 587, SecureSocketOptions.StartTls);
            await client.AuthenticateAsync(oauth2);
            await client.SendAsync(emailMessage);
            await client.DisconnectAsync(true);
        }
    }
    

private static IPublicClientApplication CreatePublicClient(string authority)

  {
          
         var appBuilder = PublicClientApplicationBuilder.Create(Config.ClientId) 
            .WithAuthority(authority)
            .WithRedirectUri("https://login.microsoftonline.com/common/oauth2/nativeclient");

        // make sure to register this redirect URI for the interactive login to work
           var app = appBuilder.Build();
          return app;
  }

private static async Task CreateCacheHelperAsync()

  {

        StorageCreationProperties storageProperties;

        try
        {
            storageProperties = new StorageCreationPropertiesBuilder(
                Config.CacheFileName,
                Config.CacheDir)
            .WithLinuxKeyring(
                Config.LinuxKeyRingSchema,
                Config.LinuxKeyRingCollection,
                Config.LinuxKeyRingLabel,
                Config.LinuxKeyRingAttr1,
                Config.LinuxKeyRingAttr2)
            .WithMacKeyChain(
                Config.KeyChainServiceName,
                Config.KeyChainAccountName)
            .WithCacheChangedEvent( // do NOT use unless really necessary, high perf penalty!
                Config.ClientId,
                Config.Authority)
            .Build();

            var cacheHelper = await MsalCacheHelper.CreateAsync(
                storageProperties).ConfigureAwait(false);
            cacheHelper.VerifyPersistence();
            return cacheHelper;

        }
        catch (MsalCachePersistenceException e)
        {
          //  Console.WriteLine($"WARNING! Unable to encrypt tokens at rest." +
           //     $" Saving tokens in plaintext at {Path.Combine(Config.CacheDir, Config.CacheFileName)} ! Please protect this directory or delete the file after use");
            Console.WriteLine($"Encryption exception: " + e);

            storageProperties =
                new StorageCreationPropertiesBuilder(
                    Config.CacheFileName + ".plaintext", // do not use the same file name so as not to overwrite the encypted version
                    Config.CacheDir)
                .WithUnprotectedFile()
                .Build();

            var cacheHelper = await MsalCacheHelper.CreateAsync(storageProperties).ConfigureAwait(false);
            cacheHelper.VerifyPersistence();
            return cacheHelper;
        }
    }

My expectation is , once I registercached(cacheHelper.RegisterCache(pca.UserTokenCache) in desktop app , the token should be available further to use in silent mode in both windows and web application. When I open my application in browser I am getting this error

ATest.EmailHelper.EmailHelperException: Unable to send email ---> System.AggregateException: One or more errors occurred. ---> System.InvalidOperationException: Showing a modal dialog box or form when the application is not running in UserInteractive mode is not a valid operation. Specify the ServiceNotification or DefaultDesktopOnly style to display a notification from a service application. at Microsoft.Identity.Client.Platforms.Features.WinFormsLegacyWebUi.WebUI.d__20.MoveNext() --- End of stack trace from previous location where exception was thrown --- at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw() at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) at Microsoft.Identity.Client.Internal.AuthCodeRequestComponent.d__7.MoveNext() --- End of stack trace from previous location where exception was thrown --- at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw() at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) at Microsoft.Identity.Client.Internal.AuthCodeRequestComponent.d__4.MoveNext() --- End of stack trace from previous location where exception was thrown --- at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw() at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) at Microsoft.Identity.Client.Internal.Requests.InteractiveRequest.d__11.MoveNext() --- End of stack trace from previous location where exception was thrown --- at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw() at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) at Microsoft.Identity.Client.Internal.Requests.InteractiveRequest.d__9.MoveNext() --- End of stack trace from previous location where exception was thrown --- at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw() at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) at Microsoft.Identity.Client.Internal.Requests.RequestBase.d__12.MoveNext() --- End of stack trace from previous location where exception was thrown --- at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw() at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) at Microsoft.Identity.Client.ApiConfig.Executors.PublicClientExecutor.d__2.MoveNext() --- End of stack trace from previous location where exception was thrown --- at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw() at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) at System.Runtime.CompilerServices.ConfiguredTaskAwaitable1.ConfiguredTaskAwaiter.GetResult() at ATest.EmailHelper.EmailMessage.<AccessTokenAsync>d__28.MoveNext() --- End of inner exception stack trace --- at System.Threading.Tasks.Task.ThrowIfExceptional(Boolean includeTaskCanceledExceptions) at System.Threading.Tasks.Task.Wait(Int32 millisecondsTimeout, CancellationToken cancellationToken) at System.Threading.Tasks.Task.Wait() at ATest.EmailHelper.EmailMessage.Send() --- End of inner exception stack trace --- at ATest.EmailHelper.EmailMessage.Send() at Microsoft.Scripting.Interpreter.ActionCallInstruction1.Run(InterpretedFrame frame) at Microsoft.Scripting.Interpreter.Interpreter.Run(InterpretedFrame frame) at Microsoft.Scripting.Interpreter.LightLambda.Run3[T0,T1,T2,TRet](T0 arg0, T1 arg1, T2 arg2) at System.Dynamic.UpdateDelegates.UpdateAndExecute2[T0,T1,TRet](CallSite site, T0 arg0, T1 arg1) at Microsoft.Scripting.Interpreter.DynamicInstruction3.Run(InterpretedFrame frame) at Microsoft.Scripting.Interpreter.Interpreter.Run(InterpretedFrame frame) at Microsoft.Scripting.Interpreter.LightLambda.Run2[T0,T1,TRet](T0 arg0, T1 arg1) at IronPython.Compiler.PythonScriptCode.RunWorker(CodeContext ctx) at Microsoft.Scripting.Hosting.ScriptSource.Execute(ScriptScope scope) at Spotfire.Dxp.Application.IronPython27.IronPythonScriptEngine.Execute(ScriptDefinition script, Dictionary2 scope) at Spotfire.Dxp.Application.Scripting.ScriptService.Execute(ScriptDefinition script, Dictionary`2 scope, InternalLibraryManager internalLibraryManager, NotificationService notificationService)

Thanks in advance.

-Rajesh

1 Answers1

0

As per documentation:

There are two flows, however, in which you should not attempt to silently acquire a token:

  • Client credentials flow, which does not use the user token cache but an application token cache. This method takes care of verifying the application token cache before sending a request to the security token service (STS).

  • Authorization code flow in web apps, as it redeems a code that the application obtained by signing in the user and having them consent to more scopes. Since a code and not an account is passed as a parameter, the method can't look in the cache before redeeming the code, which invokes a call to the service.

You can refer to Unable to acquire token silently or via redirect using msal-browser

Ecstasy
  • 1,866
  • 1
  • 9
  • 17