OAuth 2.0 clients with their client security type set to confidential are suitable for Power-Ups with a backend component or non-Power-Up Trello apps. They are more secure, but you won't be able to take advantage of the Power-Up Client Library's OAuth 2.0 helper functions.
This means the under-the-hood details like PKCE and the authorization code exchange will be things you'll have to handle yourself inside your application. See our OAuth 2.0 client configuration page for more information about public vs confidential OAuth 2.0 clients.
Additionally, if you've used Atlassian's OAuth 2.0 (3LO) before, Trello's OAuth 2.0 is similar, save for a few key differences:
We've created an example confidential client app for you to clone and learn about how to build with Trello OAuth 2.0. Note that this example app is not a Power-Up, but its concepts also apply to Power-Ups using a confidential OAuth 2.0 client. Follow the instructions in the project's README to get started!
The example project follows these general steps to authorize users with OAuth 2.0:
You can also use OAuth 2.0 libraries to handle authorization for Trello OAuth 2.0. These libraries may ask for a authorization URL and a token endpoint url. The values for these are the following:
You'll need to first construct the authorization URL to direct your users to the OAuth 2.0 consent screen, in order to generate the authorization code. Combine the authorization base url, https://auth.atlassian.com/authorize with the relevant query parameters:
| Field | Description |
|---|---|
| client_id | The ID of your OAuth 2.0 client. |
| scope | Your desired scopes, space separated. Besides the special scope offline_access, the scopes you pass in must match the ones set in your app's OAuth 2.0 client configuration. You must pass in offline_access to receive a refresh token in this flow. For example: "read:member:trello read:board:trello offline_access". |
| redirect_uri | The URL of the page that will be redirected to after the user presses "Allow". This should match one of the callback URLs set in your OAuth 2.0 client configuration. See this page for more information. |
| response_type | This must be set to "code". |
| prompt | This must be set to "consent". |
| code_challenge | The PKCE code challenge. |
| code_challenge_method | This should be set to "S256". |
Here is how the example project sets that up, in authorization.ts:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17// in the server's /auth route handler (handleAuthorRoute.ts) export const handleAuthRoute = (req: Request, res: Response) => { const params = { client_id: CLIENT_ID, scope: req.query.scopes.toString(), // e.g. "read:member:trello read:board:trello offline_access" redirect_uri: REDIRECT_URI, // e.g. "https://foo-bar.ngrok.dev/authorize-redirect.html" response_type: "code", prompt: "consent", code_challenge: req.query.codeChallenge.toString(), code_challenge_method: "S256", }; const queryString = new URLSearchParams(params).toString(); const url = `https://auth.atlassian.com/authorize?${queryString}`; return res.redirect(url); };
When the user presses "Allow" in the consent screen after clicking on your authorization URL, they will be redirected to the redirect_uri that you passed in. This page will receive the authorization code via a URL parameter called code, for example: https://foo-bar.ngrok.dev/authorize-redirect.html?code=...
You must take that code and exchange it for your access and refresh tokens. The exchange happens by hitting https://auth.atlassian.com/authorize/oauth/token with the correct payload:
| Field | Description |
|---|---|
| client_id | The ID of your OAuth 2.0 client. |
| client_secret | The secret of your OAuth 2.0 client. Your client must be set to confidential to have a client secret. Keep this value safe and do not expose it to your users. |
| grant_type | This must be set to "authorization_code". |
| redirect_uri | The URL of the page that will be redirected to after the user presses "Allow". This should match one of the callback URLs set in your OAuth 2.0 client configuration. |
| code | The authorization code obtained from the initial authorize call. |
| code_verifier | The PKCE code verifier. This must be generated along with the initial code challenge. |
If the exchange is successful, you will be returned the following in the response:
| Field | Description |
|---|---|
| access_token | An access token which can be used to hit the Trello API. By default, it expires after 1 hour. |
| refresh_token | A refresh token which can be exchanged for a new access token and refresh token. By default, it expires after 90 days. |
| expires_in | The amount of seconds the access token will expire after it's been issued. Default is 3600 (1 hour). |
| scope | The scopes that were requested during the initial authorize call. |
If there's an error, you will be returned this error object instead:
| Field | Description |
|---|---|
| error | The name of the error. |
| error_description | The description of the error. |
Here is how the example app performs the authorization code exchange via its own endpoint:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24// in the /code-exchange handler (handleCodeExchangeRoute.ts) export const handleCodeExchangeRoute = async (req: Request, res: Response) => { const requestBody = { client_id: CLIENT_ID, client_secret: CLIENT_SECRET, grant_type: "authorization_code", redirect_uri: REDIRECT_URI, code: req.body.code.toString(), code_verifier: req.body.code_verifier.toString(), }; const response = await fetch(`https://auth.atlassian.com/oauth/token`, { method: "POST", headers: { "Content-Type": "application/json; charset=utf-8", }, body: JSON.stringify(requestBody), }); const responseJson = await response.json(); res.json(responseJson); };
Now that you have your access token you can use it to make Trello API requests. Simply pass the token in the Authorization http header as a bearer token. Checkout how the example app achieves this in its client-side code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19// in api.ts const trelloGetRequest = async (path: string) => { const accessToken = await getAccessToken(); const TRELLO_URL = `https://trello.com/${path}`; const headers = { Authorization: `Bearer ${accessToken}` }; const response = await fetch(TRELLO_URL, { headers }); const responseJson = await response.json(); return responseJson; }; export const getMember = async () => { const response = await trelloGetRequest("1/members/me?fields=id,username"); return response; };
When your access token expires (the default expiry time is within 1 hour of being issued), you need to use your refresh token to get a new access token and refresh token. Refresh tokens may only be used once and expire after 90 days.
Similar to the code exchange, you must hit https://auth.atlassian.com/authorize/oauth/token with the correct payload:
| Field | Description |
|---|---|
| client_id | The ID of your OAuth 2.0 client. |
| client_secret | The secret of your OAuth 2.0 client. Your client must be set to confidential. |
| grant_type | This must be set to "refresh_token". |
| refresh_token | The current refresh token. Refresh tokens expire and cannot be used after 90 days. |
The response has the same shape as the authorization code exchange step (see above).
Again, here is how the endpoint the example app uses to refresh tokens:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23// the /refresh-token handler (handleRefreshTokenRoute.ts) export const handleRefreshTokenRoute = async (req: Request, res: Response) => { const requestBody = { client_id: CLIENT_ID, client_secret: CLIENT_SECRET, grant_type: "refresh_token", refresh_token: req.body.refresh_token, }; const response = await fetch(`https://auth.atlassian.com/oauth/token`, { method: "POST", headers: { "Content-Type": "application/json; charset=utf-8", }, body: JSON.stringify(requestBody), }); const responseJson = await response.json(); res.json(responseJson); return; };
If your app already has users authorized via Trello Auth tokens, you can migrate them to OAuth 2.0 without making them re-authorize by calling the token exchange endpoint from your backend.
The token exchange endpoint accepts a valid Trello Auth token and returns a new set of OAuth 2.0 tokens for the same user. The original Trello Auth token is revoked as part of the exchange.
The original Trello Auth token is revoked as part of the exchange and can no longer be used to call the Trello API. After the exchange, all Trello API requests for that user must use the new OAuth 2.0 access token as a bearer token (Authorization: Bearer {access_token}).
Additionally, for Power-Up OAuth 2.0 clients, the new OAuth 2.0 token is workspace-restricted to the workspace the user is currently in. If your users use your Power-Up across multiple workspaces, only the first workspace can be migrated this way. For every other workspace, you must fall back to the standard authorization flow described above so the user can consent to that workspace.
1 2POST https://api.trello.com/1/token/exchange
The request must be authenticated with the user's existing Trello Auth token using one of the standard authorization methods (query parameters, Authorization header, or request body).
| Field | Type | Required | Description |
|---|---|---|---|
| scopes | string[] | Yes | The OAuth 2.0 scopes to request for the new tokens. These must be valid scopes configured on your OAuth 2.0 client and must not exceed the permissions of the Trello Auth token being exchanged. The offline_access scope is added automatically. |
| idOrganization | string | Required for Power-Ups | The ID of the Trello workspace. This is required because Power-Up OAuth 2.0 clients are workspace-restricted. |
On success, the endpoint returns a JSON object:
| Field | Description |
|---|---|
| access_token | An OAuth 2.0 access token which can be used to hit the Trello API. By default, it expires after 1 hour. |
| refresh_token | A refresh token which can be exchanged for a new access token and refresh token. By default, it expires after 90 days. |
| id_token | An OpenID Connect ID token containing information about the authenticated user. |
1 2 3 4 5 6 7 8 9 10 11 12 13 14const response = await fetch("https://api.trello.com/1/token/exchange", { method: "POST", headers: { "Content-Type": "application/json", Authorization: `OAuth oauth_consumer_key="${API_KEY}", oauth_token="${TRELLO_AUTH_TOKEN}"`, }, body: JSON.stringify({ scopes: ["read:board:trello", "write:board:trello"], idOrganization: WORKSPACE_ID, }), }); const { access_token, refresh_token, id_token } = await response.json();
| Status Code | Cause |
|---|---|
| 400 | Requested scopes exceed the permissions granted by the Trello Auth token. |
| 401 | The Trello Auth token is invalid, or the user does not have a linked Atlassian account. |
| 403 | The Trello Auth API key is not linked to the Power-Up, or the Power-Up does not have an OAuth 2.0 client configured. |
Once you have your OAuth 2.0 tokens, use them the same way as tokens obtained through the standard OAuth 2.0 authorization flow (see Step 3: Make API requests using the access token and Step 4: Refresh your access token using your refresh token above).
Rate this page: