Accessing SharePoint sites with resource specific consent (RSC) and Microsoft Graph

Accessing SharePoint sites with resource specific consent (RSC) and Microsoft Graph

Some time ago I wrote about Azure functions and how to use them from SharePoint Framework (SPFx) solutions. For instance to have a “modern elevated privileges” scenario. A big downside was that when you need to grant application-based permissions to your app, that is the backend Azure function, the app would earn permissions to access ALL site collections in your tenant.

Since last year (2020) for Microsoft Teams and since Feb-2021 for SharePoint as well, so called resource specific consent (rsc) is available.
In short you would grant “Sites.Selected” as permission level to your application registration and in a second step (before, your permission has no effect) you would “Create” permissions to each and every site collection your app needs access to.

In this post I will demonstrate a very simple scenario how this works. Here I will only cover the backend Azure function. How to consume it from SPFx I refer to the standard documentation.

Content

The app registration

The first thing to do is create an Azure app registration in the Azure AD portal. We simply need to provide it a name, create a secret, note this down and then add an Api permission:

“Sites.Selected” – resource specific consent (rsc) api permission

Three important things here:

  1. It only works with “Application permissions”
  2. Pick “Sites.Selected”
  3. Grant admin consent afterwards (as usual for application permissions)

Once this is done, as mentioned above, the real effect is nothing so far. If authenticating with that clientID and secret now all Microsoft Graph requests will receive an access denied (except those requiring no permissions at all, yes those exist as well!)

The resource specific permission

Beyond above api permission there is the need to grant permission on a specific resource. Having an id of a specific site collection it can be established with a simple Microsoft Graph post.

POST  https://graph.microsoft.com/v1.0/sites/<Your Site ID>/permissions

	{
	    "roles": [
	        "write"
	    ],
	    "grantedToIdentities": [
	        {
	            "application": {
	                "id": "<Your ClientID>",
	                "displayName": "<Your app registration name>"
	            }
	        }
	    ]
	}

This can be quickly achieved with Graph Explorer for instance but meanwhile I also found a way with PnP PowerShell.

Grant-PnPAzureADAppSitePermission -AppId <Your ClientID>
                                                             -DisplayName <Your app registration name>
                                                             -Site <Your Site Url>
                                                             -Permissions Write

Important is: This post requires “Sites.FullControl.All” permission. In Graph Explorer for instance you might need to explicitly consent this quite exclusive permission!

The Azure function (code)

Now the solution is more than half done. Simply the code is missing but that is no rocket science and quite the same than in previous application based permission samples with client-credential-flow.

An anonymous Azure function for demo purposes would simply look like this:

public static async Task<IActionResult> Run(
        [HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = null)] HttpRequest req,
        ILogger log)
    {
      string url = req.Query["url"];
      string listTitle = req.Query["listtitle"];
      string title = req.Query["title"];

      string clientID = Environment.GetEnvironmentVariable("ClientID");
      string clientSecret = Environment.GetEnvironmentVariable("ClientSecret");
      string authority = Environment.GetEnvironmentVariable("Authority");
      
      GraphController controller = new GraphController();
      controller.Initialize(clientID, authority, clientSecret);

      var response = controller.AddListItem(title, listTitle, url).Result;
      string responseMessage = String.Format("Item created with list item id {0}", response);

      return new OkObjectResult(responseMessage);
    }

The function receives three parameters: A site url, a list title and a title. The purpose is to create a simple list item in the given site/list combination and with the provided title free of your choice.

To establish the connection here clientID, secret and authority (tenant info) is received from environment variables. Please do me a favor and do not try this at home 😉 (only lazy-me and Chuck Norris are allowed to do so) Instead use more enterprise relevant scenarios such as Azure Key Vault for this …
Next a GraphController is initialized and a function to create the list item is called.
In a GraphController class that would look like this:

class GraphController
  {
    private GraphServiceClient graphClient;

    public void Initialize(string clientId, string authority, string clientSecret)
    {
      var clientApplication = ConfidentialClientApplicationBuilder.Create(clientId)
                                              .WithAuthority(authority)
                                              .WithClientSecret(clientSecret)
                                              .Build();
      List<string> scopes = new List<string>();
      scopes.Add("https://graph.microsoft.com/.default");
      string accessToken = clientApplication.AcquireTokenForClient(scopes).ExecuteAsync().Result.AccessToken;
      GraphServiceClient graphClient = new GraphServiceClient(new DelegateAuthenticationProvider(
                    async (requestMessage) =>
                    {
                      requestMessage.Headers.Authorization = new AuthenticationHeaderValue("bearer", accessToken);
                    }));
      this.graphClient = graphClient;
    }

    public async Task<string> AddListItem(string title, string listTitle, string siteUrl)
    {
      string siteId = await GetSiteIDByUrl(siteUrl);

      ListItem item = new ListItem
      {
        Fields = new FieldValueSet
        {
          AdditionalData = new Dictionary<string, object>()
          {
            {"Title", title}            
          }
        }
      };
      try
      {
        ListItem newItem = await this.graphClient.Sites[siteId].Lists[listTitle].Items.Request().AddAsync(item);
        return newItem.Id;
      }
      catch (Exception ex)
      {
        return ex.Message;
      }
      
    }
    .....
}

In the “Initialize” function a simple credential flow based on client id, secret and authority is established and the resulting token is delegated to any call-header. Afterwards the id of the site is evaluated based on the site url for further calls (omitted for brevity reasons, refer to code repository in case of special interest).
Finally the simple list item is constructed with the desired content in the Title field and last not least created with a post request on behalf of the Microsoft Graph .Net client SDK which was used here.

Try it out. This will work in any of your sites you prepped like mentioned above. In case you chose a site where the site was not given resource specific permission you can expect an access denied (403) error instead.

For trial purposes the Azure function can be simply called via browser. In local debug mode this would look like:
http://localhost:7071/api/WriteListItem?url=<Your Site url>&listtitle=<Your list title>&title=<Your desired new item title>

The Azure function call in your browser

I hope this little demo helps to understand the very valuable capability of resource specific consent (rsc) with Microsoft Graph and SharePoint resources. A big step forward in terms of flexible and comfortable solutions that do not harm substantial security concerns as there were in the past.
For your complete reference you can also find the full code in my github repository.

Update: If you want to see how to consume this Azure function from a SPFx webpart refer to my other post. You do not need to implement the specific security pattern I show you there but can also take it as a reference how to setup calling an Azure function from SPFx component.

https://mmsharepoint.wordpress.com/2021/08/20/restrict-calls-to-azure-functions-from-spfx/
Markus is a SharePoint architect and technical consultant with focus on latest technology stack in Microsoft 365 and SharePoint Online development. He loves the new SharePoint Framework as well as some backend stuff around Azure Automation or Azure Functions and also has a passion for Microsoft Graph.
He works for Avanade as an expert for Microsoft 365 Dev and is based in Munich.
Although if partially inspired by his daily work opinions are always personal.

4 thoughts on “Accessing SharePoint sites with resource specific consent (RSC) and Microsoft Graph

  1. Hi Markus, this is very helpful. Thanks for sharing.
    I just want to add that [html] Microsoft Graph PowerShell SDK [/html] can also do it as shown below:

    $application = @{ Id = <Your client id>; DisplayName = <Your app registration name> }
    
    New-MgSitePermission 
       -SiteId <The site Id>
       -Roles "Write"
       -GrantedToIdentities @{ Application = $application }
    

    It’s worth to mention that, as pointed out in the PnP blog, the Microsoft Graph PowerShell SDK also uses SiteId and not the Site URL, as the SDK mirrors the behavior of the API.

    Like

    1. Thank you for adding this Maisa! I was not aware of that and am not familiar so much with the Graph PowerShell SDK tbh. Will have a further look at this for sure.

      Like

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s