Search code examples
.net-coreoauth-2.0azure-active-directorymicrosoft-graph-apimicrosoft-graph-sdks

Microsoft Graph SDK - Get drives as authed app (Not user)


Im having some problems retriving data from sharepoint (Disks) for a dotnet core app. At the moment my app tries to use the app itself, and not the logged in user to retrive disks, but the prefered way would be to use the accesstoken for the logged in user instead.

Maybe authenticating as the app with clientId and secret wont work with drives at all?

The login works fine.

I've set up a dotnet core app with the following startup:

 services.AddAuthentication(AzureADDefaults.AuthenticationScheme)
                .AddCookie(CookieAuthenticationDefaults.AuthenticationScheme, options =>
                {
                    options.ExpireTimeSpan = TimeSpan.FromDays(30);
                })
                .AddAzureAD(options => Configuration.Bind("AzureAd", options));

I also have the following services registered:

services.AddTransient<IAuthenticationProvider, GraphAuthenticationProvider>();
services.AddTransient<IGraphServiceClient, GraphServiceClient>();
services.AddTransient<IGraphProvider, MicrosoftGraphProvider>();

where i use the this to authenticate:

public class GraphAuthenticationProvider : IAuthenticationProvider
    {
        public const string GRAPH_URI = "https://graph.microsoft.com/";
        private string _tenantId { get; set; }
        private string _clientId { get; set; }
        private string _clientSecret { get; set; }

        public GraphAuthenticationProvider(IConfiguration configuration)
        {
            _tenantId = configuration.GetValue<string>("AzureAd:TenantId");
            _clientId = configuration.GetValue<string>("AzureAd:ClientId");
            _clientSecret = configuration.GetValue<string>("AzureAd:ClientSecret");
        }

        public async Task AuthenticateRequestAsync(HttpRequestMessage request)
        {
            AuthenticationContext authContext = new AuthenticationContext($"https://login.microsoftonline.com/{_tenantId}");
            ClientCredential creds = new ClientCredential(_clientId, _clientSecret);

            //I have tried using acquireTokensAsync with scopes, but there is no such method.

            AuthenticationResult authResult = await authContext.AcquireTokenAsync(GRAPH_URI, creds);
            request.Headers.Add("Authorization", "Bearer " + authResult.AccessToken);
        }
    }

I have given the app plenty of permissions in the API settings in portal, mostly because im unsure what i need, and at the moment im just eager to make it work first, then refactor some.

enter image description here

The app is able to log in, and retrive the following data with the SDK:

var groups = await _graphServiceClient.Groups[appSettings.AzureAd.GroupId].Request().GetAsync();

however: the following does not work:

var groupDrives = await _graphServiceClient.Groups[appSettings.AzureAd.GroupId].Drives
                .Request()
                .GetAsync();

and i get the following error: Code: AccessDenied Message: Either scp or roles claim need to be present in the token.

I also have user login in startup, and the app wont be used without logging in towards azure AD: Could i use the accessToken for the user instead?

services.Configure<OpenIdConnectOptions>(AzureADDefaults.OpenIdScheme, options =>
            {
                options.Authority = options.Authority + "/v2.0/";
                options.TokenValidationParameters = new TokenValidationParameters() { NameClaimType = "name" };
                options.TokenValidationParameters.ValidateIssuer = false;

                options.Events = new OpenIdConnectEvents
                {
                    OnTokenValidated = async ctx =>
                    {
                        var roleGroups = new Dictionary<string, string>();
                        Configuration.Bind("AuthorizationGroups", roleGroups);

                        var clientApp = ConfidentialClientApplicationBuilder
                            .Create(Configuration["AzureAD:ClientId"])
                            .WithTenantId(Configuration["AzureAD:TenantId"])
                            .WithClientSecret(Configuration["AzureAD:ClientSecret"])
                            .Build();

                        var authResult = await clientApp
                            .AcquireTokenOnBehalfOf(new[] { "User.Read", "Group.Read.All" }, new UserAssertion(ctx.SecurityToken.RawData))
                            .ExecuteAsync();

                        var graphClient = new GraphServiceClient(
                            "https://graph.microsoft.com/v1.0",
                            new DelegateAuthenticationProvider(async (requestMessage) =>
                            {
                                requestMessage.Headers.Authorization = new AuthenticationHeaderValue("bearer", authResult.AccessToken);
                            }));

                       
                        //Could i register the graphservice as a singelton with the users accesstoken?
                        //Fetching drives here with the accessToken from user works. 

                        var graphService = new GraphService(graphClient, Configuration);
                        var memberGroups = await graphService.CheckMemberGroupsAsync(roleGroups.Keys);
                        var claims = memberGroups.Select(groupGuid => new Claim(ClaimTypes.Role, roleGroups[groupGuid]));
                        var appIdentity = new ClaimsIdentity(claims);
                        ctx.Principal.AddIdentity(appIdentity);

                    }
                };
            });

I would actually like to use the users accesstoken to retrive the drives etc, but im not sure on how to store\reuse the accesstoken. I should probably register the service as a singelton with the users accesstoken as mentioned in the comment?

I followed this guide, and it has the same classes\services i have used: http://www.keithmsmith.com/get-started-microsoft-graph-api-calls-net-core-3/

I actually thought the option on top here was just a header. It might be easier now.. https://i.sstatic.net/aEL0Z.png


Solution

  • it feels like you are mixing up a whole bunch of concepts here. that example you are using is based on the client credentials flow. you should probably start by reading up on the different types of authentication flows available. https://learn.microsoft.com/en-us/azure/active-directory/develop/msal-authentication-flows

    1. In general when you use the client credential flow, the permissions you need to set are application permissions in the api permissions blade. Delegated permissions are for user login flows.

    2. when you are using delegated permissions like you are above. and you use a flow that gets user tokens, then the access that the application has is based on the access the user has. for example, if you delegate groups.read.all with delegated permissions, then that gives the application access to read all the groups that That specific user has access to. it doesn't give the application access to all groups. if this is what you want, then by all means use the user flow.

    You didn't mention if you were writing a web app, or what, but if you are you may want to look carefully at the on-behalf-of flow. here is an example of it. https://github.com/Azure-Samples/active-directory-aspnetcore-webapp-openidconnect-v2/tree/master/2-WebApp-graph-user/2-1-Call-MSGraph

    but again above applies for the permissions, when you get a user token your app will only have access to the items that user has access to. no more. eg user A has access to sharepoint site A, user B has no access to site A, when you use a user token for user B to call graph it will not return results for site A since user B does not have access to it.