Authentication
cyber-asana accepts either a personal access token or an OAuth grant obtained through
auth login.
Credential sources
Section titled “Credential sources”Resolved in this order — the first one found wins:
--token <token>on the commandASANA_ACCESS_TOKENenvironment variableASANA_TOKENenvironment variable- The stored OAuth credential from
cyber-asana auth login
A personal access token is the simplest start — create one at app.asana.com → My Apps:
export ASANA_ACCESS_TOKEN="<pat>"export ASANA_WORKSPACE="<workspace-gid>" # optional, saves --workspace-gid everywhereChecking what is in use
Section titled “Checking what is in use”cyber-asana auth statusReports whether you are authenticated, which source won, the masked token, the account it belongs to, expiry if any, and which other credentials were shadowed — all without calling the Asana API. When nothing is configured it prints the two ways to fix that.
It also reports the app registration auth login would authorize with, which resolves from
a separate chain — so a token that works says nothing about
whether the registration is the right one:
Status authenticatedSource ASANA_TOKENToken …bbbbApp …cdef (ASANA_API_CLIENT_ID)App ignored ASANA_CLIENT_IDApp names the masked client id and the source it came from; App ignored lists the
registrations that source shadowed. With --json (or --toon) the same appears as an app
object, null when no registration resolves:
{ "app": { "client_id_masked": "…cdef", "source": "ASANA_API_CLIENT_ID", "shadowed": ["ASANA_CLIENT_ID"] }}OAuth login
Section titled “OAuth login”cyber-asana auth logincyber-asana auth login --no-store --rawcyber-asana auth logoutlogin opens the browser, listens on a loopback callback, and stores the credential
locally. It runs against your own Asana app, which must be registered as an API app —
tokens from an MCP app work only with Asana’s hosted MCP server.
export ASANA_API_CLIENT_ID="<client-id>"export ASANA_API_CLIENT_SECRET="<client-secret>"| Option | Command | Description |
|---|---|---|
--client-id <id> |
status, login, token, logout |
OAuth client ID — overrides ASANA_API_CLIENT_ID, ASANA_CLIENT_ID, and settings.json |
--client-secret <secret> |
status, login, token, logout |
OAuth client secret (visible in shell history — prefer the env var) |
--no-store |
login |
Print the token instead of saving it |
--include-refresh-token |
login |
Also print the long-lived refresh token (implies --no-store) |
--raw |
login |
Print only the token, for shell substitution |
--scope <list> |
login |
Comma-separated scopes to request |
--manual |
login |
Paste the code from the browser instead of listening for a redirect |
--redirect-uri <uri> |
login |
Redirect URL registered for the app (default: the loopback URL) |
--port <port> |
login |
Callback port |
--local |
logout |
Delete local credentials without revoking the grant |
Use --manual when you are on a remote or headless machine where the browser cannot reach
the loopback listener.
App registration sources
Section titled “App registration sources”The client id and secret resolve independently, in this order:
--client-id/--client-secreton the commandASANA_API_CLIENT_ID/ASANA_API_CLIENT_SECRETenvironment variablesASANA_CLIENT_ID/ASANA_CLIENT_SECRETenvironment variablesclient_id/client_secretin~/.config/cyber-asana/settings.json
ASANA_API_CLIENT_ID and ASANA_API_CLIENT_SECRET are names cyber-asana defines — they are
not official Asana variables. They exist because Asana’s docs use ASANA_CLIENT_ID /
ASANA_CLIENT_SECRET for the hosted MCP server’s MCP app, whose credentials do not work
here; the prefixed pair lets both registrations coexist. ASANA_CLIENT_ID /
ASANA_CLIENT_SECRET still work as a fallback.
Run auth status to see which of these won and which were
shadowed. If an MCP app’s id reaches the token endpoint anyway, Asana answers with
invalid_client, and cyber-asana appends a hint naming this distinction to Asana’s own
message.
Printing the stored token
Section titled “Printing the stored token”cyber-asana auth tokenPrints the stored access token, refreshing it first when it is close to expiring. Useful for handing a fresh token to another tool:
curl -H "Authorization: Bearer $(cyber-asana auth token)" \ https://app.asana.com/api/1.0/users/me