Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

ADAL authentication without dialog box prompt

I have a console application registered in Azure AD that connects to CRM Online (configured using these steps). It queries the Web API.

The application needs to run with no user interaction... but unfortunately the call to AcquireTokenSilentAsync always fails and only AcquireTokenAsync works. This makes a user login dialog appear which fails the user interaction requirement!

Is there any way to prevent this prompt, either by saving the login somewhere on the client machine (which hasn't worked so far) or perhaps using a certificate (but how do you do this?) or something else?

I'm using the ADAL for .NET v3.10.305110106 release. The following code is used to authenticate:

private static async Task PerformOnlineAuthentication()
{
    _authInfo = new AuthInfo();  // This is just a simple class of parameters

    Console.Write("URL (include /api/data/v8.x): ");
    var url = Console.ReadLine();

    BaseUri = new Uri(url);
    var absoluteUri = BaseUri.AbsoluteUri;
    _authInfo.Resource = absoluteUri;

    Console.Write("ClientId: ");
    var clientId = Console.ReadLine();
    _authInfo.ClientId = clientId;

    Console.Write("RedirectUri: ");
    var redirectUri = Console.ReadLine();
    _authInfo.RedirectUri = new Uri(redirectUri);

    var authResourceUrl = new Uri($"{_authInfo.Resource}/api/data/");
    var authenticationParameters = await AuthenticationParameters.CreateFromResourceUrlAsync(authResourceUrl);

    _authInfo.AuthorityUrl = authenticationParameters.Authority;
    _authInfo.Resource = authenticationParameters.Resource;

    _authInfo.Context = new AuthenticationContext(_authInfo.AuthorityUrl, false);
}

private static async Task RefreshAccessToken()
{
    if (!IsCrmOnline())
        return;

    Console.WriteLine($"Acquiring token from: {_authInfo.Resource}");
    AuthenticationResult authResult;
    try
    {
        authResult = await _authInfo.Context.AcquireTokenSilentAsync(_authInfo.Resource, _authInfo.ClientId);
    }
    catch (AdalSilentTokenAcquisitionException astae)
    {
        Console.WriteLine(astae.Message);
        authResult = await _authInfo.Context.AcquireTokenAsync(_authInfo.Resource, _authInfo.ClientId, _authInfo.RedirectUri, new PlatformParameters(PromptBehavior.RefreshSession));
    }

    HttpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", authResult.AccessToken);
}
like image 493
Alex Angas Avatar asked May 23 '16 07:05

Alex Angas


1 Answers

Thanks to @aravind who pointed out the active-directory-dotnet-native-headless sample.

The sample contains a FileCache class which inherits from Microsoft.IdentityModel.Clients.ActiveDirectory.TokenCache. That class manages caching of the credentials to an encrypted file on disk. This means that there is only one prompt on first run and after that the credentials are locally stored.

The final pieces of the puzzle are:

  1. Calling a different constructor signature to initialize AuthenticationContext with the FileCache:

    _authInfo.Context = new AuthenticationContext(
        _authInfo.AuthorityUrl, false, new FileCache());
    
  2. Obtaining credentials from the user into a Microsoft.IdentityModel.Clients.ActiveDirectory.UserPasswordCredential object (see the TextualPrompt() method in the sample)

  3. Passing the credentials to a different method signature for AcquireTokenAsync():

    authResult = await _authInfo.Context.AcquireTokenAsync(
        _authInfo.Resource, _authInfo.ClientId, userCredential);
    
like image 192
Alex Angas Avatar answered Oct 07 '22 00:10

Alex Angas