SPFx - REST POST Forbidden in Chrome
-
29-12-2020 - |
Question
Problem
I'm trying to create/update records in a list using SPFx. It's working as expected in IE, but I'm getting a 403 - FORBIDDEN
in Chrome.
Request Digest
I am fetching my request digest value as such described by the documentation:
var digestCache:IDigestCache = this.context.serviceScope.consume(digestCacheServiceKey);
digestCache.fetchDigest(this.context.pageContext.web.serverRelativeUrl).then((digest: string) => {
// Do Something with the digest
console.log(digest);
});
I've seen other posts where it doesn't seem necessary to have to fetch the digest value, like Vardhaman's POST request post. For me, when I removed the fetchDigest
call, I started getting a 403
in IE.
Code
Here's my createListItem
method:
public createListItem(item: IMyListItem): Promise<IMyListItem | void> {
const { Title } = item;
const digestCache: IDigestCache = this._context.serviceScope.consume(DigestCache.serviceKey);
return digestCache.fetchDigest(this._context.pageContext.web.serverRelativeUrl)
.then((digest: string) => {
const spOpts: ISPHttpClientOptions = {
headers: {
"Accept": "application/json;odata=verbose",
"Content-Type": "application/json;odata=verbose",
"X-RequestDigest": digest
},
body: `{
__metadata: { "type": "SP.Data.MyListListItem" },
Title: "${Title}"
}`
};
return this._context.httpClient.post(
`${this._context.pageContext.web.absoluteUrl}/_api/web/lists/getbytitle('My List')/items`,
SPHttpClient.configurations.v1,
spOpts
)
.then((response: SPHttpClientResponse) => {
console.log("After creation response", response);
response.json().then((responseJSON: JSON) => {
console.log("JSON", responseJSON);
});
if (response.ok) {
return item;
}
return;
})
.catch((error: SPHttpClientResponse) => {
console.log(error);
return;
});
});
}
My updateListItem
method is the same with the difference being the header and the added item id
in the REST call.
updateListItem
headers
:
headers: {
"Accept": "application/json;odata=verbose",
"Content-Type": "application/json;odata=verbose",
"odata-version": "",
"IF-MATCH": "*",
"X-HTTP-Method": "MERGE",
"X-RequestDigest": digest
}
Notes
- User permissions are set correctly.
- Have tried this in the
/_layouts/15/workbench.aspx
as well as publishing app to app catalog and trying as an app. - Have tried multiple apps with separate types of POST requests.
Solution
With the General Availability release, we have to use SpHttpClient. i had to convert the old code that used httpclient to spHttpClient. You can also try out PnP JS in SPFx to implement CRUD operation which provides easy to implement methods. I have written an article on it. You can find the detailed code and article here
export default class PnPspCrudWebPart extends BaseClientSideWebPart<IPnPspCrudWebPartProps> {
private AddEventListeners() : void{
document.getElementById('AddItem').addEventListener('click',()=>this.AddItem());
document.getElementById('UpdateItem').addEventListener('click',()=>this.UpdateItem());
document.getElementById('DeleteItem').addEventListener('click',()=>this.DeleteItem());
}
private _getListData(): Promise<ISPList[]> {
return pnp.sp.web.lists.getByTitle("EmployeeList").items.get().then((response) => {
return response;
});
}
private getListData(): void {
this._getListData()
.then((response) => {
this._renderList(response);
});
}
private _renderList(items: ISPList[]): void {
let html: string = '<table class="TFtable" border=1 width=100% style="border-collapse: collapse;">';
html += `<th>EmployeeId</th><th>EmployeeName</th><th>Experience</th><th>Location</th>`;
items.forEach((item: ISPList) => {
html += `
<tr>
<td>${item.ID}</td>
<td>${item.EmployeeName}</td>
<td>${item.Experience}</td>
<td>${item.Location}</td>
</tr>
`;
});
html += `</table>`;
const listContainer: Element = this.domElement.querySelector('#spGetListItems');
listContainer.innerHTML = html;
}
public render(): void {
this.domElement.innerHTML = `
<div class="parentContainer" style="background-color: lightgrey">
<div class="ms-Grid-row ms-bgColor-themeDark ms-fontColor-white ${styles.row}">
<div class="ms-Grid-col ms-u-lg10 ms-u-xl8 ms-u-xlPush2 ms-u-lgPush1">
<span class="ms-font-xl ms-fontColor-white" style="font-size:28px">Welcome to SharePoint Framework Development using PnP JS Library</span>
<p class="ms-font-l ms-fontColor-white" style="text-align: left">Demo : SharePoint List CRUD using PnP JS and SPFx</p>
</div>
</div>
<div class="ms-Grid-row ms-bgColor-themeDark ms-fontColor-white ${styles.row}">
<div style="background-color:Black;color:white;text-align: center;font-
weight: bold;font-size:18px;">Employee Details</div>
</div>
<div style="background-color: lightgrey" >
<form >
<br>
<div data-role="header">
<h3>Add SharePoint List Items</h3>
</div>
<div data-role="main" class="ui-content">
<div >
<input id="EmployeeName" placeholder="EmployeeName" />
<input id="Experience" placeholder="Experience" />
<input id="Location" placeholder="Location" />
</div>
<div></br></div>
<div >
<button id="AddItem" type="submit" >Add</button>
</div>
</div>
<div data-role="header">
<h3>Update/Delete SharePoint List Items</h3>
</div>
<div data-role="main" class="ui-content">
<div >
<input id="EmployeeId" placeholder="EmployeeId" />
</div>
<div></br></div>
<div >
<button id="UpdateItem" type="submit" >Update</button>
<button id="DeleteItem" type="submit" >Delete</button>
</div>
</div>
</form>
</div>
<br>
<div style="background-color: lightgrey" id="spGetListItems" />
</div>
`;
this.getListData();
this.AddEventListeners();
}
AddItem()
{
pnp.sp.web.lists.getByTitle('EmployeeList').items.add({
EmployeeName : document.getElementById('EmployeeName')["value"],
Experience : document.getElementById('Experience')["value"],
Location:document.getElementById('Location')["value"]
});
alert("Record with Employee Name : "+ document.getElementById('EmployeeName')["value"] + " Added !");
}
UpdateItem()
{
var id = document.getElementById('EmployeeId')["value"];
pnp.sp.web.lists.getByTitle("EmployeeList").items.getById(id).update({
EmployeeName : document.getElementById('EmployeeName')["value"],
Experience : document.getElementById('Experience')["value"],
Location:document.getElementById('Location')["value"]
});
alert("Record with Employee Name : "+ document.getElementById('EmployeeName')["value"] + " Updated !");
}
DeleteItem()
{
pnp.sp.web.lists.getByTitle("EmployeeList").items.
getById(document.getElementById('EmployeeId')["value"]).delete();
alert("Record with Employee ID : "+ document.getElementById('EmployeeId')["value"] + " Deleted !");
}
protected get dataVersion(): Version {
return Version.parse('1.0');
}
protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration {
return {
pages: [
{
header: {
description: strings.PropertyPaneDescription
},
groups: [
{
groupName: strings.BasicGroupName,
groupFields: [
PropertyPaneTextField('description', {
label: strings.DescriptionFieldLabel
})
]
}
]
}
]
};
}