Access token not being sent with API Requests
Environment
- Soapbox version: soapbox-pub/soapbox (v3.2.0).
- Backend (Mastodon, Pleroma, etc): Custom in development
- Browser/OS: Google Chrome Version 114.0.5735.199 (Official Build) (64-bit) on Windows 11
Bug description
Context: I am working on a custom backend as a learning project. Users can create an app at /api/v1/apps, verify credentials, and get a Bearer token. The behavior is inconsistent as I can fetch the placeholder home timeline object I created without issue. However, when I post to my /api/v1/statuses endpoint, I get a 401 error. When I check the HTTP Request from the developer console, I see that the request is not sending the bearer token. At first I thought it was my code like okay maybe I sent the wrong response. But Soapbox sends to /api/v1/verify_credentials just fine and includes the bearer token. Does Soapbox expect me to set a cookie or something I missed?
/api/v1/statuses request sent by soapbox
fetch("https://localhost/api/v1/statuses", {
"headers": {
"accept": "application/json, text/plain, */*",
"accept-language": "en-US,en;q=0.9",
"content-type": "application/json",
"idempotency-key": "889571e2-41f1-47a8-a486-6cf4cb3561d6",
"sec-ch-ua": "\"Not.A/Brand\";v=\"8\", \"Chromium\";v=\"114\", \"Google Chrome\";v=\"114\"",
"sec-ch-ua-mobile": "?0",
"sec-ch-ua-platform": "\"Windows\"",
"sec-fetch-dest": "empty",
"sec-fetch-mode": "cors",
"sec-fetch-site": "same-origin"
},
"referrer": "https://localhost/",
"referrerPolicy": "strict-origin-when-cross-origin",
"body": "{\"status\":\"test\",\"in_reply_to_id\":null,\"quote_id\":null,\"media_ids\":[],\"sensitive\":false,\"spoiler_text\":\"\",\"visibility\":\"public\",\"content_type\":\"text/plain\",\"poll\":null,\"scheduled_at\":null,\"to\":[],\"group_id\":null}",
"method": "POST",
"mode": "cors",
"credentials": "include"
});
/oauth/token response
{
"access_token": "eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJtVlJwZ2NpM0E4NXV2bjA1NGJ6TjlnS0x3SllFLTA2NXdKNWM0QzhkUjBBIn0.eyJleHAiOjE2ODk0ODQxNjksImlhdCI6MTY4OTQ0ODE2OSwianRpIjoiMWRiYWI1OWUtYWQ2OC00YTczLTg1ODUtZWZjMDg1MTdjY2ZlIiwiaXNzIjoiaHR0cHM6Ly9hdXRoLmNlbGVzdGlhLnNvY2lhbC9yZWFsbXMvZGV2ZWxvcGVyIiwiYXVkIjoiYWNjb3VudCIsInN1YiI6ImM3NDBhMzI4LWNlNGQtNGYyZi1hNWNjLTc4ZDQ4NzRhYzdjNiIsInR5cCI6IkJlYXJlciIsImF6cCI6Im5vZGVqcyIsInNlc3Npb25fc3RhdGUiOiJmZGJmOGVjZi1mNTgwLTRhMzctYWYyZi04OTBkZWMwMzk3NTQiLCJhY3IiOiIxIiwiYWxsb3dlZC1vcmlnaW5zIjpbImh0dHBzOi8vZWxhaW5lLWphY2tzb24tc3BlY2lhbC1jb3VzY291cy1najQ0cHJwanJqY3dwdnItMzAwMC5wcmV2aWV3LmFwcC5naXRodWIuZGV2IiwiLyoiXSwicmVhbG1fYWNjZXNzIjp7InJvbGVzIjpbIm9mZmxpbmVfYWNjZXNzIiwidW1hX2F1dGhvcml6YXRpb24iLCJkZWZhdWx0LXJvbGVzLWRldmVsb3BlciJdfSwicmVzb3VyY2VfYWNjZXNzIjp7ImFjY291bnQiOnsicm9sZXMiOlsibWFuYWdlLWFjY291bnQiLCJtYW5hZ2UtYWNjb3VudC1saW5rcyIsInZpZXctcHJvZmlsZSJdfX0sInNjb3BlIjoib3BlbmlkIGVtYWlsIHByb2ZpbGUiLCJzaWQiOiJmZGJmOGVjZi1mNTgwLTRhMzctYWYyZi04OTBkZWMwMzk3NTQiLCJlbWFpbF92ZXJpZmllZCI6ZmFsc2UsInByZWZlcnJlZF91c2VybmFtZSI6ImVsYWluZSIsImdpdmVuX25hbWUiOiIiLCJmYW1pbHlfbmFtZSI6IiJ9.HLWWcENbNLYZuWgjBT9rtnnIdlRT2U_LGehUIgGVUTmOs05TJMQ-BUNNNDDr0TNYbaG2-pm0gEp1yDdrl-RBBpZ2JcNNmAvgJPha9tK_CHQZcC2pJSoD9lbrCUVZSiTj_Esb7pwdCGPE804yerexDxz1kG5LrQ8oYxb9d1pwgmTivz9u-jdFCoSphlXWem4a8sd5YW-GJFZwyCBRCNTj7kt1iwnqVVMZ6qeS8Gzohm9QHKPvmfpQ1A26T4wX-4hEKDtW5IK89zlX1fbPxoOMzLMqmdSasv4-xF9oZ0xb223MyOqT7rRpW2jq1XNGao_0zIcWNDZaQ2HgTviMTsc1SA",
"expires_in": 36000,
"refresh_expires_in": 1800,
"refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJiZWNjODVmMC0wN2I2LTQwYjAtODdiYS1iZWJiZTY4ZDg1YzAifQ.eyJleHAiOjE2ODk0NDk5NjksImlhdCI6MTY4OTQ0ODE2OSwianRpIjoiNzI5NDhhODgtZTliYy00NjMzLWFlZjItMjNjMmUwMGNkZTU2IiwiaXNzIjoiaHR0cHM6Ly9hdXRoLmNlbGVzdGlhLnNvY2lhbC9yZWFsbXMvZGV2ZWxvcGVyIiwiYXVkIjoiaHR0cHM6Ly9hdXRoLmNlbGVzdGlhLnNvY2lhbC9yZWFsbXMvZGV2ZWxvcGVyIiwic3ViIjoiYzc0MGEzMjgtY2U0ZC00ZjJmLWE1Y2MtNzhkNDg3NGFjN2M2IiwidHlwIjoiUmVmcmVzaCIsImF6cCI6Im5vZGVqcyIsInNlc3Npb25fc3RhdGUiOiJmZGJmOGVjZi1mNTgwLTRhMzctYWYyZi04OTBkZWMwMzk3NTQiLCJzY29wZSI6Im9wZW5pZCBlbWFpbCBwcm9maWxlIiwic2lkIjoiZmRiZjhlY2YtZjU4MC00YTM3LWFmMmYtODkwZGVjMDM5NzU0In0.NEDKi3xS_3dIsguLEPFV80wf95dLNoioZnDMz4Gdabs",
"token_type": "Bearer",
"id_token": "eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJtVlJwZ2NpM0E4NXV2bjA1NGJ6TjlnS0x3SllFLTA2NXdKNWM0QzhkUjBBIn0.eyJleHAiOjE2ODk0ODQxNjksImlhdCI6MTY4OTQ0ODE2OSwiYXV0aF90aW1lIjowLCJqdGkiOiI5Nzk0M2M4My1jMDhmLTRmYmQtOTgwZi1jMTAxYTU0NzdjNjAiLCJpc3MiOiJodHRwczovL2F1dGguY2VsZXN0aWEuc29jaWFsL3JlYWxtcy9kZXZlbG9wZXIiLCJhdWQiOiJub2RlanMiLCJzdWIiOiJjNzQwYTMyOC1jZTRkLTRmMmYtYTVjYy03OGQ0ODc0YWM3YzYiLCJ0eXAiOiJJRCIsImF6cCI6Im5vZGVqcyIsInNlc3Npb25fc3RhdGUiOiJmZGJmOGVjZi1mNTgwLTRhMzctYWYyZi04OTBkZWMwMzk3NTQiLCJhdF9oYXNoIjoiT3lQRlFDbGtnTmdjczBxdFVwMXdWUSIsImFjciI6IjEiLCJzaWQiOiJmZGJmOGVjZi1mNTgwLTRhMzctYWYyZi04OTBkZWMwMzk3NTQiLCJlbWFpbF92ZXJpZmllZCI6ZmFsc2UsInByZWZlcnJlZF91c2VybmFtZSI6ImVsYWluZSIsImdpdmVuX25hbWUiOiIiLCJmYW1pbHlfbmFtZSI6IiJ9.gESOHthM1p3ZQziYn3ulaZEyGM8gJ6CYVmJSRfilJAvdyALawdFvooqPEqqlw7Tn8XErsjDHioMrYSRC-PunyGy0d9xqkblYe1088R13kmvRGhNvXUfu_iF8IU_-K0zVpevUV4k3zaFbvWSMszP2g8W9gCwvRdpuDlsa-XBA9UTh0BQ1fDuWioJuyY7WEW3zK7PTnAKZni-RyFC-VHqxZC2fC7VevhsTpiWiwOD5WjdwJuYjc3bwH6jHGA_jpcwelo6GxDICtSKh-7CjwN2fqJmdBfPVrQGKdOp45mugKCIlEP_W2XextwCh8ucj7Ik42NgkOklwX_rXgT_uByswKg",
"not-before-policy": 0,
"session_state": "fdbf8ecf-f580-4a37-af2f-890dec039754",
"scope": "openid email profile"
}
soapbox:auth
{
"app": {
"access_token": null,
"client_id": "nodejs",
"client_secret": "Bw5O326Q3DDdXjdxxfugBdkXyTtVZhbA",
"id": null,
"name": null,
"redirect_uri": null,
"token_type": null,
"vapid_key": "",
"website": "https://soapbox.pub/"
},
"tokens": {
"eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJtVlJwZ2NpM0E4NXV2bjA1NGJ6TjlnS0x3SllFLTA2NXdKNWM0QzhkUjBBIn0.eyJleHAiOjE2ODk0NTc1NDcsImlhdCI6MTY4OTQ1NzI0NywianRpIjoiMzcwZWExNjItZGE0Ny00MjcyLTk2YTktOWM5NzVmMDI1MmY3IiwiaXNzIjoiaHR0cHM6Ly9hdXRoLmNlbGVzdGlhLnNvY2lhbC9yZWFsbXMvZGV2ZWxvcGVyIiwiYXVkIjoiYWNjb3VudCIsInN1YiI6ImM3NDBhMzI4LWNlNGQtNGYyZi1hNWNjLTc4ZDQ4NzRhYzdjNiIsInR5cCI6IkJlYXJlciIsImF6cCI6Im5vZGVqcyIsInNlc3Npb25fc3RhdGUiOiI4ZDhkZjNhOC1jMGM3LTRlZjktOWY4Yi1iM2JlMjkwNjdhNjUiLCJhY3IiOiIxIiwiYWxsb3dlZC1vcmlnaW5zIjpbImh0dHBzOi8vZWxhaW5lLWphY2tzb24tc3BlY2lhbC1jb3VzY291cy1najQ0cHJwanJqY3dwdnItMzAwMC5wcmV2aWV3LmFwcC5naXRodWIuZGV2IiwiLyoiXSwicmVhbG1fYWNjZXNzIjp7InJvbGVzIjpbIm9mZmxpbmVfYWNjZXNzIiwidW1hX2F1dGhvcml6YXRpb24iLCJkZWZhdWx0LXJvbGVzLWRldmVsb3BlciJdfSwicmVzb3VyY2VfYWNjZXNzIjp7ImFjY291bnQiOnsicm9sZXMiOlsibWFuYWdlLWFjY291bnQiLCJtYW5hZ2UtYWNjb3VudC1saW5rcyIsInZpZXctcHJvZmlsZSJdfX0sInNjb3BlIjoib3BlbmlkIGVtYWlsIHByb2ZpbGUiLCJzaWQiOiI4ZDhkZjNhOC1jMGM3LTRlZjktOWY4Yi1iM2JlMjkwNjdhNjUiLCJlbWFpbF92ZXJpZmllZCI6ZmFsc2UsInByZWZlcnJlZF91c2VybmFtZSI6ImVsYWluZSIsImdpdmVuX25hbWUiOiIiLCJmYW1pbHlfbmFtZSI6IiJ9.hruygTJqdSp6AKTBBWVczbBHeYBh53uPpeShKSnebOtQNO4XtoqulESSP-a-2rQPyiXF1NFgHYrRoKklgS_c-sdwGBDbbz2SfgHsNxcg73TnsesV0p7aolULH95WAt9NuBx9F4FYHfzO1CstX4F-T1x4U5uvlP6-hcme_QyDQI0ON1ieOBJhQ3yOLHgcG1NeDkcqR4P5hpRHBpgphKiz7-OwQivRbet7CUCUvo6tG6GTjUpTBT-70ec22zc55eMCgV1CJiezxzwATPbTBF7U0DxmYiSBcWzQ8sdDd_6XxsqHxqO5V8d84gRlESRCPrPwL17qTi-MW77ECT4HyKWsWQ": {
"access_token": "eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJtVlJwZ2NpM0E4NXV2bjA1NGJ6TjlnS0x3SllFLTA2NXdKNWM0QzhkUjBBIn0.eyJleHAiOjE2ODk0NTc1NDcsImlhdCI6MTY4OTQ1NzI0NywianRpIjoiMzcwZWExNjItZGE0Ny00MjcyLTk2YTktOWM5NzVmMDI1MmY3IiwiaXNzIjoiaHR0cHM6Ly9hdXRoLmNlbGVzdGlhLnNvY2lhbC9yZWFsbXMvZGV2ZWxvcGVyIiwiYXVkIjoiYWNjb3VudCIsInN1YiI6ImM3NDBhMzI4LWNlNGQtNGYyZi1hNWNjLTc4ZDQ4NzRhYzdjNiIsInR5cCI6IkJlYXJlciIsImF6cCI6Im5vZGVqcyIsInNlc3Npb25fc3RhdGUiOiI4ZDhkZjNhOC1jMGM3LTRlZjktOWY4Yi1iM2JlMjkwNjdhNjUiLCJhY3IiOiIxIiwiYWxsb3dlZC1vcmlnaW5zIjpbImh0dHBzOi8vZWxhaW5lLWphY2tzb24tc3BlY2lhbC1jb3VzY291cy1najQ0cHJwanJqY3dwdnItMzAwMC5wcmV2aWV3LmFwcC5naXRodWIuZGV2IiwiLyoiXSwicmVhbG1fYWNjZXNzIjp7InJvbGVzIjpbIm9mZmxpbmVfYWNjZXNzIiwidW1hX2F1dGhvcml6YXRpb24iLCJkZWZhdWx0LXJvbGVzLWRldmVsb3BlciJdfSwicmVzb3VyY2VfYWNjZXNzIjp7ImFjY291bnQiOnsicm9sZXMiOlsibWFuYWdlLWFjY291bnQiLCJtYW5hZ2UtYWNjb3VudC1saW5rcyIsInZpZXctcHJvZmlsZSJdfX0sInNjb3BlIjoib3BlbmlkIGVtYWlsIHByb2ZpbGUiLCJzaWQiOiI4ZDhkZjNhOC1jMGM3LTRlZjktOWY4Yi1iM2JlMjkwNjdhNjUiLCJlbWFpbF92ZXJpZmllZCI6ZmFsc2UsInByZWZlcnJlZF91c2VybmFtZSI6ImVsYWluZSIsImdpdmVuX25hbWUiOiIiLCJmYW1pbHlfbmFtZSI6IiJ9.hruygTJqdSp6AKTBBWVczbBHeYBh53uPpeShKSnebOtQNO4XtoqulESSP-a-2rQPyiXF1NFgHYrRoKklgS_c-sdwGBDbbz2SfgHsNxcg73TnsesV0p7aolULH95WAt9NuBx9F4FYHfzO1CstX4F-T1x4U5uvlP6-hcme_QyDQI0ON1ieOBJhQ3yOLHgcG1NeDkcqR4P5hpRHBpgphKiz7-OwQivRbet7CUCUvo6tG6GTjUpTBT-70ec22zc55eMCgV1CJiezxzwATPbTBF7U0DxmYiSBcWzQ8sdDd_6XxsqHxqO5V8d84gRlESRCPrPwL17qTi-MW77ECT4HyKWsWQ",
"account": "bcab0554-fbd5-4050-87cf-a7850a366666",
"created_at": 0,
"expires_in": 300,
"id": null,
"me": "https://localhost/user/elaine",
"refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJiZWNjODVmMC0wN2I2LTQwYjAtODdiYS1iZWJiZTY4ZDg1YzAifQ.eyJleHAiOjE2ODk0NTkwNDcsImlhdCI6MTY4OTQ1NzI0NywianRpIjoiMTcwNWZjZDItNDEzYi00NjU3LTljYmQtN2Y0ZmRjM2EyNDFiIiwiaXNzIjoiaHR0cHM6Ly9hdXRoLmNlbGVzdGlhLnNvY2lhbC9yZWFsbXMvZGV2ZWxvcGVyIiwiYXVkIjoiaHR0cHM6Ly9hdXRoLmNlbGVzdGlhLnNvY2lhbC9yZWFsbXMvZGV2ZWxvcGVyIiwic3ViIjoiYzc0MGEzMjgtY2U0ZC00ZjJmLWE1Y2MtNzhkNDg3NGFjN2M2IiwidHlwIjoiUmVmcmVzaCIsImF6cCI6Im5vZGVqcyIsInNlc3Npb25fc3RhdGUiOiI4ZDhkZjNhOC1jMGM3LTRlZjktOWY4Yi1iM2JlMjkwNjdhNjUiLCJzY29wZSI6Im9wZW5pZCBlbWFpbCBwcm9maWxlIiwic2lkIjoiOGQ4ZGYzYTgtYzBjNy00ZWY5LTlmOGItYjNiZTI5MDY3YTY1In0.vL5CaBLfj_8XmTo1glRyj8FsqNWZ6tvtBoKhbvtbNro",
"scope": "openid email profile",
"token_type": "Bearer"
}
},
"users": {
"https://localhost/user/elaine": {
"access_token": "eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJtVlJwZ2NpM0E4NXV2bjA1NGJ6TjlnS0x3SllFLTA2NXdKNWM0QzhkUjBBIn0.eyJleHAiOjE2ODk0NTc1NDcsImlhdCI6MTY4OTQ1NzI0NywianRpIjoiMzcwZWExNjItZGE0Ny00MjcyLTk2YTktOWM5NzVmMDI1MmY3IiwiaXNzIjoiaHR0cHM6Ly9hdXRoLmNlbGVzdGlhLnNvY2lhbC9yZWFsbXMvZGV2ZWxvcGVyIiwiYXVkIjoiYWNjb3VudCIsInN1YiI6ImM3NDBhMzI4LWNlNGQtNGYyZi1hNWNjLTc4ZDQ4NzRhYzdjNiIsInR5cCI6IkJlYXJlciIsImF6cCI6Im5vZGVqcyIsInNlc3Npb25fc3RhdGUiOiI4ZDhkZjNhOC1jMGM3LTRlZjktOWY4Yi1iM2JlMjkwNjdhNjUiLCJhY3IiOiIxIiwiYWxsb3dlZC1vcmlnaW5zIjpbImh0dHBzOi8vZWxhaW5lLWphY2tzb24tc3BlY2lhbC1jb3VzY291cy1najQ0cHJwanJqY3dwdnItMzAwMC5wcmV2aWV3LmFwcC5naXRodWIuZGV2IiwiLyoiXSwicmVhbG1fYWNjZXNzIjp7InJvbGVzIjpbIm9mZmxpbmVfYWNjZXNzIiwidW1hX2F1dGhvcml6YXRpb24iLCJkZWZhdWx0LXJvbGVzLWRldmVsb3BlciJdfSwicmVzb3VyY2VfYWNjZXNzIjp7ImFjY291bnQiOnsicm9sZXMiOlsibWFuYWdlLWFjY291bnQiLCJtYW5hZ2UtYWNjb3VudC1saW5rcyIsInZpZXctcHJvZmlsZSJdfX0sInNjb3BlIjoib3BlbmlkIGVtYWlsIHByb2ZpbGUiLCJzaWQiOiI4ZDhkZjNhOC1jMGM3LTRlZjktOWY4Yi1iM2JlMjkwNjdhNjUiLCJlbWFpbF92ZXJpZmllZCI6ZmFsc2UsInByZWZlcnJlZF91c2VybmFtZSI6ImVsYWluZSIsImdpdmVuX25hbWUiOiIiLCJmYW1pbHlfbmFtZSI6IiJ9.hruygTJqdSp6AKTBBWVczbBHeYBh53uPpeShKSnebOtQNO4XtoqulESSP-a-2rQPyiXF1NFgHYrRoKklgS_c-sdwGBDbbz2SfgHsNxcg73TnsesV0p7aolULH95WAt9NuBx9F4FYHfzO1CstX4F-T1x4U5uvlP6-hcme_QyDQI0ON1ieOBJhQ3yOLHgcG1NeDkcqR4P5hpRHBpgphKiz7-OwQivRbet7CUCUvo6tG6GTjUpTBT-70ec22zc55eMCgV1CJiezxzwATPbTBF7U0DxmYiSBcWzQ8sdDd_6XxsqHxqO5V8d84gRlESRCPrPwL17qTi-MW77ECT4HyKWsWQ",
"id": "bcab0554-fbd5-4050-87cf-a7850a366666",
"url": "https://localhost/user/elaine"
}
},
"me": "https://localhost/user/elaine"
}
It's worth noting I use keycloak and have workaround to power /oauth/token
app.post('/oauth/token', (req, res, next) => {
req.body.client_id = process.env.KEYCLOAK_RESOURCE;
req.body.client_secret = process.env.KEYCLOAK_CLIENT_SECRET;
req.body.scope = "openid profile email";
next();
}, async (req, res) => {
try {
const data = qs.stringify(req.body);
const config = {
method: 'post',
url: 'https://SERVERURL/realms/developer/protocol/openid-connect/token',
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
data : data
};
// Use axios to send a POST request to the API
const response = await axios(config);
// Return the API's response to the user
res.status(response.status).send(response.data);
} catch (error) {
// If there's an error in the axios request, forward it to the user
if (error.response) {
// The request was made and the server responded with a status code
// that falls out of the range of 2xx
res.status(error.response.status).send(error.response.data);
} else if (error.request) {
// The request was made but no response was received
res.status(500).send({ message: 'No response received from the API' });
} else {
// Something happened in setting up the request that triggered an Error
res.status(500).send({ message: 'An error occurred in the request setup' });
}
}
});