SharePoint 2013 App Only Policy Made Easy

This post demonstrates the app only policy for SharePoint 2013 app development and show how to create SharePoint 2013 apps using OAuth tokens with either a trust to ACS as well as S2S.  I show how to request the AllowAppOnlyPolicy permission and how to execute actions that the current user is not authorized to perform directly.

Background

As developers start working with SharePoint 2013 apps, you quickly realize how permissions with apps work when performing work on behalf of a user.  I can log in as the site collection administrator, and execute an app that writes to a list and see the action fail because permission was denied.  The reason it was denied is because the app did not request write permissions to the list it was trying to write to.  Similarly, I can log in as a user who only has read permission to a list and invoke an app that has full control permission and see an attempt to write to a list fail because permission was denied.  The reason this time is that even though the app had permissions, the user did not.

This is explained in the following diagram.  The app is making a call into SharePoint, and it does so by providing the OAuth access token.  SharePoint sees that no user credentials were provided in the request and an OAuth access token is present.  When acting on behalf of a user, the token will include user information, so the context will be the App+User context.

image

This is exactly the scenario I just described.  The App+User policy is applied, and both the user and the app must have sufficient permission to write to the list.

Can I Elevate Privileges?

When presenting this material to developers, this is usually right about the time someone asks me about the RunWithElevatedPrivileges API.  It should be renamed to “RunWithScissors”, because so many developers use it but don’t quite understand what the API is doing, and it is this confusion that usually generates support calls on how to properly use the API in a predictable manner.  The new app model does away with this impersonation confusion and makes things quite a bit more straightforward because there is no impersonation capability in the API.

And when I say this to developers looking at apps for the first time, they usually want to throw things at me.  How the heck am I supposed to get things done?!?  Give users permissions to everything?!?  

Let’s analyze the requirement instead of focusing on how we arrive at the solution.  We need to perform an action that the user is not entitled to.  OK, now that we agree on semantics, how, then, do I use the new app model to perform actions when the user doesn’t have permission?  In the scenario presented earlier, how can I create an app that has privileges to do something such as writing to a list even though the current user does not have permissions to write to the list?  The answer is the App Only Policy. 

Don’t think of this as impersonation or elevation of the current user’s privileges, it’s not.  In fact, we are not going to use the permissions of the current user at all.

The App Only Policy

In the previous diagram, we highlighted a flow where an app is calling into SharePoint using the CSOM.  Our new scenario of an app performing work that the user does not have permission to means we will use the app only policy.  Following the flow diagram from the Start node, there is no user credential provided, an OAuth access token is present, and this time the token does not contain user information.  In this case, we are solely evaluating permissions based on the permission of the app and not on the user. 

Doing this is actually pretty straightforward.  The first step is to request permission to use the app only policy in your app manifest by adding the AllowAppOnlyPolicy attribute to the AppPermissionRequests node with a value of true.

image

When your app requests this permission, a subtle change is reflected in the trust dialog.  You now see the text “Let it share its permissions with other users.”. 

image

This text is letting you know that the app is requesting permission to perform actions that the user may not have permission to.  In this dialog, the app will be granted Write permission (which means read, create, edit, or delete items) in the Announcements list and will be able to do so whether the user has permission or not.

This capability is only available to provider-hosted and Azure auto-hosted apps.  It is not available to SharePoint-hosted apps.  In a SharePoint-hosted app, there is necessarily always an app+user context. 

App Only Context with S2S

Creating a provider-hosted app that leverages only policy using S2S is actually very easy.  The first step is to make sure that you add the AllowAppOnlyPolicy attribute in the app manifest.  The next step is to use the TokenHelper::GetS2SAccessTokenWithWindowsIdentity method, passing a null for the WindowsIdentity parameter.

 string appOnlyAccessToken = 
   TokenHelper.GetS2SAccessTokenWithWindowsIdentity(_hostWeb, null);

If the app manifest has the AllowAppOnlyPolicy attribute set to true, this call will succeed and return a valid OAuth access token that does not contain user information.  Once you have the access token, then you can use the TokenHelper::GetClientContextWithAccessToken method to obtain a CSOM client context.

 using (ClientContext clientContext = 
    TokenHelper.GetClientContextWithAccessToken(_hostWeb.ToString(), appOnlyAccessToken))
{
    List list = clientContext.Web.Lists.GetByTitle("Announcements");
    ListItemCreationInformation info = new ListItemCreationInformation();
    Microsoft.SharePoint.Client.ListItem item = list.AddItem(info);
    item["Title"] = "Created from CSOM";
    item["Body"] = "Created from CSOM " + DateTime.Now.ToLongTimeString();

    item.Update();
    clientContext.Load(item);
    clientContext.ExecuteQuery();
}

To test this, deploy the app and then log on as a user that only has read permission to the list.  Execute the code, and a new item is created even though the user does not have permission to create items in the list.  The Created By and Modified By fields in the list will reflect that it was only the app’s permissions that were used to create the item.

image

If we had included a user identity (as the code generated by Visual Studio does by default), then the created by and modified by fields would look a little different, showing that code was executed on behalf of an individual.

image

 

App Only Context with ACS

Creating an app-only context with a provider-hosted app using a trust to ACS is just slightly more involved.  A trust to ACS is automatically established when you create your O365 tenant.  If you are self-hosting your SharePoint farm, then a trust to ACS can be established between your SharePoint farm and ACS.

We use a similar approach as before, but this time we need to extract the context token string and validate the token in order to retrieve information such as the authentication realm.  Provided this information, we can now request an app-only token using TokenHelper::GetAppOnlyAccessToken.  Once we have the access token, we can now obtain the client context using TokenHelper::GetClientContextWithAccessToken.

 string _hostWeb = new Uri(Request.QueryString["SPHostUrl"]);
string _contextTokenString = TokenHelper.GetContextTokenFromRequest(Page.Request);

//Get context token.
SharePointContextToken contextToken =
    TokenHelper.ReadAndValidateContextToken_contextTokenString, Request.Url.Authority);

//Get app only access token.
string appOnlyAccessToken = 
    TokenHelper.GetAppOnlyAccessToken(contextToken.TargetPrincipalName, 
            _hostWeb.Authority, contextToken.Realm).AccessToken;

Response.Write("<h2>Valid app-only access token retrieved</h2>");
Response.Write("<p>" + appOnlyAccessToken + "</p>");
Response.Flush();

using (ClientContext clientContext = 
    TokenHelper.GetClientContextWithAccessToken(_hostWeb.ToString(), appOnlyAccessToken))
{
    List list = clientContext.Web.Lists.GetByTitle("Announcements");
    ListItemCreationInformation info = new ListItemCreationInformation();
    Microsoft.SharePoint.Client.ListItem item = list.AddItem(info);
    item["Title"] = "Created from CSOM";
    item["Body"] = "Created from CSOM " + DateTime.Now.ToLongTimeString();

    item.Update();
    clientContext.Load(item);
    clientContext.ExecuteQuery();
}

The result is the same as before… a user that does not have write permission to a list is able to execute the app, which has been granted the AllowAppOnlyPolicy in the permission requests in the app manifest, and the write to list operation now succeeds.

 

For More Information

App authorization policy types in SharePoint 2013

SharePoint Low-Trust Apps for On-Premises Deployments