Now that you understand what application identities are and how to create and set them up in SharePoint, you can take a look at how those identities are used as part of the authentication between applications and SharePoint.
Whenever an app that is subject to external authentication needs to make an API call into SharePoint it must first confirm it has a valid and usable set of authentication tokens to do so. The two key tokens are:
The context token is passed when an application is launched. It contains information about who the calling user is and details about the SharePoint site where the application was launched. The access token is used when an application makes a call to a SharePoint API.
Several steps make up the authentication flow when these two tokens are issued and used, but there are five main occurrences that make up high-level flow when a user launches an app in SharePoint:
The full process for app authentication is slightly more complex, as shown in Figure 10-1. This detailed, step-by-step version of process is explained in the following steps:
In the case of SharePoint Online Azure Control Services (ACS), the STS is involved in creating both the context token and access tokens. In purely on-premises situations, SharePoint acts as the STS. You can find more on this topic later in this chapter in the “On-Premises App Authentication” section.
To assist with the various token-centric processes, such as validating tokens and requesting new ones from code, the default Visual Studio 2012 SharePoint application templates provide a helper class called TokenHelper. It wraps up the calls to ACS and so on to simplify the process for you.
The best way to illustrate some of the helper functions and classes that TokenHelper.cs provides is to walk through an example exercise, as follows.
public static JsonWebSecurityTokenHandler CreateJsonWebSecurityTokenHandler()
protected void Page_Load(object sender, EventArgs e) { var contextToken = TokenHelper.GetContextTokenFromRequest(Page.Request); var hostWeb = Page.Request["SPHostUrl"]; JsonWebSecurityTokenHandler tokenHandler = TokenHelper.CreateJsonWebSecurityTokenHandler(); SecurityToken securityToken = tokenHandler.ReadToken(contextToken); JsonWebSecurityToken jsonToken = securityToken as JsonWebSecurityToken; SharePointContextToken token = SharePointContextToken.Create(jsonToken); Response.Write("<b>Context Token:</b> " + contextToken); Response.Write("<b>STS:</b> " + token.SecurityTokenServiceUri); }
https://accounts.accesscontrol.windows.net/tokens/OAuth/2
SharePoint provides the ability for apps to make calls to SharePoint with access tokens that are either on behalf of a user or without user context, also known as app-only context. When an access token is used that is on behalf of a user, SharePoint treats the call as if the user is making the call. This means it is subject to the same permissions that user has. Additionally, an app can make an app-only call to SharePoint, which means no user context is passed and only the permissions that the app has been granted apply.
The TokenHelper class provides helper methods for getting each of these types of tokens. To get an access token that includes the calling user’s context, use the following method:
TokenHelper.GetAccessToken
To get an app-only access token, use the following method:
TokenHelper.GetAppOnlyAccessToken
See the “Application Authorization” section later in this chapter for more on app and user authorization and how permissions are determined by SharePoint.
As previously mentioned, when an application is launched by a user a context token is passed to it. After this has happened it is up to the application to handle the tokens and potentially store them for future use or pass between app pages. These tasks are left to the application to manage because SharePoint has no knowledge of the inner workings of the application. The developer must decide how she wants to manage these tokens when the application passes them after it is launched. Some basic options are available, including the following:
To assist with caching, the tokens provide a CacheKey and an expiry that can make caching more straightforward for the developer. The CacheKey is a property on the token that is unique to that particular token. It can be used, as the name suggests, as a primary key for that token in a cache of the developer’s choosing, such as ASP.NET application state. Additionally, the expiry time can be used in the application to flush the old tokens from the cache after they have expired.
The following exercise walks through a simple example of how to use ASP.NET application state to cache the appropriate tokens so that they can be used between page requests.
public static JsonWebSecurityTokenHandler CreateJsonWebSecurityTokenHandler()
<asp:Button ID="Button1" runat="server" Text="Process" OnClick="Button1_Click"/>
using System; using System.Collections.Generic; using System.IdentityModel.Tokens; using System.Linq; using System.Web; using System.Web.UI; using System.Web.UI.WebControls; using Microsoft.IdentityModel.S2S.Tokens; namespace SharePointTokenCacheAppWeb.Pages { public partial class Default : System.Web.UI.Page { protected void Page_Load(object sender, EventArgs e) { if (!this.IsPostBack) { var contextToken = TokenHelper.GetContextTokenFromRequest(Page.Request); var hostWeb = Page.Request["SPHostUrl"]; JsonWebSecurityTokenHandler tokenHandler = TokenHelper.CreateJsonWebSecurityTokenHandler(); SecurityToken securityToken = tokenHandler.ReadToken(contextToken); JsonWebSecurityToken jsonToken = securityToken as JsonWebSecurityToken; SharePointContextToken token = SharePointContextToken.Create(jsonToken); Application[token.CacheKey] = contextToken; Button1.CommandArgument = token.CacheKey; } } protected void Button1_Click(object sender, EventArgs e) { var contextToken = (string)Application[((Button) sender).CommandArgument]; var hostWeb = Page.Request["SPHostUrl"]; using (var clientContext = TokenHelper.GetClientContextWithContextToken(hostWeb, contextToken, Request.Url.Authority)) { clientContext.Load(clientContext.Web, web => web.Title); clientContext.ExecuteQuery(); Response.Write(clientContext.Web.Title); clientContext.ToString(); } } } }