OAuth
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:
-
A user arrives at your application and clicks a button that says Authenticate with Asana (or Connect with Asana, etc.)
-
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
-
If the user clicks Allow, they are redirected back to your application, bringing along a special
code
in the query string -
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)
-
Using this access token, the application can now make requests against the Asana API for the next hour
-
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:
To build a proper OAuth flow, you must supply your new application with two key details:
- 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.
- 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:
- Select your app in the developer console
- Navigate to the OAuth tab in the sidebar
- 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 parameter | Description |
---|---|
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 |
scope | A 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:
Scope | Access provided |
---|---|
default | Provides access to all endpoints documented in our API reference. If no scopes are requested, this scope is assumed by default. |
openid | Provides access to OpenID Connect ID tokens and the OpenID Connect user info endpoint. |
email | Provides access to the user's email through the OpenID Connect user info endpoint. |
profile | Provides 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 parameter | Description |
---|---|
code | If value of response_type is code in the authorizing request, this is the code your app can exchange for a token |
state | The 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 thestate
is the same in this response as it was in the request. If thestate
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:
- OAuth 2.0 Security Best Current Practice
- Prevent Attacks and Redirect Users with OAuth 2.0 State Parameters
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 yourclient_secret
(i.e., theclient_secret
should never be exposed in client-side code).
Details of each parameter are described below:
Parameter | Description |
---|---|
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_verifier | This 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:
Property | Description |
---|---|
access_token | The token to use in future requests against the API |
expires_in | The number of seconds that the token is valid, typically 3600 (one hour) |
token_type | The type of token (in our case, bearer ) |
refresh_token | If exchanging a code , this is a long-lived token that can be used to get new access tokens when older ones expire |
data | An 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:
Parameter | Description |
---|---|
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:
- 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 everycode_verifier
should be unique per user. This should stay in the app server and only be sent to the token exchange endpoint. - Your app server will hash the
code_verifier
with SHA256 to get a string called thecode_challenge
. Your server will give the browser only thecode_challenge
&code_challenge_method
. Thecode_challenge_method
should be the string "S256" to tell Asana we hashed with SHA256. More specifically,code_challenge = BASE64URL-ENCODE(SHA256(ASCII(code_verifier)))
. - The browser includes
code_challenge
&code_challenge_method
when redirecting to the user authorization endpoint:
{
...
"code_challenge": "671608a33392cee13585063953a86d396dffd15222d83ef958f43a2804ac7fb2",
"code_challenge_method": "S256"
}
- 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.
Updated about 1 month ago