For additional info, we are using a node server with sync gateway. Here are the sync gateway logs from when we try to upload an image.
//GOOD REQUEST THAT SUCCEEDED
21:20:52.485430 HTTP: #366: PUT /todos/<id 1>?new_edits=false (as <id 1>)
21:20:52.489843 CRUD: Stored doc "<id 1>" / "8-ea19fa2623e8c7968650784221218e40"
// FAILED REQUEST
21:18:08.827785 HTTP: #163: GET /todos/_changes?feed=longpoll&limit=50&heartbeat=300000&style=all_docs&since=30424 (as <id 2>)
21:18:08.866507 HTTP: #164: POST /todos/_revs_diff (as <id 2>)
21:18:09.107948 HTTP: #165: PUT /todos/<id 2>?new_edits=false
21:18:09.110896 Sync fn rejected: new=map[_rev:12-4d6c6fcc9fa0f028a4b5a6500ffa5f76 _attachments:map[profileImage:map[stub:true digest:sha1-GFxTm7f5N2Ott83RimyU+l/QKLM= revpos:12 content_type:image/jpg length:55321]] userId:<id 2> goal:strength type:user _revisions:map[start:12 ids:[4d6c6fcc9fa0f028a4b5a6500ffa5f76 cc5445fe9f9ad2ee1f4f0e16be811303 11a90b24bc9f0c2a3f7a714004860f22 718a3cd4e2dd4091a8729b9960fbdf05]] skillLevel:beginner weightUnit:kgs homeGym: gender:m heightUnit:cm _id:<id 2>] old={"gender":"m","goal":"strength","heightUnit":"cm","homeGym":"","type":"user","userId":"<id 2>","weightUnit":"kg"} --> 403 missing channel access
21:18:09.111048 HTTP: #165: --> 403 missing channel access (3.3 ms)
NOTE: the good request is from our iOS app and the failed request is from out android app. One difference in the logs we noticed was that the (as <id 2>) doesnt appear for the put request in the failure case. Maybe the cookie is not being sent up in the failure case?