Migrating to the new OAuth 2.1 flow

Migrate your monday app from the legacy OAuth flow to the new OAuth 2.1 flow with expiring tokens, refresh tokens, and token revocation.

The new OAuth 2.1 flow introduces several important changes to how your app handles authentication tokens. This guide covers only what you need to change in your existing integration - the authorization request (step 1 of the OAuth flow) remains unchanged.

The key differences in the new flow are:

  • Access tokens now expire - you must handle token expiration and refresh.
  • Refresh tokens - use them to obtain new access tokens without user re-authorization.
  • Token revocation - explicitly revoke tokens when they are no longer needed.
  • New token endpoint URL - token exchange uses a different endpoint.
📘

New to OAuth?

If you haven't implemented OAuth yet, start with the OAuth and Permissions guide first, then return here to use the new flow.

1. Enable the new OAuth flow

Before migrating, enable the new OAuth flow for your app version in the Developer Center:

  1. Go to your app in the Developer Center.
  2. Navigate to the OAuth & Permissions tab.
  3. Create a new draft version of your app. The toggle is per version.
  4. Enable the New OAuth Flow toggle for the draft version.
  5. Test the flow using the draft version by setting it as Active for me.
  6. After verifying the flow works, promote the draft version to live.
Enable the New OAuth flow toggle in the OAuth & permissions tab of the Developer Center

The New OAuth flow toggle in the OAuth & permissions tab

2. Update your token endpoint URL

The authorize endpoint stays unchanged:

GET https://auth.monday.com/oauth2/authorize

It now supports PKCE, which adds the following query parameters:

ParameterDescriptionRequired
code_challengeA PKCE code challenge (a base64url-encoded SHA-256 hash of the code_verifier). Must be 43–128 characters. See the PKCE section below.Required
code_challenge_methodThe method used to generate the code challenge. Only S256 is supported.Required

The token exchange endpoint has changed:

Old flowNew flow
Token URLPOST https://auth.monday.com/oauth2/tokenPOST https://auth.monday.com/oauth_ms/oauth/token

Update your backend code to use the new URL when exchanging authorization codes for tokens.

To support PKCE, the token exchange request accepts an additional parameter:

ParameterDescriptionRequired
code_verifierThe original PKCE code verifier string (43–128 characters). Required if code_challenge was sent during authorization. See the PKCE section below.Required

Updated token exchange request

curl -X POST https://auth.monday.com/oauth_ms/oauth/token \
  -H "Content-Type: application/json" \
  -d '{
    "grant_type": "authorization_code",
    "client_id": "YOUR_CLIENT_ID",
    "client_secret": "YOUR_CLIENT_SECRET",
    "code": "AUTHORIZATION_CODE",
    "redirect_uri": "https://yourapp.com/callback"
  }'
📘

Redirect URI

The redirect_uri parameter is required in the token exchange request if it was provided in the authorization request.

Updated token exchange response

The response now includes a refresh_token, and the access token has an expiration:

{
  "access_token": "eyJhbGciOi...",
  "refresh_token": "eyJhbGciOi...",
  "token_type": "Bearer",
  "scope": "boards:read users:read"
}
KeyChange from old flow
access_tokenNow expires. Decode the JWT to check the exp field for the expiration timestamp. Previously, tokens were valid until the user uninstalled your app.
refresh_tokenNew. Use this to obtain a new access token when the current one expires.
token_typeNo change. Always Bearer.
scopeA space-separated list of granted scopes.

3. Implement token refresh

Since access tokens now expire, your app must refresh them using the refresh token. This does not require user interaction.

POST https://auth.monday.com/oauth_ms/oauth/token

curl -X POST https://auth.monday.com/oauth_ms/oauth/token \
  -H "Content-Type: application/json" \
  -d '{
    "grant_type": "refresh_token",
    "client_id": "YOUR_CLIENT_ID",
    "client_secret": "YOUR_CLIENT_SECRET",
    "refresh_token": "YOUR_REFRESH_TOKEN"
  }'

The response returns a new access token and a new refresh token:

{
  "access_token": "eyJhbGciOi...",
  "refresh_token": "eyJhbGciOi...",
  "token_type": "Bearer",
  "scope": "boards:read users:read"
}
🚧

Store the latest refresh token

Each refresh returns a new refresh_token. Always store the latest refresh token and discard the old one.

Recommended refresh strategy

  • Decode the access_token JWT and read the exp claim to know when it expires.
  • Refresh the token proactively before it expires-for example, when less than 5 minutes remain.
  • If an API call returns a 401 Unauthorized, attempt a token refresh and retry the request.

4. Revoke tokens

You can now explicitly revoke tokens when they are no longer needed-for example, when a user disconnects your app.

POST https://auth.monday.com/oauth_ms/oauth/revoke

curl -X POST https://auth.monday.com/oauth_ms/oauth/revoke \
  -H "Content-Type: application/json" \
  -d '{
    "token": "TOKEN_TO_REVOKE",
    "client_id": "YOUR_CLIENT_ID",
    "client_secret": "YOUR_CLIENT_SECRET",
    "token_type_hint": "access_token"
  }'
ParameterDescriptionRequired
tokenThe access token or refresh token to revoke. Depends on token_type_hint.Yes
client_idYour app's unique identifier.Yes
client_secretYour app's secret.Yes
token_type_hintEither access_token or refresh_token. Helps the server identify the token type.No

On success, the response contains:

{
  "success": true
}

PKCE (Proof Key for Code Exchange)

PKCE protects the authorization code flow from interception attacks, especially for public clients such as mobile apps, single-page apps, and CLI tools. It is required for all apps using the new OAuth flow.

1. Generate a code verifier

Create a cryptographically random string between 43 and 128 characters using unreserved characters ([A-Z] / [a-z] / [0-9] / - / . / _ / ~).

const crypto = require("crypto");
const codeVerifier = crypto.randomBytes(32).toString("base64url");

2. Compute the code challenge

Hash the verifier with SHA-256 and base64url-encode the result:

const codeChallenge = crypto
  .createHash("sha256")
  .update(codeVerifier)
  .digest("base64url");

3. Include the challenge in the authorization request

GET https://auth.monday.com/oauth2/authorize?client_id=YOUR_CLIENT_ID&code_challenge=CODE_CHALLENGE&code_challenge_method=S256

4. Include the verifier in the token exchange

curl -X POST https://auth.monday.com/oauth_ms/oauth/token \
  -H "Content-Type: application/json" \
  -d '{
    "grant_type": "authorization_code",
    "client_id": "YOUR_CLIENT_ID",
    "client_secret": "YOUR_CLIENT_SECRET",
    "code": "AUTHORIZATION_CODE",
    "code_verifier": "YOUR_CODE_VERIFIER"
  }'

The server verifies that SHA256(code_verifier) matches the code_challenge sent during authorization. If they don't match, the token exchange is rejected.

🚧

Only S256 is supported

Only S256 is supported as the challenge method. Plain code challenges are not accepted.

What stays the same

The following parts of the OAuth flow are unchanged and do not require any modifications:

  • Authorization URL: GET https://auth.monday.com/oauth2/authorize (same URL, same parameters).
  • Permission scopes: Same scope definitions and configuration in the Developer Center.
  • Redirect URLs: Same configuration and validation rules.
  • Redirect callback: Same parameters-code, state, and status. Check status to confirm the user approved the authorization.
  • Authorization code: Still valid for 10 minutes, with the same exchange process (just a different endpoint URL).
  • Client credentials: Same client_id and client_secret from the Developer Center.
📘

Join our developer community!

We've created a community specifically for our devs where you can search through previous topics to find solutions, ask new questions, hear about new features and updates, and learn tips and tricks from other devs. Come join in on the fun! 😎