8

I have the following code (copied from Microsoft Learn), that was working fine with Microsoft.Graph 4.54.0

var authProvider = new DelegateAuthenticationProvider(async (request) => {
                // Use Microsoft.Identity.Client to retrieve token
                var assertion = new UserAssertion(token.AccessToken);
                var result = await clientApplication.AcquireTokenOnBehalfOf(scopes, assertion).ExecuteAsync();

                request.Headers.Authorization =
                    new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", result.AccessToken);
            });

I then created a new project using Microsoft.Graph 5.0.0 and Microsoft.Graph.Core 3.0.0, this gives an error that DelegateAuthenticationProvider could not be found.

How do I create an AuthenticationProvider that I can use with the GraphServiceClient?

Tiny Wang
  • 10,423
  • 1
  • 11
  • 29
Shiraz Bhaiji
  • 64,065
  • 34
  • 143
  • 252

5 Answers5

5

I haven't figured your issue out because I never used your code before, I just followed this official document to create GraphServiceClient via on-behalf-of flow. I will try to continue. Here's what I already got and it worked.

using Azure.Core;
using Azure.Identity;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Primitives;
using Microsoft.Graph;

namespace WebMvcGraph5Ofo.Controllers
{
    [Route("api/[controller]")]
    [ApiController]
    public class HelloController : ControllerBase
    {
        public async Task<string> Get() {
            StringValues authorizationToken;
            HttpContext.Request.Headers.TryGetValue("Authorization", out authorizationToken);
            var token = authorizationToken.ToString().Replace("Bearer ","");
            var scopes = new[] { "User.Read.All" };
            var tenantId = "tenantId";
            var clientId = "client_id";
            var clientSecret = "client_secret";
            var onBehalfOfCredential = new OnBehalfOfCredential(tenantId, clientId, clientSecret, token);
            var tokenRequestContext = new TokenRequestContext(scopes);
            var token2 = onBehalfOfCredential.GetTokenAsync(tokenRequestContext, new CancellationToken()).Result.Token;
            var graphClient = new GraphServiceClient(onBehalfOfCredential, scopes);
            var user = await graphClient.Users.GetAsync();
            return "hello";
        }

        [Route("ClientCredentialFlow")]
        public async Task<string> clientAsync() {
            var scopes = new[] { "https://graph.microsoft.com/.default" };
            var tenantId = "tenantId";
            var clientId = "client_id";
            var clientSecret = "client_secret";
            var clientSecretCredential = new ClientSecretCredential(
                            tenantId, clientId, clientSecret);
            var graphClient = new GraphServiceClient(clientSecretCredential, scopes);
            var users = await graphClient.Users.GetAsync();
            return "world";
        }
        
        [Route("provider")]
        public async Task<string> providerAsync()
        {
            StringValues authorizationToken;
            HttpContext.Request.Headers.TryGetValue("Authorization", out authorizationToken);
            string incomingToken = authorizationToken.ToString().Replace("Bearer ", "");
            TokenProvider provider = new TokenProvider();
            provider.token = incomingToken;
            var authenticationProvider = new BaseBearerTokenAuthenticationProvider(provider);
            var graphServiceClient = new GraphServiceClient(authenticationProvider);
            var user = await graphServiceClient.Users.GetAsync();
            return "!!";
        }
    }
    
    public class TokenProvider : IAccessTokenProvider
    {
        public string token { get; set; }
        public AllowedHostsValidator AllowedHostsValidator => throw new NotImplementedException();

        public Task<string> GetAuthorizationTokenAsync(Uri uri, Dictionary<string, object>? additionalAuthenticationContext = null, CancellationToken cancellationToken = default)
        {
            return Task.FromResult(token);
        }
    }
}

enter image description here

=========================================================

Just like what @user2250152 shared,

In place of the DelegateAuthenticationProvider, custom authentication flows can be done creating an implementation of IAccessTokenProvider, and using with the BaseBearerTokenAuthenticationProvider from the Kiota abstractions as follows

we can't use DelegateAuthenticationProvider any more to use it directly to new GraphServiceClient(delegateAuthenticationProvider), if we want to generate the auth_provider to new GraphServiceClient, we have to follow to use BaseBearerTokenAuthenticationProvider + IAccessTokenProvider. I had a test like below, it can work but seems not meet the requirement for on-behalf-of flow.

Just like you know, the whole flow for O-B-O should be, using an access token A to call the web API which is protected by AAD, then the API code use O-B-O flow to generate a new access token B to do other request, such as calling graph API.

Then the scope or role for token A should look like api://xxxx/scope_name(.default for role), and the scope for token B should be graph API scope such as User.Read.All.

In my test, I used graph client so that I don't need to generate token B, but it still need to authenticate the graph client. I found that when I pass token A with api://xxxx/scope_name to auth the graph client, I will get error, but when the token A is generated by graph API scope, I will successfully auth the graph client.

enter image description here

Tiny Wang
  • 10,423
  • 1
  • 11
  • 29
4

For Microsoft.Graph v5.0.0 the GraphServiceClient constructor accepts instances of TokenCredential from Azure.Identity package.

There is a lot of classes derived from TokenCredential like InteractiveBrowserCredential or ClientSecretCredential

Example for InteractiveBrowserCredential

var interactiveBrowserCredential = new InteractiveBrowserCredential(interactiveBrowserCredentialOptions);
var graphServiceClient = new GraphServiceClient(interactiveBrowserCredential);

Not sure but probably OnBehalfOfCredential class is similar to Microsoft.Identity UserAssertion

Resource:

Upgrade to v5: authentication

user2250152
  • 14,658
  • 4
  • 33
  • 57
3

Also had the same problem, I resolved it according to the documentation: https://github.com/microsoftgraph/msgraph-sdk-dotnet/blob/feature/5.0/docs/upgrade-to-v5.md#authentication

This was my solution:

public class TokenProvider : IAccessTokenProvider
{
    public async Task<string> GetAuthorizationTokenAsync(Uri uri, Dictionary<string, object> additionalAuthenticationContext = default,
    CancellationToken cancellationToken = default)
    {
        //Tried to adjust this part to your code.
        // Use Microsoft.Identity.Client to retrieve token
        var assertion = new UserAssertion(token.AccessToken);
        var result = await clientApplication.AcquireTokenOnBehalfOf(scopes, assertion).ExecuteAsync();

        return result.AccessToken;
    }

    public AllowedHostsValidator AllowedHostsValidator { get; }
}

And the use:

var authenticationProvider = new BaseBearerTokenAuthenticationProvider(new TokenProvider(configuration));
graphServiceClient = new GraphServiceClient(authenticationProvider);

Hope it helps.

Moran Barzilay
  • 133
  • 2
  • 13
2

The Interactive provider authentication provider works for a desktop application as I think DelegateAuthenticationProvider used to - it pops up a sign-in dialog. There is sample code here: https://learn.microsoft.com/en-us/graph/sdks/choose-authentication-providers?tabs=CS#interactive-provider. All the other samples I found on the web use device token authentication, which is unfriendly. The code below (adapted from the sample) works for me:

// User auth token credential
    private static InteractiveBrowserCredential? interactiveCredential;
    // Client configured with user authentication
    private static GraphServiceClient? _userClient;
    private static string[] scopes = new[] { "User.Read" };

     public static void InitializeGraphForUserAuth()
     {         

        var tenantId = "common";

        var clientId = "[Your client ID]";

        var options = new InteractiveBrowserCredentialOptions
        {
            TenantId = tenantId,
            ClientId = clientId,
            AuthorityHost = AzureAuthorityHosts.AzurePublicCloud,

            // RedirectUri must be http://localhost or http://localhost:PORT,
// and must be added to the RedirectUris in the Azure app registration

            RedirectUri = new Uri("http://localhost"),
        };


        interactiveCredential = new InteractiveBrowserCredential(options);

        _userClient = new GraphServiceClient(interactiveCredential, scopes);
    }`

If you put this into the sample application at https://learn.microsoft.com/en-us/graph/tutorials/dotnet?tabs=aad in place of the InitializeGraphForUserAuth function there (which uses device code authentication) and make conforming changes you can get a token and call the API.

user3673052
  • 147
  • 1
  • 7
1

i had same problem for authenticating Behalf of a user.
you can use an Authentication Provider class

using Microsoft.Kiota.Abstractions;
using Microsoft.Kiota.Abstractions.Authentication;
using Newtonsoft.Json;

namespace GraphHelper;

public class TokenProvider : IAuthenticationProvider
{
    private async Task<string> GenerateToken()
    {
        using (HttpClient httpClient = new HttpClient())
        {
            var tokenUrl = "https://login.microsoftonline.com/[YOUR_TENANT_ID]/oauth2/token";
            var request = new HttpRequestMessage(HttpMethod.Post, tokenUrl);

            request.Headers.Add("Referer", "login.microsoftonline.com");
            request.Headers.Add("Accept", "application/x-www-form-urlencoded");
            request.Headers.Add("CacheControl", "no-cache");

            var AuthenticationContext = new Dictionary<string, string>
                {
                { "grant_type", "password" },
                    { "client_id", "[YOUR_CLIENT_ID]" },
                    { "client_secret", "[YOUR_CLIENT_SECRET]" },
                    { "resource", "[YOUR_RESOURCE]" },
                    { "username", "[YOUR_USERNAME]" },
                    { "password", "[YOUR_PASSWORD]" }
                };

            request.Content = new FormUrlEncodedContent(AuthenticationContext);

            var response = await httpClient.SendAsync(request);
            if (response.IsSuccessStatusCode)
            {
                var content = await response.Content.ReadAsStringAsync();
                var oAuthResponse = JsonConvert.DeserializeObject<OAuthResponse>(content);
                return oAuthResponse.access_token;
            }

            throw new Exception("Authentication problem" + response.ReasonPhrase);

        }
    }

    public async Task AuthenticateRequestAsync(RequestInformation request, Dictionary<string, object>? additionalAuthenticationContext = null, CancellationToken cancellationToken = default)
    {
        var token = await GenerateToken();
        request.Headers.Add("Authorization", $"bearer {token}");
    }

    
}

Just create an instance of this class and pass it as paramter to GraphServiceClient constructor

   var tokenProvider = new TokenProvider();
            return new GraphServiceClient(tokenProvider);

by each request to Graph , AuthenticateRequestAsync will be executed and a new token will be generated. you can manage Generating token when it really needed ( when you don't have a token or when it has been expired)