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 example

You can access an open source example OAuth server (written in Node.js) here in the devrel-examples repository. Feel free to follow the comments in the ./index.js file as you inspect the overall OAuth flow.

Note that this OAuth server 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 two 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).

Along with the 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.

❗️

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

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.

πŸ“˜

API endpoint

The 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 sending a user to https://app.asana.com/-/oauth_authorize via a basic link:

<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=default"
  >Authenticate with Asana</a
>

By clicking on Authenticate with Asana 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 is used

OAuth scopes

The Asana API supports a small set of OAuth scopes you can request using the scope parameter during this user authorization step of your authentication flow. Multiple scopes can be requested at once as a space-delimited list of scopes. An exhaustive list of the supported scopes is provided here:

ScopeAccess provided
defaultProvides access to all endpoints documented in our API reference. If no scopes are requested, this scope is assumed by default.
openidProvides access to OpenID Connect ID tokens and the OpenID Connect user info endpoint.
emailProvides access to the user's email through the OpenID Connect user info endpoint.
profileProvides access to the user's name and profile photo through the OpenID Connect user info endpoint.

πŸ“˜

OpenID Connect

For more information about the OpenID Connect and the openid scope, view its documentation.

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.

πŸ“˜

API endpoint

The 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
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.

πŸ“˜

API endpoint

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.