Blog Overview of users, groups and permissions in Microsoft Graph – Part 2 RSS Feed

Overview of users, groups and permissions in Microsoft Graph – Part 2

Oct 14, 2019

In part 1 of the series, we utilized the Microsoft Graph to create a user and unified group in Office 365. In this blog, we will explore the Microsoft Graph to assign permissions to users by adding them to the required Office 365 groups. To authenticate and call Microsoft Graph, we will create a console application based on .NET core to achieve the functionality in less number of steps. The application will use client secret retrieved earlier and Microsoft Authentication Library (MSAL) to establish a context for authentication with the Microsoft Graph API.

Tabe of content

  1. Prerequisites
  2. Add and validate user permissions in Office groups
  3. Create a .NET Core Console Application
  4. Extend the app for Azure AD Authentication
  5. Conclusion

Prerequisites

You will require a few prerequisites to execute the process:

Add and validate user permissions in Office groups

Once Office 365 user is successfully created, we can add the permissions needed for the users in the respective groups. The user added is also granted permissions on the associated SharePoint Online group, if any.

We can get groups and directory roles that a user is member of by using Microsoft Graph endpoint /users/{id|userPrincipalName}/memberOf. Also, we can add user to a group using endpoint /groups/{id}/members/$ref. We already have necessary permissions granted in Azure app earlier for accessing both the Graph endpoints.

Create a .NET Core Console Application

  • Create a folder named GraphNETCore in your development environment file explorer.
  • Navigate to the folder in the command prompt and run below command: dotnet new console

Navigate to the folder in the command prompt

  • Install below NuGet packages that you will use later using commands.

    • Identity.Client > dotnet add package Microsoft.Identity.Client –version 2.3.1-preview
    • Graph > dotnet add package Microsoft.Graph
    • Extensions.Configuration > dotnet add package Microsoft.Extensions.Configuration 
    • Extensions.Configuration.FileExtensions > dotnet add package Microsoft.Extensions.Configuration.FileExtensions
    • Extensions.Configuration.Json > dotnet add package Microsoft.Extensions.Configuration.Json

Install below NuGet packages

Note: We will use the same Azure web application AzureADUserManagement registered earlier while provisioning users and groups in part 1 of this blog.

Extend the app for Azure AD Authentication

To obtain a necessary OAuth access token to call Microsoft Graph, we will extend the registered application to support authentication with Azure AD. Here, we will integrate Microsoft Authentication Library into the application.

Create configuration file

  • Open the project in Visual Studio Code by running below command
    code.

Open the project in Visual Studio Code

  • Create a file having name as appsettings.json and its content as below. Drop in the values obtained earlier to add Application (Client) ID, Application (Client) Secret, Tenant (Domain) ID and application redirect URI. It is generally a best practice to define configuration i.e. Tenant (Domain) Id, Application (Client) Id, Application (Client) Secret independent of the actual code.

Create a file having name as appsettings.json

appsettings.json

{
"applicationId": " ",
"applicationSecret": " ",
"tenantId": " ",
"redirectUri": "https://localhost:8080"
}

Note: The SharePoint Online access token's default lifespan is 1 hour. The Refresh tokens are valid for up to 14 days and can be valid for up to 90 days with ongoing use. After 90 days, the users are required to re-authenticate.

Create helper classes

  • Add a new folder inside the project called Helpers.

Add a new folder called Helper

  • Create a new file AuthHandler.cs under Helper folder and add below content inside the file.

AuthHandler.cs

using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading.Tasks;
using Microsoft.Identity.Client;
using Microsoft.Graph;
using Microsoft.Extensions.Configuration;
using System.Linq;
using System.Threading;
 
namespace GraphNETCore
{
    // This class allows an implementation of IAuthenticationProvider to be inserted into the DelegatingHandler
    // pipeline of an HttpClient instance.  In future versions of GraphSDK, many cross-cutting concernts will
    // be implemented as DelegatingHandlers.  This AuthHandler will come in the box.
    public class AuthHandler : DelegatingHandler {
        private IAuthenticationProvider _authenticationProvider;
 
        public AuthHandler(IAuthenticationProvider authenticationProvider, HttpMessageHandler innerHandler) 
    {
            InnerHandler = innerHandler;
            _authenticationProvider = authenticationProvider;
    }
 
        protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) 
    {
            await _authenticationProvider.AuthenticateRequestAsync(request);
            return await base.SendAsync(request,cancellationToken);
    }
    }
}
  • Add a new file MsalAuthenticationProvider.cs in the Helpers folder and add below content inside the file.

MsalAuthenticationProvider.cs

using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading.Tasks;
using Microsoft.Identity.Client;
using Microsoft.Graph;
using Microsoft.Extensions.Configuration;
using System.Linq;
 
namespace GraphNETCore
{
    // This class encapsulates the details of getting a token from MSAL and exposes it via the 
    // IAuthenticationProvider interface so that GraphServiceClient or AuthHandler can use it.
    // A significantly enhanced version of this class will in the future be available from
    // the GraphSDK team.  It will supports all the types of Client Application as defined by MSAL.
    public class MsalAuthenticationProvider : IAuthenticationProvider
    {
        private IConfidentialClientApplication _clientApplication;
        private string[] _scopes;
 
        public MsalAuthenticationProvider(IConfidentialClientApplication clientApplication, string[] scopes) {
            _clientApplication = clientApplication;
            _scopes = scopes;
    }
 
        /// <summary>
        /// Update HttpRequestMessage with credentials
        /// </summary>
        public async Task AuthenticateRequestAsync(HttpRequestMessage request)
    {
            var token = await GetTokenAsync();
            request.Headers.Authorization = new AuthenticationHeaderValue("bearer", token);
    }
 
        /// <summary>
        /// Acquire Token 
        /// </summary>
        public async Task<string> GetTokenAsync()
        {
            AuthenticationResult authResult = null;
            authResult = await _clientApplication.AcquireTokenForClient(_scopes)
                                .ExecuteAsync();
            return authResult.AccessToken;
        }
    }
}

Extend the app for Microsoft Graph

To make calls to the Microsoft Graph, we will use the Microsoft Graph Client Library for. NET. Here we'll see how the Microsoft Graph can be incorporated into the application.

Get user information from tenant

  • Add the following statements at the top of the Program.cs file.

Program.cs

using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading.Tasks;
using Microsoft.Identity.Client;
using Microsoft.Graph;
using Microsoft.Extensions.Configuration;
  • Add static variables for GraphServiceClient and HttpClient inside Program class to make calls against Microsoft Graph.

Program.cs

private static GraphServiceClient _graphServiceClient; 
private static HttpClient _httpClient;
  • Add a new method LoadAppSettings inside Program class to fetch configuration values from appsettings.json file.

Program.cs

private static IConfigurationRoot LoadAppSettings()
{
    try
    {
        var config = new ConfigurationBuilder()
        .SetBasePath(System.IO.Directory.GetCurrentDirectory())
        .AddJsonFile("appsettings.json", false, true)
        .Build();
 
        // Validate required settings
        if (string.IsNullOrEmpty(config["applicationId"]) ||
            string.IsNullOrEmpty(config["applicationSecret"]) ||
            string.IsNullOrEmpty(config["redirectUri"]) ||
            string.IsNullOrEmpty(config["tenantId"]) ||
            string.IsNullOrEmpty(config["domain"]))
        {
            return null;
        }
 
        return config;
    }
    catch (System.IO.FileNotFoundException)
    {
        return null;
    }
}
  • To make calls against Microsoft Graph, create a new method named CreateAuthorizationProvider inside Program class and this method will be consumed later. It makes use of ConfidentialClientApplication to get configuration data.

Program.cs

private static IAuthenticationProvider CreateAuthorizationProvider(IConfigurationRoot config)
{
    var clientId = config["applicationId"];
    var clientSecret = config["applicationSecret"];
    var redirectUri = config["redirectUri"];
    var authority = $"https://login.microsoftonline.com/{config["tenantId"]}/v2.0";
 
    List<string> scopes = new List<string>();
    scopes.Add("https://graph.microsoft.com/.default");
 
    var cca = ConfidentialClientApplicationBuilder.Create(clientId)
                                            .WithAuthority(authority)
                                            .WithRedirectUri(redirectUri)
                                            .WithClientSecret(clientSecret)
                                            .Build();
    return new MsalAuthenticationProvider(cca, scopes.ToArray());
}
  • Generate a new method GetAuthenticatedGraphClient inside Program class to create an instance of GraphServiceClient from the static reference defined earlier. Utilize the returned configuration of the previous method.

Program.cs

private static GraphServiceClient GetAuthenticatedGraphClient(IConfigurationRoot config)
{
    var authenticationProvider = CreateAuthorizationProvider(config);
    _graphServiceClient = new GraphServiceClient(authenticationProvider);
    return _graphServiceClient;
}
  • Create a new method called GetAuthenticatedHTTPClient inside Program class to create an instance of HTTPClient from the static reference declared earlier. Utilize the returned configuration of the previous method.

Program.cs

private static HttpClient GetAuthenticatedHTTPClient(IConfigurationRoot config)
{
    var authenticationProvider = CreateAuthorizationProvider(config);
    _httpClient = new HttpClient(new AuthHandler(authenticationProvider, new HttpClientHandler()));
    return _httpClient;
}
  • Place the below code snippet instead of “Console.WriteLine("Hello World!");” inside the Main method.

Program.js -> Main

// Load appsettings.json
var config = LoadAppSettings();
if (null == config)
{
    Console.WriteLine("Missing or invalid appsettings.json file. Please see README.md for configuration instructions.");
    return;
}
  • Add below code continuing inside the Main method to retrieve an authenticated instance of GraphServiceClient.

Program.cs

GraphServiceClient graphClient = GetAuthenticatedGraphClient(config);

Get list of groups a user belongs to

  • Create a file named PermissionHelper.cs under Helpers folder to fetch all the groups that a user is a member of.

Create a file named PermissionHelper.cs

  • Create UserMemberOf helper method inside the file.

PermissionHelper.cs

using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.Graph;
 
namespace GraphNETCore
{
    public class PermissionHelper
    {
        private GraphServiceClient _graphClient;
 
        public PermissionHelper(GraphServiceClient graphClient)
        {
            if (null == graphClient) throw new ArgumentNullException(nameof(graphClient));
            _graphClient = graphClient;
        }
 
        //Returns a list of groups that the given user belongs to
        public async Task<List<ResultsItem>> UserMemberOf(string alias)
        {
            User user = FindByAlias(alias).Result;
            List<ResultsItem> items = new List<ResultsItem>();
 
            IUserMemberOfCollectionWithReferencesPage groupsCollection = await _graphClient.Users[user.Id].MemberOf.Request().GetAsync();
            if (groupsCollection?.Count > 0)
            {
                foreach (DirectoryObject dirObject in groupsCollection)
                {
                    if (dirObject is Group)
                    {
                        Group group = dirObject as Group;
                        items.Add(new ResultsItem
                        {
                            Display = group.DisplayName,
                            Id = group.Id
                        });
                    }
                }
            }
            return items;
        }
    }
}

Determine group and add user permissions to the group

  • Add below helper methods to add user inside unified Office 365 group “Test Azure” created earlier in part 1, while also checking if the user is not already part of that group.

PermissionHelper.cs

//Adds the user to the given group if not already a member of
public async Task AddUserToGroup(string alias, string groupId)
{
    User user = FindByAlias(alias).Result;
    List<ResultsItem> items = UserMemberOf(alias).Result;
    if (items.FindIndex(f => f.Id == groupId) >= 0)
        Console.WriteLine("User already belongs to this group");
    else
        await _graphClient.Groups[groupId].Members.References.Request().AddAsync(user);
}
 
//Returns the first unified group with the given suffix
public async Task<string> GetGroupByName(string groupNameSuffix)
{
    string groupId = string.Empty;
    var groups = await _graphClient.Groups.Request().Filter("groupTypes/any(c:c%20eq%20'unified') AND startswith(displayName,'" + groupNameSuffix + "')").Select("displayName,description,id").GetAsync();
    if (groups?.Count > 0)
    {
        groupId = (groups[0] as Group).Id;
    }
    else
    {
        groupId = CreateGroup().Result;           
    }
    return groupId;
}
 
//Returns the User object for the given alias
public async Task<User> FindByAlias(string alias)
{
    List<QueryOption> queryOptions = new List<QueryOption>
    {
        new QueryOption("$filter", $@"mailNickname eq '{alias}'")
    };
 
    var userResult = await _graphClient.Users.Request(queryOptions).GetAsync();
    if (userResult.Count != 1) throw new ApplicationException($"Unable to find a user with the alias {alias}");
    return userResult[0];
}
  • Create below helper method to create a group if it doesn’t already exist.

PermissionHelper.cs

//Creates a Unified O365 Group
public async Task<string> CreateGroup()
{
    string guid = Guid.NewGuid().ToString();
    string groupPrefix = "Test Azure -";
    Group group = await _graphClient.Groups.Request().AddAsync(new Group
    {
        GroupTypes = new List<string> { "Unified" },
        DisplayName = groupPrefix + guid.Substring(0, 11),
        Description = groupPrefix + guid,
        MailNickname = groupPrefix.Replace(" ", "").ToLower() + guid.Substring(0, 11),
        MailEnabled = false,
        SecurityEnabled = false
    });
    if (null == group)
        throw new ApplicationException($"Unable to create a unified group"); 
 
    return group.Id;
}
  • Now that the helper methods are ready, we can consume them in Program.cs class.

Program.cs

private static void PermissionHelperExampleScenario()
{
    const string alias = " ";
    ListUnifiedGroupsForUser(alias);
    string groupId = GetUnifiedGroupStartswith("Test Azure");
    AddUserToUnifiedGroup(alias, groupId);
    ListUnifiedGroupsForUser(alias);
}
 
private static void ListUnifiedGroupsForUser(string alias)
{
    var permissionHelper = new PermissionHelper(_graphServiceClient);
    List<ResultsItem> items = permissionHelper.UserMemberOf(alias).Result;
    Console.WriteLine("User is member of "+ items.Count +" group(s).");
    foreach(ResultsItem item in items)
    {
        Console.WriteLine("  Group Name: "+ item.Display);
    }
}
 
private static string GetUnifiedGroupStartswith(string groupPrefix)
{
    var permissionHelper = new PermissionHelper(_graphServiceClient);
    var groupId = permissionHelper.GetGroupByName(groupPrefix).Result;
    return groupId;
}
 
private static void AddUserToUnifiedGroup(string alias, string groupId)
{
    var permissionHelper = new PermissionHelper(_graphServiceClient);
    permissionHelper.AddUserToGroup(alias, groupId).GetAwaiter().GetResult();
}
  • PermissionHelperExampleScenario first retrieves all the unified groups a user is a member of, then it examines passed unified group and tries to add specified user inside the group.
  • Call PermissionHelperExampleScenario method inside the Main method.

Program.cs -> Main

static void Main(string[] args)
{
    // Load appsettings.json
    var config = LoadAppSettings();
    if (null == config)
    {
        Console.WriteLine("Missing or invalid appsettings.json file. Please see README.md for configuration instructions.");
        return;
    }
    GraphServiceClient graphClient = GetAuthenticatedGraphClient(config);
 
    //Executes the scenario that shows how to add user to unified group
    //Validate the user permissions to the group which also implies the associated SPO site
    PermissionHelperExampleScenario();
}

After ensuring that all files are saved, run below command in command prompt to test the console application.

dotnet build

dotnet run

Conclusion

You can use the Microsoft Graph to build intuitive app experiences based on users and their relationships with unified groups. Albeit, Microsoft Graph also provides API’s that you can make calls against to manage Office 365 groups and users’ access to those groups according to your organization’s scenario. Microsoft Graph makes it possible for developers and IT Pros to create applications and automation tools using a variety of Microsoft services without adding multiple authentication contexts or generating individual calls to services.

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *

About Tatvasoft

TatvaSoft is a CMMi Level 3 and Microsoft Gold Certified Software Development Company offering custom software development services on diverse technology platforms, like Microsoft, SharePoint, Biztalk, Java, PHP, Open Source, BI, Big Data and Mobile.

Follow Us