| name | monday-oauth-migrate |
| description | Migrate an existing monday.com app from the old OAuth flow to the new OAuth 2.1 flow. Changes token endpoint from oauth2/token to oauth_ms/oauth/token, adds refresh token support, updates token storage, and optionally adds PKCE. ALWAYS use when someone mentions migrating monday OAuth, switching to the new OAuth flow, adding refresh tokens, updating the monday token endpoint, token expiration, or when code calls auth.monday.com/oauth2/token. Also trigger on OAuth deadline/deprecation announcements. Do NOT use for new OAuth from scratch, non-monday OAuth (Google, Slack, Spotify), client credentials flow, DCR, or general monday debugging. |
| allowed-tools | Read Bash(grep *) Bash(find *) Bash(git *) Bash(npm *) Bash(yarn *) Bash(pnpm *) |
Monday.com OAuth Migration Skill
You are migrating a monday.com app from the old OAuth flow to the new OAuth 2.1 flow. Follow these phases in order. Each phase has clear entry/exit criteria. Do not skip phases.
Phase 0: Detection โ Does This App Need Migration?
Search the entire codebase for these patterns:
grep -r "auth.monday.com/oauth2/authorize" . --include="*.ts" --include="*.js" --include="*.py" --include="*.rb" --include="*.go" --include="*.java" --include="*.php" --include="*.cs" --include="*.rs" --include="*.jsx" --include="*.tsx" --include="*.mjs" --include="*.cjs" -l
grep -r "auth.monday.com/oauth2/token" . --include="*.ts" --include="*.js" --include="*.py" --include="*.rb" --include="*.go" --include="*.java" --include="*.php" --include="*.cs" --include="*.rs" --include="*.jsx" --include="*.tsx" --include="*.mjs" --include="*.cjs" -l
Also search for partial patterns like oauth2/authorize and oauth2/token in case the base URL is constructed dynamically.
If NEITHER pattern is found:
Tell the developer:
"I didn't find any OAuth flow usage in this codebase (no calls to auth.monday.com/oauth2/authorize or auth.monday.com/oauth2/token). This app doesn't appear to be using the OAuth flow yet. This skill is specifically designed for migrating existing OAuth integrations. If you'd like to implement OAuth from scratch instead, I can help, but the migration workflow may not fit perfectly โ want to continue anyway?"
If they want to continue, proceed but note that some steps (like finding need extra guidance.
If patterns ARE found: proceed to Phase 1.
Before moving on, report what you found: which files contain OAuth references, and give a brief summary of the current OAuth implementation structure you see (authorization redirect, callback handler, token exchange, token storage).
Phase 1: Enable OAuth in the Dev Center (Manual Step)
This step cannot be automated. Ask the developer to do the following manually:
You need to enable the new OAuth flow in the monday.com Developer Center:
- Go to your app in the Developer Center
- Navigate to the "OAuth & Permissions" tab
- Create a new draft version of your app (the toggle is per-version)
- Enable the "New OAuth Flow" toggle for the draft version
- Make sure the draft version is set to "Active for me" so you can test it
Your scopes and redirect URIs will be cloned from the previous version automatically โ no need to reconfigure them.
Tell me when you've completed this step so I can start migrating your code.
Wait for the developer to confirm before proceeding. Do NOT start code changes until they confirm.
Phase 2: PKCE Decision
Use AskUserQuestion to ask:
Question: "Would you like to add PKCE (Proof Key for Code Exchange) to your OAuth flow? PKCE adds an extra layer of security that protects against authorization code interception attacks. It's recommended for all apps and required for DCR-registered clients."
Options:
- Yes, add PKCE (Recommended) โ Adds code_verifier generation and code_challenge to the authorization flow
- No, skip PKCE โ Keep the flow simple without PKCE
Remember their choice โ it affects Phase 3 and Phase 4.
Phase 3: Migrate the Authorization Request (only if PKCE was chosen)
If the developer chose No PKCE โ skip this phase entirely. The authorization endpoint (GET https://auth.monday.com/oauth2/authorize) stays exactly the same.
If the developer chose Yes PKCE, find where the authorization URL is constructed and add PKCE parameters:
What to add
- Generate a code verifier โ a cryptographically random string (43-128 characters):
JavaScript/TypeScript:
const crypto = require('crypto');
const codeVerifier = crypto.randomBytes(32).toString('base64url');
Python:
import secrets, hashlib, base64
code_verifier = secrets.token_urlsafe(32)
- Compute the code challenge โ SHA-256 hash of the verifier, base64url-encoded:
JavaScript/TypeScript:
const codeChallenge = crypto.createHash('sha256').update(codeVerifier).digest('base64url');
Python:
code_challenge = base64.urlsafe_b64encode(hashlib.sha256(code_verifier.encode()).digest()).rstrip(b'=').decode()
- Add ONLY the PKCE params to the authorization URL โ do NOT touch existing parameters:
&code_challenge=CODE_CHALLENGE&code_challenge_method=S256
Important: The app may already use state, scope, redirect_uri, or other query parameters in the authorization URL. Preserve ALL existing parameters exactly as they are. Only append code_challenge and code_challenge_method. Do not replace, rename, or restructure the URL construction โ just add the two new params.
- Store the code_verifier โ it must be available when exchanging the code for tokens in the callback. Use whatever mechanism the app already uses to maintain state between the authorization redirect and the callback (session, database, cookie, etc.). If the app already passes a
state parameter, you can key the verifier by that existing state value โ do not generate a new state to replace it.
Adapt the code to match the language, framework, and patterns already used in the developer's codebase. Do not introduce new dependencies if the language's standard library already provides crypto primitives. Do not restructure or rename existing variables, routes, or functions โ make the minimal additions needed.
Phase 4: Migrate the Token Exchange
This is the core migration step. Find the callback endpoint that:
- Receives the authorization
code from monday.com
- Sends it (along with
client_id and client_secret) to exchange for tokens
4a. Change the token exchange endpoint
Find the call to the old endpoint:
POST https://auth.monday.com/oauth2/token
Change it to the new endpoint:
POST https://auth.monday.com/oauth_ms/oauth/token
The request body parameters remain the same: client_id, client_secret, code, and optionally redirect_uri.
If PKCE was chosen: also include code_verifier in the request body โ this is the same verifier generated in Phase 3.
4b. Handle the new response format
The old flow returned only an access_token (which never expired).
The new flow returns both an access_token (which expires) and a refresh_token:
{
"access_token": "eyJhbGciOi...",
"refresh_token": "eyJhbGciOi...",
"token_type": "Bearer",
"scope": "boards:read users:read"
}
4c. Update token storage
This is critical. Find where the old access_token was being saved (database, file, env variable, key-value store, etc.) and ensure:
- The
refresh_token is also saved alongside the access token
- Both tokens are associated with the same user/account context
- The storage mechanism can handle updating both tokens (they will change on every refresh)
Look for patterns like:
- Database columns (e.g.,
access_token column โ add a refresh_token column)
- Object/model properties storing the token
- Key-value store entries
- Environment variables or config files
If the developer stores tokens in a database, you may need to add a migration or alter the schema. If they use a simpler storage mechanism, just ensure both tokens are persisted.
Phase 5: Add Token Refresh Logic
This is entirely new functionality โ the old flow had no token expiration, so there was no refresh mechanism.
5a. Create the refresh function
Create a function that refreshes an expired access token. It calls the same token endpoint but with different parameters:
Endpoint:
POST https://auth.monday.com/oauth_ms/oauth/token
Request body:
{
"client_id": "YOUR_CLIENT_ID",
"client_secret": "YOUR_CLIENT_SECRET",
"refresh_token": "CURRENT_REFRESH_TOKEN",
"grant_type": "refresh_token"
}
Response:
{
"access_token": "new_access_token...",
"refresh_token": "new_refresh_token...",
"token_type": "Bearer",
"scope": "boards:read users:read"
}
Each refresh returns a new refresh_token. Always replace both the access token AND the refresh token in storage.
5b. Integrate refresh into the app's API call flow
Find where the app uses the access token to call the monday.com API (typically via Authorization: Bearer <token> header or the monday SDK). Add logic to handle token expiration:
Recommended pattern โ proactive check before API calls:
- Decode the JWT access token and check the
exp (expiration) claim
- If the token is expired or about to expire (within ~60 seconds), refresh it first
- Then proceed with the API call using the new token
Alternative pattern โ reactive refresh on 401:
- Make the API call
- If the response is
401 Unauthorized, attempt to refresh the token
- Retry the original API call with the new token
- If refresh also fails, the user needs to re-authorize
Choose whichever pattern best fits the existing codebase architecture. If the app already has middleware, interceptors, or a central API client, hook into that.
5c. Handle refresh failures
If a refresh call fails (e.g., refresh token was revoked or expired), the user must re-authorize through the full OAuth flow again. Make sure the app handles this gracefully โ redirect to the authorization URL or show an appropriate error.
Phase 6: Token Revocation (Optional)
Use AskUserQuestion to ask:
Question: "Would you like to add a token revocation function? This lets your app revoke tokens when a user disconnects or uninstalls (e.g., cleanup on uninstall webhook)."
Options:
- Yes, add revocation โ Adds a function to revoke access or refresh tokens
- No, skip โ Skip revocation for now
If Yes, create a revocation function:
Endpoint:
POST https://auth.monday.com/oauth_ms/oauth/revoke
Request body:
{
"token": "TOKEN_TO_REVOKE",
"client_id": "YOUR_CLIENT_ID",
"client_secret": "YOUR_CLIENT_SECRET",
"token_type_hint": "access_token"
}
token_type_hint can be either "access_token" or "refresh_token" โ it helps the server identify the token type but is optional.
- On success, the response is
{ "success": true }.
Place this function near the other OAuth functions in the codebase.
If No, skip this phase entirely.
Phase 7: Run Tests
After all code changes are complete:
- Detect the test runner โ look for
package.json scripts, pytest.ini, go test, Makefile targets, etc.
- Run the existing test suite to check for regressions
- Report results โ if tests fail, investigate whether the failures are related to the migration changes and fix them
If there are no existing tests, skip this step but mention it to the developer.
Phase 8: Final Instructions
After all changes are made and tests pass, tell the developer:
Migration complete! Here's how to test:
- Make sure your draft version with the "New OAuth Flow" toggle is "Active for me"
- Start your app's development server
- Trigger the OAuth flow (install/re-authorize your app on the account where the draft is active)
- Verify you receive both an access token and a refresh token
- Make an API call with the access token to confirm it works
- Wait for the token to expire (or decode the JWT to check the
exp claim), then verify the refresh logic kicks in
- Once everything works, promote the draft version to live
Key differences from the old flow to remember:
- Access tokens now expire โ your app must handle refresh
- Each refresh gives you a new refresh token โ always store the latest one
- The token endpoint changed from
/oauth2/token to /oauth_ms/oauth/token
Migration Summary Checklist
Before claiming completion, verify ALL of these: