I’m building a full-stack app using monday code (React UI + Express backend) and I want to make sure I’m following the correct authentication and authorization best practices.
Current architecture
UI → Backend authentication
From the UI, I fetch monday.get("sessionToken")
I send it to my backend in a request header
In the backend, I verify/decode the sessionToken using my client_secret
This works well for validating that the request comes from monday and from my app
When decoding the sessionToken JWT, I noticed that it does not include a short-lived token that can be used to call monday APIs.
OAuth & monday API calls
I implemented a standard OAuth flow
In the OAuth callback, I store the returned OAuth access token in Secure Storage
When calling monday APIs from the backend, I retrieve that token and use it like this:
import { ApiClient } from "@mondaydotcomorg/api"; const mondayApiClient = new ApiClient({ token: accessToken }); const query = ` query { custom_activity { color icon_id id name type } } `; const response = await mondayApiClient.request(query);
The concern
Since the backend uses the app’s OAuth access token, API calls are executed with the permissions granted to the app, not necessarily the permissions of the current user interacting with the UI.
This means a user could potentially trigger backend endpoints that:
Perform actions they personally don’t have permission for
For example: updating an item, creating updates, modifying board data, etc.
I want to ensure my app design aligns with monday’s security model and recommended approach.
Thanks in advance for any guidance 🙏
Best answer by mitchell.hudson
@emrekaraduman
Yeah, this is where it gets messy…
So you need to check the board, workspace, user and team permissions.
Using this query, you would then check the following
If board_kind is shareable/private then you need to check if the user has been added to the board directly (via owners or users) or via a team (get the team ID in team_owners and team_subscribers) and then check if the user is part of this team
You should be able to make a single query on the users query to retrieve all teams the user belongs to
If the board_kind is public, you will need to check the workspace kind, if this is closed, then you need to check the workspace teams and users (similar to board kind) to check if that user has access
If the board_kind is public and workspace kind is open, you need to check if the user has access to the workspace in the given product. I.e if I have a workspace in CRM, and I don’t have a CRM license I can view only, i can’t edit. (If you only need to worry about view only, then you can skip this step. I don’t actually know if you can check all products the user has access to)
If you have verified the steps above, you then need to check permissions on the board. This could beassignee collaborators everyone owners
If assignee, then you need to check all board People columns, check to see if User X is assigned to one of these columns directly of via a team
If everyone, then the user should have access
I don’t quite understand collaborators or owners (I assume owners only allows board owners, and collaborators is anyone assigned in a people column or via item_subscribers on the item level. It’s also a little tough because the https://developer.monday.com/api-reference/reference/boards#set-board-permission mutation accepts different values then the permissions field provides back.
I haven’t tested the query but it should work. It is messy, and would require maintence if monday ever add new permissions or change the structure, etc… but it should give the verification you are looking for.
1 - Recommended: Use the sessionToken to make API calls from the client side in your react app. This ensures that the user permissions match the logged in user.
2 - What you currently have, this is probably the easiest option outside of the first suggestion, but like you mentioned, your permissions may change. Sometimes this is required, i.e. you may want to perform an elevated action in some cases
3 - You can get a shortLivedToken when you trigger a custom automation (workflow/automation), but these need to be set up on a column change, etc… on the board and is quite annoying for the end user to manage this, requires lots of training, etc… and you would probably still end up with the same issue above that you are using a token for the user who added the automation, not the current user.
Is there a reason you can’t use the client side SDK to make the API calls?
It may just require sending a few requests between your front and back end to transfer the data you need and ultimately end up making the API request in your front end.
Hey @mitchell.hudson , thanks for the reply — that helps clarify the general model 👍 Let me explain why client-side API calls don’t fully work for my use case, and where my concern comes from.
In my app, I have scheduler / background events that run without an active UI context. Those scheduled executions read data from app storage, so the data must be stored and accessed on the backend.
I’m using:
import { Storage } from "@mondaycom/apps-sdk";
Because of this:
The UI sends structured objects to the backend
The backend persists them in Storage
Later, a scheduler event reads that stored data and performs actions
So moving all API calls to the client side isn’t really an option, because:
Schedulers don’t have a user session
They rely entirely on backend + storage state
The actual security concern
My backend endpoints are exposed under a live URL.
A legitimate flow today looks like this:
User interacts with the UI
UI sends:
sessionToken
an object containing an itemId (that the user does have access to)
Backend:
decodes & verifies sessionToken
stores the object in Storage
However, the risk scenario is:
A user copies the request from the Network tab
Replays it via curl / Postman
Changes the itemId to another item they don’t have permission for
The sessionToken is still valid
My middleware accepts the request
The backend stores an itemId the user should not be allowed to reference
At that point, the backend should reject the request, but:
The OAuth access token I’m using represents the app
Not the current user
So permission validation at the backend level becomes ambiguous
That’s the gap I’m trying to reason about — not how to elevate permissions, but how to avoid persisting data for resources the current user isn’t allowed to touch, when everything looks valid from a token perspective.
Just wanted to clarify the constraint and the concern clearly — appreciate any thoughts on whether this aligns with monday’s intended security model.
monday seperates this on a accountId + appId basis so there is no risk between different accounts, etc...
You can do all of your validation on the front end using the session token, save the itemId to global storage Then in your backend you can query the storage values using the app’s API token
Of course you may still run into an issue where the itemId that is stored in storage isn’t accessable by the user who authorised your app when processing your backend (although you will likely still have that issue with your current setup unless you are also verifying the itemId with both sessionToken and the app API token)
This probably doesn’t solve your root concern though, given someone could still clone the network request and alter the itemId that is being stored in storage
I can’t think of a way to protect against that without performing a full validation on the backend, i.e. getting the board permissions, checking the if the user has access to that item (i.e is assigned to a people column if needed), etc…
Thanks for the Global Storage tip—that’s a clever way to handle data sharing without exposing endpoints, though as you noted, the root risk of a replayed request with a manipulated itemId remains.
Regarding the "full validation" on the backend you mentioned:
Could you elaborate on what that looks like in practice with the monday API?
I’ve looked through the Core, Apps, and Marketplace API References, and I couldn’t find any explicit Permission object or a query/mutation that allows checking access levels (e.g., something like canUserEdit(userId, itemId)).
Since my backend executes requests using the App’s OAuth Token (which often has broader access than the specific user), simply querying the item will likely succeed even if the user shouldn't have access.
If there is a specific GraphQL pattern or endpoint for verifying User X’s access to Item Y via the backend, I’d love to know about it!
Using this query, you would then check the following
If board_kind is shareable/private then you need to check if the user has been added to the board directly (via owners or users) or via a team (get the team ID in team_owners and team_subscribers) and then check if the user is part of this team
You should be able to make a single query on the users query to retrieve all teams the user belongs to
If the board_kind is public, you will need to check the workspace kind, if this is closed, then you need to check the workspace teams and users (similar to board kind) to check if that user has access
If the board_kind is public and workspace kind is open, you need to check if the user has access to the workspace in the given product. I.e if I have a workspace in CRM, and I don’t have a CRM license I can view only, i can’t edit. (If you only need to worry about view only, then you can skip this step. I don’t actually know if you can check all products the user has access to)
If you have verified the steps above, you then need to check permissions on the board. This could beassignee collaborators everyone owners
If assignee, then you need to check all board People columns, check to see if User X is assigned to one of these columns directly of via a team
If everyone, then the user should have access
I don’t quite understand collaborators or owners (I assume owners only allows board owners, and collaborators is anyone assigned in a people column or via item_subscribers on the item level. It’s also a little tough because the https://developer.monday.com/api-reference/reference/boards#set-board-permission mutation accepts different values then the permissions field provides back.
I haven’t tested the query but it should work. It is messy, and would require maintence if monday ever add new permissions or change the structure, etc… but it should give the verification you are looking for.