tloflin
tloflin

Reputation: 4050

Confused by Graph API authentication on ASP.Net MVC

I can't seem to figure this out. I'm confused by the whole authentication process. There's so much jargon--tokens, auth codes, identities, accounts, tenants, claims principals, scopes, client applications, OWIN, OpenID, MSAL, ADAL, Azure, AD, Office365, Graph API, etc. And it's changed several times, so odds are whatever documentation I'm looking at doesn't even apply to the current version!

I'll walk through my current problem, but I'm seeking some general help as well. For site authentication, I've got some code in my Startup class that initializes authentication for the site:

app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);

app.UseCookieAuthentication(new CookieAuthenticationOptions { CookieManager = new SystemWebCookieManager() });

string authority = string.Format(CultureInfo.InvariantCulture, aadInstance, tenant);

app.UseOpenIdConnectAuthentication(
    new OpenIdConnectAuthenticationOptions
    {
        ClientId = clientID,
        Authority = authority,

        Notifications = new OpenIdConnectAuthenticationNotifications()
        {
            // when an auth code is received...
            AuthorizationCodeReceived = OnAuthorizationCodeReceivedAsync,
            AuthenticationFailed = (context) =>
            {
                context.HandleResponse();
                return Task.FromResult(0);
            }
        }
    });

I think this handles sending the user out to be signed in. Then (I think?!) Microsoft sends an auth code back, which gets handled here:

private async Task OnAuthorizationCodeReceivedAsync(AuthorizationCodeReceivedNotification notification)
{
    var idClient = ConfidentialClientApplicationBuilder.Create(clientID)
        .WithAuthority(AzureCloudInstance.AzurePublic, tenant)
        .WithRedirectUri(notification.Request.Uri.AbsoluteUri)
        .WithClientSecret(clientSecret)
        .Build();

    var signedInUser = new ClaimsPrincipal(notification.AuthenticationTicket.Identity);
    var tokenStore = new MemoryTokenStore(idClient.UserTokenCache, HttpContext.Current, signedInUser);

    try
    {
        string[] scopes = graphScopes.Split(' ');

        var result = await idClient.AcquireTokenByAuthorizationCode(scopes, notification.Code).ExecuteAsync();

        var graphClient = new GraphServiceClient(
            new DelegateAuthenticationProvider(
                async (requestMessage) => {
                    requestMessage.Headers.Authorization = new AuthenticationHeaderValue("Bearer", result.AccessToken);
                }));

        // Grab and cache user details...
    }
    catch (Exception ex) 
    {
        // Handle exception...
    }
}

Then in my Controllers, I decorate secure actions with [Authorize]. I believe all of the above is working correctly. I don't really understand what it's doing, but I can sign in and view the secure actions. Where I'm having trouble is accessing the Graph API within the action:

public async Task<GraphServiceClient> GetAuthenticatedClient()
{
    var idClient = ConfidentialClientApplicationBuilder.Create(clientID)
        .WithAuthority(AzureCloudInstance.AzurePublic, tenant)
        .WithRedirectUri(HttpContext.Request.Url.AbsoluteUri)
        .WithClientSecret(clientSecret)
        .WithLogging(writeToLog, LogLevel.Verbose, true)
        .Build();

    var tokenStore = new MemoryTokenStore(idClient.UserTokenCache, HttpContext, ClaimsPrincipal.Current);

    var accounts = await idClient.GetAccountsAsync();

    var scopes = graphScopes.Split(' ');
    var result = await idClient.AcquireTokenSilent(scopes, accounts.FirstOrDefault()).ExecuteAsync();

    return new GraphServiceClient(
        new DelegateAuthenticationProvider(
            async (requestMessage) => {
                requestMessage.Headers.Authorization = new AuthenticationHeaderValue("Bearer", result.AccessToken);
            }
        )
    );
}

I'm doing essentially the exact same thing as in site authentication, but since I don't have an auth code I'm trying to use AcquireTokenSilent. But that requires an Identity, which from the tutorials I read said I should be getting from the ConfidentialClientApplication, as shown. But most of the time, I get zero accounts when calling GetAccountsAsync(). Once or twice I got the account and everything worked, but it's very intermittent. When I look at the debugging info, I get something like this:

Tried to use network cache provider for login.microsoftonline.com. Success? False

Tried to use known metadata provider for login.microsoftonline.com. Success? True

In the one instance it successfully retrieved the account while I was debugging, it said:

Tried to use network cache provider for login.microsoftonline.com. Success? True

Upvotes: 1

Views: 657

Answers (1)

Gary Archer
Gary Archer

Reputation: 29301

Above all else when using OAuth based tech I recommend:

  • Understanding standards based solutions
  • Being able to view OAuth messages

I have some notes on Azure AD Troubleshooting which may show you the type of approach I use - it also has some Azure / Graph setup.

If you can get to the state where you can post HTTP messages -rather than C# code - you will get the best answers.

Upvotes: 1

Related Questions