At its core, OAuth ("Open Authorization") is a mechanism for applications to access the Asana API on behalf of a user, all without the application having access to the user's username and password. Instead, the application gets a token, which can be used in subsequent API calls through the addition of an Authorization header:

-H "Authorization: Bearer ACCESS_TOKEN"

In the above example, ACCESS_TOKEN should be replaced by the actual token received during the token exchange.

Most of the time, OAuth is the preferred method of authentication for developers, users, and Asana as a platform. If you are building a custom app, you should consider building a secure OAuth flow to authenticate users of your app.

📘

Quick reference

If you are already using an OAuth library and/or have a deep understanding of OAuth, you may wish to skip ahead to the quick reference below:

  • Asana supports the authorization code grant flow
  • Applications can be created from the developer console (i.e., "My apps")
  • The endpoint for user authorization is GET https://app.asana.com/-/oauth_authorize
  • The endpoint for token exchange is POST https://app.asana.com/-/oauth_token
  • The endpoint for revoking a token is POST https://app.asana.com/-/oauth_revoke
  • Once an access token (i.e., bearer token) has been obtained, your application can make API requests on behalf of the user

📘

Open source examples

You can access an open source examples for OAuth servers, provided in the following languages:

Feel free to follow the comments in the server files as you inspect the overall OAuth flow. Note: These OAuth servers should only be used for testing and learning purposes.

In addition to learning about how to use OAuth on the Asana platform (i.e., through this guide), feel free to review the official OAuth 2.0 specification.


Overview of the OAuth process

This section describes the overall OAuth process (i.e., with the authorization code grant flow, which is the most common).

📘

OAuth libraries

We recommend using a library (available in your language of choice) to handle the details of OAuth. Along with expediting development time, using a library can help mitigate the risk of security vulnerabilities due to inexperience or oversight.

As a prerequisite, ensure that you have registered an application with Asana. Take note of the application's client ID and the client secret (which should be protected as a password). Then, to begin:

  1. A user arrives at your application and clicks a button that says Authenticate with Asana (or Connect with Asana, etc.)

  2. The user is taken to the user authorization endpoint, which displays a page that asks the user if they would like to grant access to your third-party application

  3. If the user clicks Allow, they are redirected back to your application, bringing along a special code in the query string

  4. The application makes a request to the token exchange endpoint to exchange that code, along with the application's client secret, for two tokens:

    • An access token (i.e., a bearer token, which lasts an hour)
    • A refresh token (which can be used to fetch a new access token when the current one expires)
  5. Using this access token, the application can now make requests against the Asana API for the next hour

  6. 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. This can be repeated for as long as the user has authorized the application

📘

Authorization code grant

For additional details and a diagram of the authorization code grant flow, see Authorization Code Grant in the OAuth 2.0 specification.


Register an application

You must first register your application with Asana to receive a client ID and client secret. To do so, first visit the developer console and select Create new app, as shown below:

1237

To build a proper OAuth flow, you must supply your new application with three key details:

  1. App name - The name for your application. Users will see this name when your application requests permission to access their account as well as when they review the list of apps they have authorized.
The app name can be changed at any time in the **Basic information** tab in the sidebar of the [developer console](https://app.asana.com/0/my-apps)

The app name can be changed at any time in the Basic information tab in the sidebar of the developer console

  1. Redirect URL - Otherwise known as the callback URL, this is where the user will be redirected upon successful or failed authentication. Native or command line applications should use the special redirect URL urn:ietf:wg:oauth:2.0:oob. For security reasons, non-native applications must supply a "https" URL (more on this below).

❗️

Client secret

Your client secret is a secret, and it should never be shared with anyone or added into source code that others could gain access to. If you need to reset your client secret:

  1. Select your app in the developer console
  2. Navigate to the OAuth tab in the sidebar
  3. Select Reset next to your client secret
  1. (OAuth) Permission scopes: You'll need to specify which OAuth scopes your app can request during this registration step. Only these pre-approved scopes will be accepted later during user authorization.

You can always update the list of scopes later in the developer console (under OAuth > Permission scopes). Below is an example of what it looks like in the developer console. Note that you can either toggle Full permissions (which grants access to all endpoints) or manually choose a specific set of OAuth scopes for your app:

In the above example, the following OAuth scopes were chosen (we'll review OAuth scopes in more detail shortly in this guide):

  • projects:read
  • tasks:read
  • tasks:write
  • tasks:delete

Along with all the app registration information above, you can also upload an icon, a description, and other basic information about your application. All of these attributes can also be changed later as well.

Once you have created an app, the OAuth tab in the sidebar will include a client ID (needed to uniquely identify your app to the Asana API) as well as a client secret.


User authorization endpoint

During the user authorization step, the user is prompted by the application to either grant or deny the application to access to their account.

The API endpoint for user authorization is: GET https://app.asana.com/-/oauth_authorize

❗️

App distribution settings

In order for a user to be able to authorize the OAuth application via this user authorization endpoint, the application must be available in the user's workspace.

If you have not yet configured your application's distribution settings, visit the manage distribution documentation before moving forward.

Request

Here is an example of basic link that sends a user to https://app.asana.com/-/oauth_authorize:

<a
  href="https://app.asana.com/-/oauth_authorize
?client_id=753482910
&redirect_uri=https://my.app.com
&response_type=code
&state=thisIsARandomString
&code_challenge_method=S256
&code_challenge=671608a33392cee13585063953a86d396dffd15222d83ef958f43a2804ac7fb2
&scope=projects:read tasks:read tasks:write tasks:delete"
>
  Authenticate with Asana
</a>

This example uses scopes chosen during the app registration example above. The scope parameter is space-separated but must be URL-encoded in practice (i.e., spaces become %20).

By clicking on Authenticate with Asana link in the above example, the application redirects the user to https://app.asana.com/-/oauth_authorize, passing query parameters along as a standard query string:

Query parameterDescription
client_id (required)The client ID uniquely identifies the application making the request
redirect_uri (required)The URI to redirect to on success or error. This must match the redirect URL specified in the application settings
response_type (required)Must be either code or id_token, or the space-delimited combination: code id_token
state (required)Encodes state of the app, which will be returned verbatim in the response and can be used to match the response up to a given request
code_challenge_method (conditional)PKCE The hash method used to generate the challenge. This is typically S256
code_challenge (conditional)PKCE. The code challenge used for PKCE
scopeA space-delimited set of one or more scopes to get the user's permission to access. If no scopes are specified, the default OAuth scope will be used—provided the app was originally registered with Full permissions.

The resulting authentication screen will look something like this to the end-user:

OAuth scopes

🚧

Preview

OAuth permission scopes are in preview. More scopes will be added over time, and some details may change before the anticipated final release in July 2025. Use the Full Permissions toggle to opt out of the preview and get user full access.

Asana uses OAuth 2.0 for secure user authorization. Scopes allow your app to request only the access it needs, following the principle of least privilege. That is, OAuth scopes define which parts of the Asana API your app can access. This helps users trust your app and ensures their data is protected.

Each scope follows the format:

<resource>:<action>

Where:

  • <resource> refers to an object (e.g.,tasks, projects, users)
  • <action> is one of: read, write, or delete
    • These do not imply inheritance (e.g., write does not grant read access)

For example:

  • tasks:read – View task data
  • projects:write – Create or modify projects
  • users:read – Access basic user info

Related object fields in responses

Asana’s data model is graph-based, so many endpoints include nested related objects (e.g., a task object may include the assignee, which is a user object). By default, only the following fields are available for these related objects without additional scopes:

  • gid
  • name or title
  • resource_type
  • resource_subtype

Fields which are not in this list will be omitted. If you request any additional fields (using the opt_fields query parameter), your app must request the corresponding scope. For example:

GET /tasks/123?opt_fields=assignee.email

This request requires the users:read scope. Without it, you’ll receive a 403 Unauthorized error.

Request a subset of scopes

Previously, in the app registration example above, an app was registered with the following scopes: projects:read, tasks:read, tasks:write, and tasks:delete.

Asana's expanded scopes require explicit specification during the OAuth authorization step. Below is an example <a> tag that an app may use to initiate OAuth with scoped access to a subset of the registered scopes:

<a
  href="https://app.asana.com/-/oauth_authorize
?client_id=753482910
&redirect_uri=https://my.app.com
&response_type=code
&state=thisIsARandomString
&code_challenge_method=S256
&code_challenge=671608a33392cee13585063953a86d396dffd15222d83ef958f43a2804ac7fb2
&scope=projects:read"
>
  Authenticate with Asana
</a>

That is, through the app was registered with four distinct scopes, the above example shows how you can request a subset of it (i.e., in this case, the projects:read scope). Note that only scopes pre-approved during registration will be honored. Once authorized, the resulting access token will be limited to the scopes requested. To add scopes, users must reauthorize the app.

❗️

Scope registration required

If you request a scope that is not in your app's approved (i.e., registered) list, the request will be rejected.

You can update the scopes your app is allowed to request by editing your app's settings in the developer console. Visit the developer console, navigate to OAuth in the sidebar, then select your preferred scopes under Permission scopes.

📘

What scopes are available?

To view all scopes currently available view its documentation.

Testing in Postman

To quickly test with scopes:

  1. Use Asana's Postman Collection
  2. In the developer console, set your app's redirect URL to:
https://oauth.pstmn.io/v1/callback
  1. Under Manage Distribution on the left sidebar of the developer console, make sure that the Asana workspace for your user can access your app under Specific workspaces or choose Any workspace.
  1. In Postman, select Asana at the top level of the collection, then select the Authorization tab. Enter these settings (leave defaults for other settings):
  • Auth Type: OAuth 2.0
  • Grant Type: Authorization Code
  • Callback URL: https://oauth.pstmn.io/v1/callback
  • Auth URL: https://app.asana.com/-/oauth_authorize
  • Access Token URL: https://app.asana.com/-/oauth_token
  • Client ID: (enter your test ID)
  • Client Secret: (enter your secret from the Asana developer console)
  • Scope: (space separated list of scopes from the sheet above) (e.g., tasks:read tasks:write projects:read, no quotes)
  • State: (enter a random string like 1234)
  1. Select Get New Access Token
  2. The Asana consent screen should load if there is no error. Select Allow. You should be redirected back to Postman
  3. If successful, you should have a model with a new access token. You can optionally give the token a name under Token Name
    1. Select Use Token
    2. Select Save (if you don’t save, the token is there, but not used yet in your collection)

Now you should be able to use this call in Postman. Be sure that when looking at an endpoint, on the Authorization tab, Auth Type is set to Inherit auth from parent.


Response

If either the client_id or redirect_uri do not match, the user will simply see a plain-text error. Otherwise,
all errors will be sent back to the redirect_uri specified.

The user then sees a screen giving them the opportunity to accept or reject the request for authorization. In either case, the user will be redirected back to the redirect_uri.

Below is an example URL through which a user is redirected to the redirect_uri:

https://my.app.com?code=325797325&state=thisIsARandomString
Query parameterDescription
codeIf value of response_type is code in the authorizing request, this is the code your app can exchange for a token
stateThe state parameter that was sent with the authorizing request

🚧

Preventing CSRF attacks

The state parameter is necessary to prevent CSRF attacks. As such, you must check that the state is the same in this response as it was in the request. If the state parameter is not used, or not tied to the user's session, then attackers can initiate an OAuth flow themselves before tricking a user's browser into completing it. That is, users can unknowingly bind their accounts to an attacker account.

In terms of requirements, the state parameter must contain an unguessable value tied to the user's session, which can be the hash of something tied to their session when the OAuth flow is first initiated (e.g., their session cookie). This value is then passed back and forth between the client application and the OAuth service as a form of CSRF token for the client application.

For additional security resources, see:


Token exchange endpoint

The token exchange endpoint is used to exchange a code or refresh token for an access token.

The API endpoint for token exchange is: POST https://app.asana.com/-/oauth_token

Request

When your app receives a code from the authorization endpoint, it can now be exchanged for a proper token. At this point, your app should make a POST request to https://app.asana.com/-/oauth_token, passing the parameters as part of a standard form-encoded POST body (i.e., passing in the data into a request with header 'Content-Type: application/x-www-form-urlencoded')

Below is an example request body in a POST request to https://app.asana.com/-/oauth_token and an example cURL call:

{
  "grant_type": "authorization_code",
  "client_id": "753482910",
  "client_secret": "6572195638271537892521",
  "redirect_uri": "https://my.app.com",
  "code": "325797325",
  "code_verifier": "fdsuiafhjbkewbfnmdxzvbuicxlhkvnemwavx"
}
curl --location 'https://app.asana.com/-/oauth_token' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'grant_type=refresh_token' \
--data-urlencode 'refresh_token=<YOUR_REFRESH_TOKEN>' \
--data-urlencode 'client_id=<YOUR_CLIENT_ID>' \
--data-urlencode 'client_secret=<YOUR_CLIENT_SECRET>'

🚧

Sending the client_secret

If you have a client_secret, this request should be sent from your secure server. The browser should not see your client_secret (i.e., the client_secret should never be exposed in client-side code).

Details of each parameter are described below:

ParameterDescription
grant_type (required)One of authorization_code or refresh_token (see below for more details)
client_id (required)The client ID uniquely identifies the application making the request
client_secret (required)The client secret belonging to the app, found in the Basic information tab of the developer console
redirect_uri (required)Must match the redirect_uri specified in the original request
code (required)This is the code you are exchanging for an authorization token
refresh_token (conditional)If the value of grant_type is refresh_token, this is the refresh token you are using to be granted a new access token
code_verifierThis is the string previously used to generate the code_challenge.

Response

In the response to the request above, you will receive a JSON object similar to the example below:

{
  "access_token": "f6ds7fdsa69ags7ag9sd5a",
  "expires_in": 3600,
  "token_type": "bearer",
  "data": {
    "id": 4673218951,
    "gid": "4673218951",
    "name": "Greg Sanchez",
    "email": "[email protected]"
  },
  "refresh_token": "hjkl325hjkl4325hj4kl32fjds"
}

Details of each property are described below:

PropertyDescription
access_tokenThe token to use in future requests against the API. This token is only valid for the scopes requested during user authorization. To gain additional permissions, the user must reauthorize the app with an updated set of scopes.
expires_inThe number of seconds that the token is valid, typically 3600 (one hour)
token_typeThe type of token (in our case, bearer)
refresh_tokenIf exchanging a code, this is a long-lived token that can be used to get new access tokens when older ones expire
dataAn object encoding a few key fields about the logged-in user. Currently, this is the user's id, gid, name, and email

Token expiration

When an access (bearer) token has expired, you'll see the following error when using such a token in an API request:

The bearer token has expired. If you have a refresh token, please use it to request a new bearer token, otherwise allow the user to re-authenticate.

You can get a new access token by having your application make a POST request back to the token exchange endpoint using a grant type of "refresh_token". In the same request you must also pass in your long-lived refresh_token from the original token exchange request.

🚧

Note on token format

Asana API tokens should be treated as opaque. Token formats may change without notice. Validating a token’s format on the client side could result in unexpected breakages. This applies to any Asana API tokens, including: personal access tokens, service account tokens, and both OAuth refresh tokens and access tokens.


Token deauthorization endpoint

An authorization token can be deauthorized or invalidated (i.e., revoked) by making a request to Asana's API.

The endpoint for revoking a token is: POST https://app.asana.com/-/oauth_revoke

Request

Your app should make a POST request to https://app.asana.com/-/oauth_revoke, passing the following parameters as part of a standard form-encoded POST body:

ParameterDescription
client_id (required)The client ID uniquely identifies the application making the request.
client_secret (required)The client secret belonging to the app, found in the details pane of the developer console
token (required)The refresh token that should be deauthorized. Access tokens (i.e., bearer tokens) will be rejected

The body should include a valid refresh token, which will cause the refresh token and any associated bearer tokens to be deauthorized. Bearer tokens are not accepted in the request body since a new bearer token can always be obtained by reusing an authorized refresh token.

Response

A successful response with a 200 status code indicates that the token was deauthorized or not found. An unsuccessful response with a 400 status code will be returned if the request was malformed due to missing any required fields or due to an invalid token (such as a bearer token).


Security considerations

PKCE OAuth extension

PKCE (proof key for code exchange) proves the app that started the authorization flow is the same app that finishes the flow. You can read more about it here: Protecting Apps with PKCE.

In short, the process is as follows:

  1. Whenever a user wants to OAuth with Asana, your app server should generate a random string called the code_verifier. This string should be saved to the user, since every code_verifier should be unique per user. This should stay in the app server and only be sent to the token exchange endpoint.
  2. Your app server will hash the code_verifier with SHA256 to get a string called the code_challenge. Your server will give the browser only the code_challenge & code_challenge_method. The code_challenge_method should be the string "S256" to tell Asana we hashed with SHA256. More specifically, code_challenge = BASE64URL-ENCODE(SHA256(ASCII(code_verifier))).
  3. The browser includes code_challenge & code_challenge_method when redirecting to the user authorization endpoint:
{
  ...

  "code_challenge": "671608a33392cee13585063953a86d396dffd15222d83ef958f43a2804ac7fb2",
  "code_challenge_method": "S256"
}
  1. The app server should include the code_verifier in its request to the token exchange endpoint.

Asana confirms that hashing the code_verifier with the code_challenge_method results in the code_challenge. This proves to Asana the app that hit the user authorization endpoint is the same app that hit the token exchange endpoint:

{
  ...

  "code_verifier": "fdsuiafhjbkewbfnmdxzvbuicxlhkvnemwavx"
}

Secure redirect endpoint

As the redirect from the authorization endpoint in either grant procedure contains a code that is secret between Asana's authorization servers and your application, this response should not occur in plaintext over an unencrypted http connection. Because of this, we enforce the use of https redirect endpoints for application registrations.

For non-production or personal use, you may wish to check out stunnel, which can act as a proxy to receive an encrypted connection, decrypt it, and forward it on to your application. For development work, you may wish to create a self-signed SSL/TLS certificate for use with your web server; for production work we recommend purchasing a certificate from a certificate authority. You may review a short summary of the steps for each of these processes here.

Your application will need to be configured to accept SSL/TLS connections for your redirect endpoint when users become authenticated. For many apps, this will simply require a configuration update of your application server. Instructions for Apache and Nginx can be found on their respective websites, and most popular application servers will contain documentation on how to proceed.


Asana Home
Asana helps you manage projects, focus on what's important, and organize work in one place for seamless collaboration.
© 2023 Asana, Inc.