I am developing SharePoint 2013 Provider hosted app using javascript REST Api. In order to perform create (POST), or update (MERGE) operations on sharepoint items I need to set
I realize you've already answered your own question within the context of a provider-hosted app, but for developers like myself who need to access the REST API from a language not based in the .NET framework, (and who cannot write their project as a web app) I'd like to expand on the subject a bit more. I was tasked with writing an iPad app recently that required this functionality, and ended up reverse-engineering the following:
Not going to actually cover this, as there are plenty of examples online that demonstrate the more common methods. The Microsoft.SharePoint.Client
libraries mostly seem to use claims-based authentication when working with SharePoint Online, with the token being requested through the endpoint found at: https://login.microsoftonline.com/RST2.srf
If you're feeling lazy, you can always take your authenticated cookies, make a GET request to the homepage of the target web, and use a regular expression like:
/(]*?)name="?__REQUESTDIGEST"?(?:[^>]*?)\/>)/i
to scrape the HTML from the response. From there, it'd just be a matter of extracting the value
attribute for your digest.
The CSOM libraries currently use a SOAP endpoint when acquiring the request digest it uses for its API calls. You can do the same by making a SOAP request to the $(SPWebUrl)/_vti_bin/sites.asmx
web service similar to the following:
POST $(SPWebUrl)/_vti_bin/sites.asmx HTTP/1.1
Content-Type: text/xml
SOAPAction: http://schemas.microsoft.com/sharepoint/soap/GetUpdatedFormDigestInformation
X-RequestForceAuthentication: true
Host: $(SPSiteHostname)
Expect: 100-continue
Accept-Encoding: gzip, deflate
Cookie: $(Authenticated Cookies - Either "FedAuth=...; rtFa=..." or "SPOIDCRL=...")
Content-Length: $(Whatever)
When executed successfully, the response body will look something like:
0x1122334455 ... FF,27 Jul 2015 03:06:54 -0000
1800
$(SPWebUrl)
16.0.3208.1222
14.0.0.0,15.0.0.0
At that point, you can just extract your request digest from the DigestValue
block.
The last approach I'm aware of uses an OData request made to the $(SPWebUrl)/_api/contextinfo
endpoint:
POST $(SPWebUrl)/_api/contextinfo HTTP/1.1
Host: $(SPSiteHostname)
DataServiceVersion: 3.0
Accept: application/json; odata=nometadata
Content-Type: application/json; odata=verbose
Cookie: $(Authenticated Cookies)
Content-Length: 2
{}
When executed successfully, the response body will look like the following:
{
"FormDigestTimeoutSeconds" : 1800,
"FormDigestValue" : "0x1122334455 ... FF,27 Jul 2015 03:06:54 -0000",
"LibraryVersion" : "16.0.4230.1217",
"SiteFullUrl" : "$(SPSiteUrl)",
"SupportedSchemaVersions" : ["14.0.0.0", "15.0.0.0"],
"WebFullUrl" : "$(SPWebUrl)"
}
The request digest can then be extracted from the FormDigestValue
property.
If you're using CSOM, you have functionality for dealing with this built-in. (probably JSOM, too, unless it uses the __REQUESTDIGEST input) Microsoft.SharePoint.Client.ClientContext
uses the SOAP approach internally to manage its request digest and publicly exposes this functionality through its GetFormDigestDirect
method.
ClientContext clientContext = new ClientContext(webUrl);
// ...
FormDigestInfo formDigest = clientContext.GetFormDigestDirect();
// X-RequestDigest header value
string headerValue = formDigest.DigestValue;
// Digest expiration
DateTime expirationDate = formDigest.Expiration;
Usage Notes: While ClientContext
maintains and reuses a cached form digest for its requests, this method does not give you access to that cached value. Instead, this method requests a brand new form digest with each call, so you'll want to setup your own caching mechanism in order to re-use unexpired digests across multiple requests.
If you're using the JSOM API and don't have access to a __REQUESTDIGEST
input value, you can access the ClientContext
's cached digest with the following extensions. (Thanks to bdimag for pointing out the cache)
Assuming you use the request digest before the TimeoutSeconds
have elapsed, a valid REST request made like the following:
POST $(SPWebUrl)/_api/web/lists/getByTitle('MyList')/getchanges HTTP/1.1
Host: $(SPSiteHostname)
DataServiceVersion: 3.0
Accept: application/json; odata=nometadata
Content-Type: application/json; odata=verbose
X-RequestDigest: $(Request Digest)
Cookie: $(Authenticated Cookies)
Content-Length: 140
{
"query" : {
"__metadata" : {
"type" : "SP.ChangeQuery"
},
"Add" : "True",
"Item" : "True",
"Update" : "True"
}
}
should result in a successful response. If you inspect the headers of that response, you'll find something like:
HTTP/1.1 200 OK
Cache-Control: private, max-age=0
Content-Type: application/json;odata=fullmetadata;streaming=true;charset=utf-8
...
X-RequestDigest: 0xAABBCC...00,03 Sep 2014 18:09:34 -0000
...
Extracting the X-RequestDigest
response header will allow you to use it in a subsequent call. (I'm guessing that the timeout starts over from the time of your new response + $(TimeoutSeconds)
from the original digest request, but I've yet to confirm)
Unfortunately, the X-RequestDigest
header is only returned by REST requests that actually require a request digest. You will not receive the header for requests where a request digest is unrequired, such as: $(SPWebUrl)/_api/web/lists/getByTitle('MyList')/items
. Should you find yourself needing a new digest after the original has timed out, you'll need to make another request to the $(SPWebUrl)/_vti_bin/sites.asmx
web service.
A few example responses from when our requests fail:
The following response comes from a REST request made to the $(SPWebUrl)/_api/contextinfo
endpoint. (no authentication cookies specified)
HTTP/1.1 403 Forbidden
Cache-Control: private, max-age=0
Content-Type: application/json;odata=nometadata;charset=utf-8
...
Server: Microsoft-IIS/8.5
X-SharePointHealthScore: 0
X-Forms_Based_Auth_Required: $(SPRootSiteUrl)/_forms/default.aspx?ReturnUrl=/_layouts/15/error.aspx&Source=%2f_vti_bin%2fclient.svc%2fcontextinfo
X-Forms_Based_Auth_Return_Url: $(SPRootSiteUrl)/_layouts/15/error.aspx
X-MSDAVEXT_Error: 917656; Access+denied.+Before+opening+files+in+this+location%2c+you+must+first+browse+to+the+web+site+and+select+the+option+to+login+automatically.
DATASERVICEVERSION: 3.0
X-AspNet-Version: 4.0.30319
X-IDCRL_AUTH_PARAMS_V1: IDCRL Type="BPOSIDCRL", EndPoint="$(SiteRelativeUrl)/_vti_bin/idcrl.svc/", RootDomain="sharepoint.com", Policy="MBI"
...
Date: Wed, 12 Aug 2015 02:27:35 GMT
Content-Length: 201
{
"odata.error" : {
"code" : "-2147024891, System.UnauthorizedAccessException",
"message" : {
"lang" : "en-US",
"value" : "Access denied. You do not have permission to perform this action or access this resource."
}
}
}
Next, a response originating from a REST request made with an expired request digest (Note the X-RequestDigest
header specified in the response.. Not sure if that's usable, but it's worth a shot):
HTTP/1.1 403 FORBIDDEN
Cache-Control: private, max-age=0
Content-Type: application/json;odata=fullmetadata;charset=utf-8
...
Server: Microsoft-IIS/8.5
Set-Cookie: rtFa=$(RtfaAuthCookie)
Set-Cookie: FedAuth=$(FedAuth)
X-SharePointHealthScore: 0
X-RequestDigest: 0x19EFFF80617AB2E48B0A9FF0ABA1440B5301E7445F3859177771BF6A39C7E4A74643108D862505A2C99350B0EDB871EF3DDE960BB68060601268818027F04956,12 Aug 2015 02:39:22 -0000
DATASERVICEVERSION: 3.0
X-AspNet-Version: 4.0.30319
...
Date: Wed, 12 Aug 2015 02:39:22 GMT
Content-Length: 253
{
"odata.error" : {
"code" : "-2130575251, Microsoft.SharePoint.SPException",
"message" : {
"lang" : "en-US",
"value" : "The security validation for this page is invalid and might be corrupted. Please use your web browser's Back button to try your operation again."
}
}
}