Inside SharePoint 2013 OAuth Context Tokens
This post will show you how to inspect the SharePoint 2013 context token to better understand how OAuth is used in SharePoint 2013 apps.
First, Some Context
In order to use a context token with SharePoint 2013 apps, you will need to create a provider-hosted app that uses a client ID and a client secret. This requires that the target SharePoint farm has a trust configured to Azure Access Control Services, or ACS. Office365 automatically configures this trust for you, so if you create your app using Office365 then this works just fine. If you create your app using your own SharePoint farm, then you will need to configure low-trust apps for on-premises deployments in order to establish a trust with Azure ACS for your SharePoint farm.
When you use a client ID and a client secret to build your app, you are using Azure ACS as the authentication server. The OAuth flow looks like this:
This is what I jokingly refer to as “the scary slide”. It’s not really that scary once you understand it.
- User makes a request to SharePoint
- SharePoint requests the context token from Azure ACS
- The signed context token is returned
- The HTML markup for the SharePoint page is sent to the client
- If the SharePoint page contents contain a client app part, then a request is sent to the remote web server for the IFrame’s contents. If the user simply clicked on the app to launch it, then a page in SharePoint called appredirect.aspx is used to redirect to the app’s start URL. In either case, a context token is sent along with that page request in a form parameter called SPAppToken.
- The context token is a JWT token. The JWT token is a set of base64 encoded claims. One claim is named “refreshtoken” that has a base64 encoded value. One of the claims is “appctx” which contains a child object, one of the properties is SecurityTokenServiceUri with a value https://accounts.accesscontrol.windows.net/tokens/OAuth/2. Your app uses the TokenHelper.cs class to extract the refreshtoken and then send it to the SecurityTokenServiceUri to request an access token.
- The access token is retrieved from ACS.
- The access token is passed to the SharePoint site in the HTTP header Authorization that has a value beginning with “Bearer” and has your access token. This token is passed in the HTTP header to the CSOM endpoint _vti_bin/client.svc/ProcessQuery when you use the CSOM.
- If the app is authorized, SharePoint returns the data requested.
- Finally, the app server returns the requested HTML markup as a response.
The really important thing to understand is what’s passed to your app, the context token. The context token contains a few key pieces of information necessary for the rest of the plumbing to function.
What’s In Your Wallet?
Now that we’ve walked through the steps of the OAuth dance, let’s take a look at what’s in that context token that is critical to the whole process. When you use Visual Studio to create a provider-hosted app, the boiler plate code looks like this:
protected void Page_Load(object sender, EventArgs e)
{
// The following code gets the client context and Title property by using TokenHelper.
// To access other properties, you may need to request permissions on the host web.
var contextToken = TokenHelper.GetContextTokenFromRequest(Page.Request);
Response.Write(contextToken + "<br/>");
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);
}
}
Reading that code, you can see that the context token is passed to your app, but it is a base64 encoded string that is unintelligible. I added a Response.Write statement to write the context token to the page, but you can also see it in Fiddler. In the following screen shot, I highlighted the SPAppToken HTTP form variable where the context token is passed.
Once you have that context token, you have the base64 encoded value of the context token, which is a JWT token. The JWT token format is pretty straightforward, and the TokenHelper.cs class in Visual Studio simply parses that JWT token for you. However, for the curious who want to see what it looks like, you can use a tool like https://openidtest.uninett.no/jwt to inspect the JWT token. Copy the context token string into the “Encoded JWT” text box and the client secret into the “Secret” text box, and then press decode.
What is returned is the following JSON object, which is a JWT token that contains a set of claims.
{
"aud": "4c2df2aa-3d14-4d84-8a79-5a75135e98d0/localhost:44346@d341a536-1d82-4267-87e6-e2dfff4fa325",
"iss": "00000001-0000-0000-c000-000000000000@d341a536-1d82-4267-87e6-e2dfff4fa325",
"nbf": 1365177964,
"exp": 1365221164,
"appctxsender": "00000003-0000-0ff1-ce00-000000000000@d341a536-1d82-4267-87e6-e2dfff4fa325",
"appctx": "{\"CacheKey\":\"em1/saZohTOS4nOUZHXMb8QJgyNbkEO86TSe5j9WYmo=\",
\"SecurityTokenServiceUri\":\"https://accounts.accesscontrol.windows.net/tokens/OAuth/2\"}",
"refreshtoken": "IAAAANc8bAVMWZceOsdfgsdfggbfm7oU_aM7D2qofUpQstMsdfgsdfgfYS0OtbZ-
eY9UQGvlYSl5kpPi913G1AwIVBMxoCux8-bhcCCiaGVo-vuFzrXetdhRGPftQdHh-
1rS5cvDuuQ_bw_mjySIyuHNGSavEs8HUgHY9BOVc3pTGZtZ_nS-
1NbDLYObjnznasdfasdfasdfQreLAeeOpVRY1PGsdfgsdfgOITA3BKhjJFz_40YJMubdHmY2OTS
nqwNnUe-rBBCtfvKt4xFWvdRzTzwfW",
"isbrowserhostedapp": "true"
}
You can now see that the context token contains the refresh token as a base64 encoded value.
What are the claims in the context token?
The following shows the properties for the context token.
aud | Short for “audience”, means the principal the token is intended for. The format is <client ID>/<target URL authority>@<target realm>. Based on this information, you can determine the client ID for your app and the realm. In an on-premise environment, there is typically just one realm, and its identifier matches your farm ID. For Office 365, this is your tenant ID. | ||||||||||
iss | Short for “issuer”, this is the principal that issued the token, in the form of <principal ID>@<realm> . The principal ID value 00000001-0000-0000-c000-000000000000 is ACS. | ||||||||||
nbf | Short for “not before”, this is the number of seconds after January 1, 1970 (part of the JWT specification) that the token starts being valid. | ||||||||||
exp | Short for “expires”, represents the number of seconds after January 1, 1970 that the token stops being valid. | ||||||||||
appctxsender | The sender of the token in the form <sender ID>@<realm>. The value 00000003-0000-0ff1-ce00-000000000000 is the identifier for SharePoint. For trivia:
|
||||||||||
appctx | Contains two properties, CacheKey and SecurityTokenServiceUri. CacheKey: UserNameId + "," + UserNameIdIssuer + "," + ApplicationId + "," + Realm This is provided so that you can cache the value in a cookie or in session to identify that the user has already authenticated. SecurityTokenServiceUri: The URL for Azure ACS where the token is to be validated. The URL is https://accounts.accesscontrol.windows.net/tokens/OAuth/2. | ||||||||||
refreshtoken | The contents of the refresh token that are sent to Azure ACS. | ||||||||||
isbrowserhostedapp | Indicates if the request initiated from a user interacting with the browser and not an app event receiver |
During Ignite training, I tell the attendees that the most important thing you can understand when building apps for SharePoint is OAuth. You need to understand the OAuth dance, the reason for the context token, and it is invaluable to understand what the TokenHelper.cs is doing for you when it does things like GetClientContextFromContextToken. This post goes into some of the details on why this is such an important piece to understand when troubleshooting apps. You can also see why it is mandatory for apps to SSL due to the nature of information being sent in HTTP.
You can find more information about the context token, calculating nbf and exp claim values, and links to additional articles at https://msdn.microsoft.com/en-us/library/office/apps/fp179932.aspx.
For More Information
configure low-trust apps for on-premises deployments
Tips and FAQs: OAuth and remote apps for SharePoint 2013
Comments
Anonymous
April 05, 2013
Good Post.Anonymous
April 11, 2013
Nice Post. I did a blog post recently explaining how we can use just HTTP requests to get the accesstoken, which explains some of the internal flow of TokenHelper.Here is the post : jomit.blogspot.com/.../authentication-and-authorization-with.htmlAnonymous
October 14, 2014
I used the VS model that mounts to interact with the app, but when the user gets some minutes without interacting with the buttons, the context is null and the error page. The user is then required to start the app again. Know what can be?Anonymous
December 07, 2014
The comment has been removedAnonymous
January 28, 2015
This all worked fine in development but when I published, my web site no longer receives the context token. What setting did I miss?Anonymous
January 29, 2015
I had to use Fiddler to figure out what was going wrong. I had to adjust the settings in my client ID setup in the seller dashboard to match what was expected as far as the domain and URL.Anonymous
March 07, 2015
The question is why? Why is Microsoft making it so hard to communicate with SharePoint server. JSOM?CSOM has FormDigest. why can't you just make the same set of calls? To make it worse they never explained in their documentation or sample what is going on behind the scene like you so clearly explained. Thanks for sharing.Anonymous
March 08, 2015
@Kenny - it's not at all harder, it's actually quite easier than it was in the SOAP web service days as the tooling hides all of this complexity while OAuth opens a ton of new scenarios that weren't possible previously. As for the FormDigest... that assumes that the CSOM call is rendered in the same page as SharePoint. If that is the case, then none of this applies as that is not an app, that's just JavaScript in a page, and you don't need any OAuth for that. This scenario is for when your server-side code is running on a completely different server, domain, and possibly even platform than SharePoint. I can now easily write apps using Java, Node.js, and PHP that communicate with SharePoint... this was difficult using SOAP.Anonymous
April 24, 2015
Hi Kirk,Nice post! BTW, I'm having the same issue as Dave.. When I'm running the webapp locally everything is right, but when I upload it to Azure, SPAppToken is null and it can't authenticate...Any thoughts on this?Many thanks.Anonymous
April 25, 2015
@fretwio and @Dave -Assuming you are using AppRegNew.aspx, the domain and callback URL are required when registering the app. If you use them locally, then you would have registered http://localhost. When publishing to another site (such as Azure Web App) you have to register a new entry with the new domain and callback URL. These are not editable when using AppRegNew.aspx.If you are using the store registration, same thing... you need to update the domain and callback URL.Anonymous
June 16, 2015
I am a little baffled about that. Every decent developer must be switching back and forth between testing locally and deploying remotely constantly. Is the intention that they should edit their Web.config to change the client ID each time such a switch is made, or is there a less dumb way to go about it?Anonymous
September 15, 2015
I'm trying to get the access_token, but i got this error {"error":"invalid_request","error_description":"ACS90004: The request contains 1 tokens separated by u0027=u0027 instead of a single key value pair.rnTrace ID: 1772198e-ddb6-4dbc-82cf-d0a487b887adrnCorrelation ID: 9a2b81d4-13ce-4a9d-9 bff-8cb7ae41e447rnTimestamp: 2015-09-15 16:57:50Z","error_codes":[90004],"time stamp":"2015-09-15 16:57:50Z","trace_id":"1772198e-ddb6-4dbc-82cf-d0a487b887ad", "correlation_id":"9a2b81d4-13ce-4a9d-9bff-8cb7ae41e447"} Any guess? Thanks!Anonymous
September 15, 2015
@Mauricio - Sorry, no clue. I would suggest either creating a support ticket or posting your question to a community forum such as stackoverflow.com/.../sharepoint.Anonymous
September 17, 2015
@kirk I've already found the error, i guess it was because i was using only ' instead of ", now i got a new error "{"error":"invalid_request","error_description":"ACS90019: Unable to determine the tenant identifier from the request.\r\nTrace ID: 5c538fef- b2f4-458f-b245-d8ee008ea68c\r\nCorrelation ID: 52aa08b0-3bc6-4850-85ef-c1d7d3f3fc91\r\nTimestamp: 2015-09-17 17:25:10Z","error_codes":[90019], "timestamp":"2015-09-17 17:25:10Z","trace_id":"5c538fef-b2f4-458f-b245-d8ee008ea68c","correlation_id":"52aa08b0-3bc6-4850-85ef-c1d7d3f3fc91 "}" I'm trying to read this link, but 404 msdn.microsoft.com/.../fp179932.aspx Thanks @KirkAnonymous
September 17, 2015
@kirk i found a solution, here is the gist gist.github.com/.../ce4c4af9eb845735a825Anonymous
November 12, 2015
Is there any way to get information about the user from the token? I have a provider hosted app that is not accessing any Sharepoint resources. The hope was to launch through Sharepoint and accpet authentication provided by Sharepoint but provide my own authorizationAnonymous
November 12, 2015
Sorry, I should have provided more information. The provider hosted app is written in Java. So, I do not have access to the TokenHelper or some of the other convenient .NET classes.Anonymous
November 12, 2015
The comment has been removed