Asana
asana.com ↗OAuth is self-serve: create an app in the developer console, register scopes, and run the authorization-code flow in an afternoon. The one real gate is Manage distribution — leave 'Specific workspaces' empty and every user hits 'This app is not available to your Asana workspace'.
01Pick your path
02Time & effort
- DEVCreate app + register scopes10–20 minutessourced[S1][S6]You must be signed in to the developer console; an agent cannot click Create new app for you.
- DEVImplement code + PKCE flow1–3 hourssourced[S1][S9]An OAuth library plus the documented endpoints; the agent can write all of this once it has client ID/secret.
- DEVTest consent in your workspace10–30 minutessourced[S1][S6]Consent only renders if the app is available in the user's workspace via Manage distribution.
- PRODHarden: refresh, 429s, per-user tokens1–4 hourssourced[S1][S3]Access token is 1 hour; rate limits are per token, so one integration instance = one token bucket.
- PRODPublic listing review (optional)wait 0–4 weeksinferred[S4][S6]Sources describe the App Directory as a place to publish apps but state no review timeline or requirements; 2-4 weeks is our estimate, not documented. If you only need your own workspaces, skip this phase entirely.
03Steps
Get a working connection
Developer console → My apps → Create new app. Pick the standard API app type (MCP apps issue tokens that only work against the MCP server). Fill app name, the exact https redirect URL, and your initial scope set. Changing the name, icon, or scopes later is allowed, but the redirect URL must match byte-for-byte at authorize time.
“To build a proper OAuth flow, you must supply your new application with three key details:” — [S1] verified verbatim
◆ prepared by agent · App registration checklistApp name: <agent name> — <one-line purpose, e.g. 'drafts and files status updates in Asana'>. Redirect URL: https://<your-host>/callback (native/CLI: urn:ietf:wg:oauth:2.0:oob) App type: standard API app (not 'MCP app' unless you only call mcp.asana.com/v2/mcp). Permission scopes: tasks:read, tasks:write, projects:read, stories:write, users:read Distribution: Specific workspaces → <your test workspace> until the flow works.
Build the URL against https://app.asana.com/-/oauth_authorize with client_id, redirect_uri, response_type=code, state, code_challenge/code_challenge_method=S256, and a space-delimited scope list that is URL-encoded (spaces → %20). Verify state on the callback before doing anything with the code.
“The scope parameter is space-separated but must be URL-encoded in practice (i.e., spaces become %20 ).” — [S1] verified verbatim
◆ prepared by agent · Authorize URL with PKCE + narrow scopesconst params = new URLSearchParams({ client_id: process.env.ASANA_CLIENT_ID, redirect_uri: 'https://your-host/callback', response_type: 'code', state, code_challenge_method: 'S256', code_challenge, scope: 'tasks:read tasks:write projects:read stories:write users:read', }); // Asana wants %20 for spaces, not '+' const url = 'https://app.asana.com/-/oauth_authorize?' + params.toString().replace(/\+/g, '%20');Start from read scopes, add write only for actions the agent actually performs, and check each one against the scope→endpoint table so you do not register a scope you never call. Remember write does not imply read. Note which features need the app-level Full permissions toggle because their endpoint has no scope.
“Scopes allow your app to request only the access it needs, following the principle of least privilege.” — [S1] verified verbatim
◆ prepared by agent · Scope justification draft (consent screen / review)We ask for: tasks:read + projects:read (find the work you point us at), tasks:write (create and update tasks on your behalf), stories:write (post the comment/status we draft — we label these as written by the app), users:read (assign work to the right teammate). We do not request delete scopes. We store an hourly access token plus a refresh token server-side and revoke on disconnect.
POST the code with client_id and client_secret to https://app.asana.com/-/oauth_token from your backend. Store the access token (1 hour) and the refresh token; re-run the exchange with the refresh token instead of prompting the user again. Revoke at https://app.asana.com/-/oauth_revoke on disconnect.
“Once the access token expires, the application can use the token exchange endpoint again (i.e., without user intervention) to exchange the refresh token for a new access token.” — [S1] verified verbatim
In the developer console → Manage distribution, choose 'Specific workspaces' and tick your test workspace (or 'Any workspace'). Leaving 'Specific workspaces' empty produces 'This app is not available to your Asana workspace or organization.' on the consent screen. Then run a full authorize → allow → callback → GET /users/me loop.
“If you choose "Specific workspaces" but don't select any workspaces, users will see an error saying "This app is not available to your Asana workspace or organization."” — [S6] verified verbatim
Make it production-ready
Refresh proactively before expiry. Respect Retry-After on 429s, keep in-flight requests ≤50 GET / ≤15 writes, use limit=100 and opt_fields to reduce cost, and give each user or integration instance its own token so quotas do not stack into one bucket.
Poll the Events API rather than standing up a webhook receiver unless real-time is a hard requirement — Asana's own guidance says to prefer polling and calls webhooks noisy, unreliable, and needing a backup mechanism. If you do add webhooks, budget a fallback poller.
“Avoid using webhooks unless reacting to events in real time is a strict requirement for your app.” — [S7] verified verbatim
Flip Manage distribution to 'Any workspace' when you are ready for users outside your org. If you also want an App Directory listing, that is a separate submission whose requirements and turnaround are not documented in these sources — plan for a multi-week wait and have the scope justification ready.
04Requirements, scopes & nuances
You'll need
- ▸An Asana account with access to the developer console and at least one workspace you can use for testing. inferred[S1]
- ▸A registered app in the developer console, giving you a client ID and a client secret (the secret must stay server-side). sourced ❝[S1]
- ▸An https redirect URL (non-native apps); native or CLI apps may use urn:ietf:wg:oauth:2.0:oob instead. sourced ❝[S1]
- ▸Every scope you plan to request listed under OAuth → Permission scopes before the first authorization attempt. sourced ❝[S1]
- ▸The target workspace added under Manage distribution ('Specific workspaces') or 'Any workspace' selected. sourced ❝[S6]
- ▸A server-side token exchange — Asana does not support token requests from a browser. sourced ❝[S9]
Gotchas
- !Registration happens in two places people forget: the OAuth tab (client ID/secret, redirect URL, scopes) and Manage distribution (which workspaces may authorize). Skipping the second one makes a perfectly correct flow fail at the consent screen. sourced ❝[S6]
- !Scopes are pre-approved at registration time; an unregistered scope is not a warning, it is a hard forbidden_scopes error for the end user. inferred[S1]
- !Scopes do not cover every endpoint, and the scope list includes no 'workspaces:write', no search-specific scope, and no batch scope — the fallback is the app-level Full permissions toggle, which grants access to all endpoints and must be justified to users. sourced ❝[S1][S8]
- !write does not imply read (write does not grant read access). An agent that must read a task then comment on it needs tasks:read AND stories:write, not just a write scope. sourced ❝[S1][S8]
- !Access tokens last one hour. If you do not persist and use the refresh token, users re-consent hourly — the single most common 'it worked in Postman' failure. inferred[S1]
- !Rate limits are per authorization token (150 req/min free, 1,500 paid, 50 concurrent GET / 15 writes). Funnelling all users through one PAT shares one bucket; per-user OAuth tokens each get their own. inferred[S3]
- !There is no OIDC discovery document at /.well-known/openid-configuration (404). Do not build a generic OIDC client that depends on it; use the oauth-authorization-server metadata or hardcode the documented endpoints. inferred
| Scope / permission | Tier | Note | Evidence |
|---|---|---|---|
| tasks:read | basic | GET /tasks, /tasks/{gid}, /projects/{gid}/tasks, subtasks, search. Start here; add write only if the agent mutates. | sourced ❝[S8] |
| tasks:write | sensitive | Create/update/duplicate tasks, set parents, add projects/tags/followers. This is the core 'agent does work for you' scope. | sourced ❝[S8] |
| stories:write | sensitive | POST /tasks/{task_gid}/stories — i.e. commenting as the user. Users notice this one; justify it or offer read-only mode. | sourced ❝[S8] |
| projects:read | basic | List projects and a project's tasks so the agent can resolve names to gids before acting. | sourced ❝[S8] |
| users:read | basic | Resolve assignees and get /users/me. Needed for almost any 'assign this to…' feature. | sourced ❝[S8] |
| webhooks:write | sensitive | Only if the agent must react in real time; Asana's own guidance is to prefer polling unless real-time is a strict requirement. | inferred[S8][S7] |
| tasks:delete / projects:delete | restricted | Destructive and irreversible from the API. Request only behind an explicit user action, never at first connect. | inferred[S8] |
| Full permissions (app-level toggle) | gated | Not a scope — a console toggle under OAuth > Permission scopes that grants access to all endpoints, including ones with no scope defined. If you need it, say why on the consent screen; a reviewer-grade justification is in the step artifact. Asana does not publish scope sensitivity tiers; basic/sensitive/restricted here are our classification. | sourced ❝[S1][S8] |
| openid, email, profile | basic | Only if you use Asana as a login identity via OIDC, not for API access. | sourced ❝[S8][S2] |
05If you get blocked
→ Open the app in the developer console → OAuth → Permission scopes, add the scope, save, and rebuild the authorize URL with the same scope string.
→ Developer console → Manage distribution → select 'Any workspace', or 'Specific workspaces' plus at least one workspace, then Save changes.
→ Byte-for-byte match the redirect_uri to the value saved in the app's OAuth tab; non-native apps must use https.
→ Move the token exchange behind your backend (or use the OOB redirect for a native/CLI client).
→ Remove the scope parameter — MCP apps take no scopes — and remember MCP tokens cannot call the standard REST API; create a separate API app for that.
06Evidence: what was tested, sourced, and still uncertain
Sources read
- [S1] OAuth official
- [S2] Authentication official
- [S3] Rate limits official
- [S4] Asana Docs official
- [S5] Webhooks official
- [S6] Integrating with Asana's MCP Server official
- [S7] Building the app official
- [S8] OAuth scopes official
- [S9] OAuth Code Samples / Libraries official
- [composio] Composio — Managed OAuth apps directory official
- [probe] Live HTTP probes run by the agent (dummy client_id, no credentials) probe
Still uncertain
- ? No source states App Directory review requirements, timeline, or whether 'Any workspace' distribution alone is enough for a public commercial app. The 2-4 week figure is our estimate.
- ? Asana publishes no scope sensitivity tiers. The basic/sensitive/restricted labels here are our judgement based on what each scope lets an app do (read, mutate, comment, delete), not provider guidance.
- ? Discovery metadata returned scopes_supported=n/a, so scopes cannot be discovered programmatically — they must be read from the docs and hardcoded. We could not verify whether metadata lists grant types (probe output truncated).
- ? The probes could not confirm client-ID validation behaviour beyond 'HTML returns 200'; the invalid-client error text was not readable, so we cannot say exactly what a developer sees with a mistyped client_id.
- ? Timings for 'create app' and 'implement flow' are inferred from typical effort, not measured.
- ? Service Account (org-wide) is gated to Enterprise and creatable only by Asana super admins; we did not verify the commercial or contractual requirements.
- ? Generated automatically; no app was registered and no review was submitted.