@lbeveridge
Not necessarily relevant to the issue, but are you trying to add this to the custom global JavaScript that runs inside Canvas or are you trying to do this from outside Canvas?
An example of the first kind would be to go through and make something a favorite every time people log into Canvas (a course that cannot be un-favorited). An example of the second kind would be if you want to go through and add a favorite to everyone and then be done with it and they can remove it if they want?
Both can be done with JavaScript. The first kind needs the CSRF token and runs within the browser, but may not get those people coming in through the mobile apps. The second kinds needs to be ran externally, say using a NodeJS script but needs to use the authorization header and won't have access to a CSRF token. If you're trying to take the CSRF token from within Canvas and then use it externally, you're going to have issues (401 error invalid access token).
The reason I ask is because if you want to do it outside of the browser, you can do it for all users using masquerading and an access token belonging to someone with the ability to masquerade. If I was a student and someone kept on favoriting a course for me each time I logged in and I kept on unfavoriting it, I wouldn't be happy as space on the dashboard is limited.
Okay, now to the issue. I'm including the whole investigative process so other people with the issue can come along later and have a resource. What I think the issue is at the bottom, but no one has ever accused me of being a TL;DR kind of person.
There are a couple of things that come to mind.
The first was is the person who is running the script (the person you're logged in as within Canvas or the person owning the token) actually enrolled in course 656? That is, can you go to the Courses page for that user and click the favorites to add or remove it? Not all courses are can be made favorites. However, I don't think this is it. Canvas returns a 200 OK when I try to add a favorite for a person who isn't enrolled in a course (maybe they will be at some time in the future).
The second is whether the course ID is correct. If I try this with a bogus ID that doesn't belong to us, I get a 404 Not Found error, not a 422 code.
So the next thing I do is to open up the developer tools in Chrome and look at the network traffic that Canvas sends to see what's missing. The only two things it's sending in the payload is the _method and the authenticity token. I notice you don't have _method in there.
So, I right click on the request and copy it as a fetch so I can replay it without the _method. Nope, that still works.
Then I look at the headers and something jumps out at me. They do include the CSRF token in the header, but they don't call it authenticity_token and then don't call it authorization. It's called x-csrf-token. So I take out that header and it still works.
That makes me wonder what you are including in the authenticity_token. It should just be the value of the CSRF.
If I remove the x-csrf-token header and garble the what I'm sending as an authenticity_token, I'm able to get the 422 error.
Garbling the authenticity_token so that it doesn't match the x-csrf-token gives a 500 code.
Changing the x-csrf-token while using the right authenticity_token works.
It seems that the x-csrf-token header is optional. If it is included, you do not need to include the authenticity_token in the data, but if you do include the authenticity_token in the data, then the x-csrf-token is overridden by the authenticity_token.
That seems that the easiest way to do it would be to include the x-csrf-token in the header and not mess with a body at all.
But all that then comes down to what you are using as the token. You didn't share that (good reason for not doing so), but then you have to wonder, after tracking everything else down, whether you're sending the right thing or not.
You should send just the value of the csrf-token and not the _crsf_token= portion. Depending on where you send it will determine whether you should decode it or not first.
If I look at my set-cookie header that is returned as part of the response or I use document.cookie to obtain the _csrf_token value, I get a uriEncoded value that contains things like %2F, %2B, and %3D.
Those cannot be passed in either the x-csrf-token or authenticity_token (if you are using JSON). You have to pass the decoded form.
If I let run my cookie _csrf_token through decodeURIComponent(), then I can send it as either x-csrf-token or authenticity_token. Note that decodeURI will not fix it, it needs to be decodeURIComponent.
If you are using the content-type of application/x-www-form-urlencoded; charset=UTF-8, then you must include the encoded form. If you try to send the decoded form as the authenticity_token, it will give a 422 error.
In short, use a header of x-csrf-token and make sure you pass the _csrf_token cookie value through decodeURIComponent() before doing so.