OAuth authentication and authorization flow for apps that ask for access permissions on the fly in SharePoint 2013 (advanced topic)

apps for SharePoint

Learn the OAuth flow for apps that request permission to access SharePoint 2013 resources on the fly, and how to use OAuthAuthorize.aspx and a redirect URI.

Note Note

This article assumes that you are familiar with the concepts and principles behind OAuth. For more information about OAuth, see OAuth.net and Web Authorization Protocol (oauth).

In some scenarios, an app can request permission to access SharePoint resources on the fly (that is, an app can request permission to access SharePoint resources dynamically during run time, instead of at app installation time). This type of app does not have to originate from SharePoint 2013. For example, it could be an app that is launched from any website or an app for Office launched from Office 2013 that wants to access resources on SharePoint 2013 on the fly.

Note Note

This type of app can only be run by users who have Manage permissions to the resources the app wants to access, because only they have sufficient rights to grant the app the permissions that it requests. For example, if an app requests only Read permission to a website, a user who has Read, but not Manage, rights to the website cannot run the app.

To be able to call into SharePoint, this type of app must first be registered through the Seller Dashboard or the appregnew.aspx page. For more information about registering apps via the Seller Dashboard or appregnew.aspx, see Guidelines for registering apps for SharePoint 2013.

After you've registered your app, it has an app identity and is associated with a security principal, referred to as an app principal. Like users and groups, an app principal has certain permissions and rights. For more information about app principals, see Guidelines for registering apps for SharePoint 2013.

You'll get a client id, client secret, app domain, and redirect URI associated with that particular app principal. This app information identifies that particular app principal. This app information is registered with the authorization server, Microsoft Azure Access Control Service (ACS).

Note Note

The redirect URI is a required field for apps that request permissions to access SharePoint resources on the fly. You also must use HTTPS.

This section describes the OAuth authentication and authorization flow for a SharePoint 2013 app requesting permissions on the fly. The sequence describes how an app that is not launched from within SharePoint 2013 can access resources in SharePoint 2013.

Note Note

To work, this OAuth flow requires SharePoint 2013 must have an Internet connection to communicate with ACS.

In SharePoint 2013, the OAuth authentication and authorization flow for an app that requests permissions to access SharePoint resources on the fly involves a series of interactions between your app, SharePoint 2013, the authorization server (which is ACS), and the end user at run time.

For an app that requests permissions to access SharePoint resources, you have a server that is separate from SharePoint 2013, and not part of the SharePoint farm. You, the app developer, own this server (let's call it the Contoso.com server). You write the code for your app and deploy the app to the Contoso.com server.

The Contoso.com server uses the SharePoint client object model (CSOM) or REST endpoints with CSOM to make calls to SharePoint 2013. The Contoso.com app uses OAuth to authenticate with SharePoint 2013. The ACS is where SharePoint requests an authorization code that it can send to the Contoso.com server. Later, the Contoso.com server uses the authorization code to request an access token from the ACS as part of the OAuth transaction process. The Contoso.com server then uses the access token to talk back to SharePoint.

OAuth flow for granting app permission to access SharePoint resources on the fly

As an example, let's say Contoso provides a photo-printing service online. A user wants to print some photos. The user wants to give consent to a Contoso photo-printing service to access and print photos from a set of photo libraries that the user keeps on an Office 365 site, fabrikam.sharepoint.com.

OAuth

As a first step before Contoso created its photo-printing app, Contoso registered its photo-printing app. So, it already has a client id, client secret, redirect URI, and other app information for the photo-printing app. The redirect URI that Contoso provided during its app registration is https://contoso.com/RedirectAccept.aspx. The client id and client secret information is included in the app web.config file. The following is an example of the app web.config file with client id and client secret information.

<?xml version="1.0"?>

<!--
  For more information on how to configure your ASP.NET application, please visit
  http://go.microsoft.com/fwlink/?LinkId=169433
  -->

<configuration>
  <appSettings>
    <add key="ClientId" value="c78d058c-7f82-44ca-a077-fba855e14d38"/>
    <add key="ClientSecret" value="SbALAKghPXTjbBiLQZP+GnbmN+vrgeCMMvptbgk7T6w="/>

  </appSettings>

</configuration>

The OAuth authentication and authorization flow for a SharePoint 2013 app requesting permissions on the fly is described in the following steps.

TipTip

These steps refer to methods in the TokenHelper.cs (or .vb) file. This managed code is not compiled, so there are no reference topics for it. However, the file itself is fully commented with descriptions of every class, member parameter, and return value. Consider having a copy of it open to refer to as you read these steps.

1

Oauth

Client accesses an app for SharePoint, and then directs it to a SharePoint site for data.

A user browses to a Contoso website. The Contoso website shows a UI element that indicates it is an app that prints photos. It also indicates that it's possible for the user to access and print photos that are kept in any Office 365 site.

The user clicks on the photo-printing UI element. The photo-printing app in this example is https://contoso.com/print/home.aspx. This app is a photo-printing app for SharePoint that implements accessing a SharePoint site by asking for app permission on the fly.

The photo-printing app asks the user to enter the URL of the location of the photos to be printed. The user enters a URL pointing to an Office 365 site on Fabrikam. The URL the user provides is https://fabrikam.sharepoint.com/.

2

Oauth

The app redirects to the SharePoint site authorization URL.

The Contoso photo-printing app doesn't have a token for this user yet, but it wants to connect to https://fabrikam.sharepoint.com/. The Contoso photo-printing app redirects the browser to a specific URL on https://fabrikam.sharepoint.com/, that is, a URL on an Office 365 site. This redirect is a HTTP 302 Redirect Response.

If you're using Microsoft .NET, Response.Redirect is one of several ways you can do the redirect from your code. Using the TokenHelper.cs (or .vb) file in your project, you can call the overloaded GetAuthorizationUrl method (using the method with three arguments). This method constructs the OAuthAuthorize.aspx redirect URL for you. Or, you can manually construct the URL.

For example, if you choose to call the GetAuthorizationUrl method to construct the OAuthAuthorize.aspx redirect URL for you, using the TokenHelper.cs (or .vb) in your project, the code is as follows:


Response.Redirect(TokenHelper.GetAuthorizationUrl(
    sharePointSiteUrl.ToString(),
    "Web.Read List.Write",
    "https://contoso.com/RedirectAccept.aspx"));

If you look at the three-parameter overload of the GetAuthorizationUrl method in TokenHelper.cs (or .vb), you see that the second parameter is a permission scope parameter, which is a space-delimited list of permissions the app requests in shorthand format. For more information about permission scopes, see App permission scope aliases and OAuthAuthorize.aspx. The third parameter must be the same redirect URI that was used when the app is registered with the AppRegNew.aspx page. You will also see that the returned string is a URL including query string parameters.

If you prefer, you can manually construct the OAuthAuthorize.aspx redirect URL. For example, the URL that the Contoso photo-printing app redirects the user to in this case is:

https://fabrikam.sharepoint.com/_layouts/15/OAuthAuthorize.aspx?client_id=client_GUID&scope=app_permissions_list&response_type=code&redirect_uri=redirect_uri

The Contoso photo-printing app sends the OAuth client Id and redirect URI to the Fabrikam Office 365 site as query string parameters. The following is an example of the GET request with sample query string values. Line breaks have been added for clarity. The actual target URL is a single line.

GET /authcode HTTP/1.1
Host: contoso.sp.com

/oauthauthorize.aspx
     ?client_id= c78d058c-7f82-44ca-a077-fba855e14d38
     &scope=list.read
     &response_type=code
     &redirect_uri= https%3A%2F%2Fcontoso%2Ecom%2Fredirectaccept.aspx

If you want a separate consent page pop-up window, you can add the query parameter IsDlg=1 to the URL construct as shown here:

/oauthauthorize.aspx?IsDlg=1&client_id= c78d058c-7f82-44ca-a077-fba855e14d38&scope=list.read&response_type=code&redirect_uri= https%3A%2F%2Fcontoso%2Ecom%2Fredirectaccept.aspx

3

Oauth

Once the user is signed in, the SharePoint site displays the consent page so the user can grant the app permissions.

If the user is not already signed into the Fabrikam Office 365 site, the user is prompted to sign in. If the user is already signed in, the Fabrikam Office 365 site renders an HTML consent page.

That is, the user is prompted to either grant or not grant the Contoso photo-printing app the permissions the app requests. In this case, the user would be granting the app read access to the user's picture library on Fabrikam.

NoteNote

After the user grants the Contoso photo-printing app the permissions the app requests, if the app caches them, the app won't have to ask the user for consent again. If the app doesn't cache, the next time the app wants to access the user's data in the Fabrikam Office 365 site, the app has to ask the user to grant permission again.

4

Oauth

The SharePoint site requests a short-lived authorization code from the ACS server.

The Fabrikam Office 365 site asks ACS to create a short-lived (approximately 5 minutes) authorization code unique to this combination of user and app.

ACS sends the authorization code to the Fabrikam Office 365 site.

5

Oauth

The SharePoint site redirects to the app’s registered redirect URI, passing the authorization code to the app server.

The Fabrikam Office 365 site redirects the browser back to Contoso via HTTP 302 Response. The URL construct for this redirection uses the redirect URI that Contoso provided during registration. It also includes the authorization code as a query string. The redirect URL in this example looks similar to the following:

https://contoso.com/RedirectAccept.aspx?code=authcode

6

Oauth

The app server uses the authorization code to request an access token from the ACS server. The ACS server validates the request, invalidates the authorization code, and then sends access and refresh tokens to the app server.

Contoso retrieves the authorization code from the request. After Contoso has the authorization code, Contoso requests an access token from ACS. Contoso also sends the authorization code to ACS along with the client Id and the client secret when making the access token request.

Using TokenHelper.cs (or .vb) in your project, the method that makes this call is GetClientContextWithAuthorizationCode. For example, in this case the code looks similar to the following:

TokenHelper.GetClientContextWithAuthorizationCode(
     "https://fabrikam.sharepoint.com/", 
     "00000003-0000-0ff1-ce00-000000000000", 
     authCode, 
     "1ee82b34-7c1b-471b-b27e-ff272accd564", 
     new Uri(Request.Url.GetLeftPart(UriPartial.Path)));

If you look at the TokenHelper.cs (or .vb) file, the second parameter of the GetClientContextWithAuthorizationCode method is the targetPrincipalName. This value is always the constant "00000003-0000-0ff1-ce00-000000000000" in an app that is accessing SharePoint.

ACS receives Contoso's request and validates the client Id, client secret, redirect URI, and authorization code. If all are valid, the ACS invalidates the authorization code (it can be used only once) and creates a refresh token and access token. The access token contains information that indicates the ISV is Contoso, the Fabrikan Office 365 user identifier, and the handle to the delegation. ACS persist the refresh token in such a way that it can re-create an access token from it.

ACS returns a refresh token and an access token to the Contoso.com server. Contoso.com can cache this access token. That way, the Contoso.com server doesn't have to ask ACS for an access token every time that it talks back to SharePoint. (Or, Contoso.com can make an access token request every time and not cache the access token.)

By default, access tokens are good for about 12 hours. Each access token is specific to the user account that is specified in the original request for authorization, and grants access only to the services that are specified in that request. Your app should store the access token securely, because it is required for all access to a user's data. For more information about access tokens, see Authorization and authentication for apps in SharePoint 2013.

NoteNote

Access tokens are not as long-lived as refresh tokens. When you get an access token, the access token also includes information as to when the access token will expire. You'll also get a refresh token. By default, refresh tokens are good for 6 months from the date the refresh token is issued. So, the same refresh token can be redeemed for a new access token from ACS for 6 months.

You can cache an access token alongside the refresh token. When the access token expires, use the same refresh token to get a new access token.

7

Oauth

The app server can now use the access token to request data from the SharePoint site. The app server can then pass that data to the client.

Contoso.com can use the access token to make a web service call or CSOM request to SharePoint, passing the OAuth access token in the HTTP Authorization header.

SharePoint returns the information that Contoso.com requested to Contoso.com.

NoteNote

For more information about OAuth tokens, see Tips and FAQ: OAuth and remote apps for SharePoint.

This section assumes you are familiar with app permissions.

Note Note

For more information about app permission, see App permissions in SharePoint 2013.

Table 1 shows the same app permission request scope URIs that are shown in App permissions in SharePoint 2013, except it has one additional column (Scope Alias) and the FullControl right is not available in the Available Rights column.

Note Note

An app that request permission to access SharePoint resources on the fly can't request full control right.

The permission request scope values in the Scope Alias column correspond to their counterparts in the Scope URI column. The difference is that the values listed in the Scope Alias column are used only by apps that request permission to access SharePoint resources on the fly).

Note Note

The scope URI values are used by apps that are launched from SharePoint 2013. The scope URI values are used in the app manifest. Apps that are launched from SharePoint 2013 request permission during app installation. For more information about app permissions, see App permissions in SharePoint 2013.

In addition, the scope alias values are used only in the context of using the OAuthAuthorize.aspx redirect page. The scope alias value is used in step 2 of the OAuth flow described in the previous section. Specifically, it's used when you call the GetAuthorizationUrl method to construct the redirect URL for you. With TokenHelper.cs (or .vb) in your project, the code is as follows:

Response.Redirect(TokenHelper.GetAuthorizationUrl(
    sharePointSiteUrl.ToString(), 
    "Web.Read List.Write", 
    "https://contoso.com/RedirectAccept.aspx"));

The scope parameter value, Web.Read List.Write, is an example of how you would request permissions using the scope alias. The scope parameter is a space-delimited permission value to request from the SharePoint site in "shorthand" format.

If you choose to construct the redirect URI yourself, the scope alias is used in the scope field in the redirect URL. For example:

https://fabrikam.sharepoint.com/_layout/15/OAuthAuthorize.aspx?client_id=c78d058c-7f82-44ca-a077-fba855e14d38&scope=list.write&response_type=code&redirect_uri=https%3A%2F%2Fcontoso%2Ecom%2Fredirectaccept.aspx

NoteNote

For a description of the scopes, see App permissions in SharePoint 2013.

Table 1. SharePoint app permission request scope URIs and their corresponding scope aliases

Scope URI

Scope Alias

Available Rights

http://sharepoint/content/sitecollection

Site

Read, Write, Manage

http://sharepoint/content/sitecollection/web

Web

Read, Write, Manage

http://sharepoint/content/sitecollection/web/list

List

Read, Write, Manage

http://sharepoint/content/tenant

AllSites

Read, Write, Manage

http://sharepoint/bcs/connection

None (currently not supported)

Read

http://sharepoint/search

Search

QueryAsUserIgnoreAppPrincipal

http://sharepoint/projectserver

ProjectAdmin

Manage

http://sharepoint/projectserver/projects

Projects

Read, Write

http://sharepoint/projectserver/projects/project

Project

Read, Write

http://sharepoint/projectserver/enterpriseresources

ProjectResources

Read, Write

http://sharepoint/projectserver/statusing

ProjectStatusing

SubmitStatus

http://sharepoint/projectserver/reporting

ProjectReporting

Read

http://sharepoint/projectserver/workflow

ProjectWorkflow

Elevate

http://sharepoint/social/tenant

AllProfiles

Read, Write, Manage

http://sharepoint/social/core

Social

Read, Write, Manage

http://sharepoint/social/microfeed

Microfeed

Read, Write, Manage

http://sharepoint/taxonomy

TermStore

Read, Write

The redirect URI is used by apps that request permission on the fly. It is the URI that SharePoint 2013 redirects the browser to after consent is granted. The following example uses ASP.NET.

From an ASPX page, an app makes a redirect call to RedirectAccept.aspx, as shown in the following sample code snippet.

Response.Redirect(TokenHelper.GetAuthorizationUrl(
    "https://fabrikam.sharepoint.com/", 
    "Web.Read List.Write", 
    "https://contoso.com/RedirectAccept.aspx")), 
    true);  


You can also use a configurable redirect URL (from a web.config file) as shown in the following sample:

<configuration>
  <appSettings>
    <add key="RedirectUri" value="https://contoso.com/RedirectAccept.aspx" />
  </appSettings>
<configuration>

The redirect URI value in the web.config file is read at runtime as shown in the following sample code snippet:

WebConfigurationManager.AppSettings.Get("RedirectUri")

Then, in the RedirectAccept.aspx.cs file, the app gets the context token by passing authorization code it got from SharePoint 2013:

public partial class RedirectAccept : System.Web.UI.Page
{
    protected void Page_Load(object sender, EventArgs e)
    {
        string authCode = Request.QueryString["code"];
        Uri rUri = new Uri("https://contoso.com/RedirectAccept.aspx");

        ClientContext context = TokenHelper.GetClientContextWithAuthorizationCode(
            "https://fabrikam.sharepoint.com/", 
            authCode, 
            rUri);

        context.Load(context.Web);
        context.ExecuteQuery();
        context.Dispose();
    }
}

The following is a code example for a default page or home page.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using Microsoft.SharePoint.Samples;
using Microsoft.SharePoint.Client;

namespace DynamicAppPermissionRequest
{
    public partial class Default : System.Web.UI.Page
    {
        protected void Page_Load(object sender, EventArgs e)
        {
            Uri sharePointSiteUrl = new Uri("https://fabrikam.sharpoint.com/print/");

            if (Request.QueryString["code"] != null)
            {
                TokenCache.UpdateCacheWithCode(Request, Response, sharePointSiteUrl);
            }

            if (!TokenCache.IsTokenInCache(Request.Cookies))
            {
                Response.Redirect(TokenHelper.GetAuthorizationUrl(sharePointSiteUrl.ToString(), 
                                                                  "Web.Write"));
            }
            else
            {
                string refreshToken = TokenCache.GetCachedRefreshToken(Request.Cookies);
                string accessToken = 
                    TokenHelper.GetAccessToken(
                           refreshToken, 
                           "00000003-0000-0ff1-ce00-000000000000", 
                           sharePointSiteUrl.Authority, 
                           TokenHelper.GetRealmFromTargetUrl(sharePointSiteUrl)).AccessToken;

                using (ClientContext context = 
                    TokenHelper.GetClientContextWithAccessToken(sharePointSiteUrl.ToString(), 
                                                                accessToken))
                {
                    context.Load(context.Web);
                    context.ExecuteQuery();

                    Response.Write("<p>" + context.Web.Title + "</p>");
                }
            }
        }
    }
}

The following is a code example for a token cache module that the previous default page or home page calls.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using Microsoft.SharePoint.Samples;

namespace DynamicAppPermissionRequest
{
    public static class TokenCache
    {
        private const string REFRESH_TOKEN_COOKIE_NAME = "RefreshToken";

        public static void UpdateCacheWithCode(HttpRequest request, 
                                               HttpResponse response, 
                                               Uri targetUri)
        {
            string refreshToken = 
                TokenHelper.GetAccessToken(
                    request.QueryString["code"], 
                    "00000003-0000-0ff1-ce00-000000000000", 
                    targetUri.Authority, 
                    TokenHelper.GetRealmFromTargetUrl(targetUri), 
                    new Uri(request.Url.GetLeftPart(UriPartial.Path)))
                   .RefreshToken;
            SetRefreshTokenCookie(response.Cookies, refreshToken);
            SetRefreshTokenCookie(request.Cookies, refreshToken);
        }

        internal static string GetCachedRefreshToken(HttpCookieCollection requestCookies)
        {
            return GetRefreshTokenFromCookie(requestCookies);
        }

        internal static bool IsTokenInCache(HttpCookieCollection requestCookies)
        {
            return requestCookies[REFRESH_TOKEN_COOKIE_NAME] != null;
        }

        private static string GetRefreshTokenFromCookie(HttpCookieCollection cookies)
        {
            if (cookies[REFRESH_TOKEN_COOKIE_NAME] != null)
            {
                return cookies[REFRESH_TOKEN_COOKIE_NAME].Value;
            }
            else
            {
                return null;
            }
        }

        private static void SetRefreshTokenCookie(HttpCookieCollection cookies, 
                                                  string refreshToken)
        {
            if (cookies[REFRESH_TOKEN_COOKIE_NAME] != null)
            {
                cookies[REFRESH_TOKEN_COOKIE_NAME].Value = refreshToken;
            }
            else
            {
                HttpCookie cookie = new HttpCookie(REFRESH_TOKEN_COOKIE_NAME, 
                                                   refreshToken);
                cookie.Expires = DateTime.Now.AddDays(30);
                cookies.Add(cookie);
            }
        }
    }
}

Show:
© 2014 Microsoft