DEV Community

Cover image for I chose modern OAuth for Trello. Then rolled back to a key and token.
Eugeniya Ivanova
Eugeniya Ivanova

Posted on

I chose modern OAuth for Trello. Then rolled back to a key and token.

Let me tell you about the Trello Power-Up we built. The idea is simple: you keep a content plan in Trello, one card is one post, and the card's due date is the publish date. What was left was manual. On the day, someone copied the text out of the card and posted it to each social network by hand. We made it so you can press Publora right inside the card and queue the post to publish automatically on that date.

I should admit up front: I don't write the code, Claude does. I sit next to it, ask questions, and sometimes ask it to double-check something it already considers done. This time, most of our hours went into its idea of using a newer way to authenticate.

We built the whole thing in a day. Read the docs, made a text-only prototype, added images and video, tested across three browsers in light and dark themes. Submitted for review. Atlassian approved the Power-Up in three days, though their docs warn that review usually takes two weeks or more. There are over six hundred Power-Ups in the catalog, and we couldn't find a single one that publishes cards to social.

The authentication, though, took some wrestling.

Modern OAuth couldn't download a file

The Power-Up pulls images from the card's attachments. To fetch them, Claude and I went with Trello's OAuth 2.0. The newer way to authenticate, all by the book.

But the attachment download endpoint kept answering 401: unauthorized permission requested. I even asked for the write scope, though we only needed read. Nothing changed. Claude wanted to keep digging through scopes; I decided to try the old auth instead.

And it worked. Trello's classic key-and-token scheme, with read-only rights, downloaded the same attachments without complaint. It stung a little: we'd set the new auth up straight from the docs, and the file came from the old one.

So we left two ways to authenticate. Publora uses OAuth with PKCE, and downloading Trello attachments uses a key and token with read-only rights. We even left a comment in the code, so that in six months nobody, Claude included, decides it's forgotten legacy that urgently needs replacing:

// The Power-Up's Trello API key (public). Used only to read the card's
// attachments: Trello's OAuth 2.0 tokens are refused by the attachment download
// endpoint ("unauthorized permission requested", checked 2026-10-13, even with
// write scope), while a read-only key/token pair downloads them.
Enter fullscreen mode Exit fullscreen mode

Signing into Trello only asks for read:

var url = "https://trello.com/1/authorize?expiration=never&name=Publora&scope=read&callback_method=fragment" +
  "&key=" + TRELLO_KEY + "&return_url=" + encodeURIComponent(location.origin + "/trello-callback");
Enter fullscreen mode Exit fullscreen mode

So we're leaving the old auth alone for now. It does, at least, download files.

Trello hands you a file with no type or size

The next annoyance was smaller. Trello returns an attachment with no mimeType. The file is there, but the API doesn't say what kind of file it is. We had to work out the type from the extension:

var MIME_BY_EXT = { jpg: "image/jpeg", jpeg: "image/jpeg", png: "image/png", gif: "image/gif", webp: "image/webp",
  tif: "image/tiff", tiff: "image/tiff", avif: "image/avif", mp4: "video/mp4", mov: "video/quicktime", webm: "video/webm" };
function mimeFor(name) { var m = String(name || "").toLowerCase().match(/[.]([a-z0-9]+)$/); return m ? MIME_BY_EXT[m[1]] || null : null; }
Enter fullscreen mode Exit fullscreen mode

Trello doesn't report the size either, so our bridge works that out.

The file doesn't go through the user

This part I particularly like, and credit to Claude for it. The user doesn't have to download the file from Trello and then upload it to Publora. The file goes straight from Trello into Publora's storage, without being buffered whole in memory. That way even a 150 MB video can go through without hitting the worker's memory limits:

const length = Number(file.headers.get("Content-Length") ?? "0");
const limit = body.mimeType.startsWith("video/") ? 150 * 1024 * 1024 : 25 * 1024 * 1024;
if (length > limit) { await file.body.cancel(); /* 413 too_large */ }

const { readable, writable } = new FixedLengthStream(length);
file.body.pipeTo(writable);
await fetch(slot.uploadUrl, { method: "PUT", headers: { "Content-Type": body.mimeType }, body: readable });
Enter fullscreen mode Exit fullscreen mode

The sequence is: create a draft, get an upload URL, send the file with PUT, call complete-media, and create the post. If anything fails along the way, we delete the draft so we don't leave stray records.

A bug we caught ourselves

We found this one before submitting, while checking the Power-Up against the guidelines.

The post status on a card, Scheduled, Published or Failed, should only be updated by the user who sent the post. Otherwise a board member on a different Publora account could see someone else's card marked Removed. To them the post didn't exist, and they could overwrite the status the author saw.

We fixed it before review. Good thing we tested that case at all: when you test everything under one account, a bug like this is easy to miss.

Review, and the letter F that became A+

Review took three days. On the first round Atlassian came back with one note: HSTS wasn't enabled. The letter said "< 365 days", though their own guidelines ask for at least a year. A typo, presumably.

Since we were back in the security settings anyway, Claude and I went through the whole guide: added headers, set up CSP with no inline scripts, no-store. The securityheaders.com score went from F to A+.

That took another evening. But there were no more notes.

Across the whole project, I don't think I suggested a newer technology even once. That's usually Claude's department. After the OAuth episode, I decided to let it get on with things, just not to agree quite so fast.

What came of it

Publora is in Trello now. From a single card you can schedule a post to ten social networks at once, on the date in the card's due field. Everything happens inside Trello, with no switching between apps.

The Power-Up works on free Trello. It isn't available in the Trello mobile apps, because they don't run third-party Power-Ups. It does work in the mobile browser.

Listing link: [coming tomorrow].


Have you ever picked a newer way to authenticate, and then found the older one handled the job you needed better?

Top comments (5)

Collapse
 
sgaggjhkjh profile image
Arjun Sharma •

The server-side file transfer design is the detail I appreciated most. Keeping the file off the user's device and cleaning up the draft on failure shows real respect for the user's bandwidth and storage, and the dated comment in the code about why the old auth stays is a practice more teams should adopt.

Collapse
 
eugeniya_ivanova_4a58eadc profile image
Eugeniya Ivanova •

Thanks, that’s exactly the pair I was quietest and happiest about.

The draft cleanup came from the opposite experience. Early on we left failed drafts lying around, the dashboard filled up with half-made posts, and now a failed upload takes its draft down with it.

The dated comment is almost a confession. We left it because we’re the team most likely to look at that old key in six months, decide it smells like legacy, and “upgrade” it straight back into the 401. Future us needed the warning more than anyone else would.

Collapse
 
sgaggjhkjh profile image
Arjun Sharma •

I love that the dated comment is a deliberate warning to future-you. Designing for the team that inherits the code six months later is exactly the kind of care that survives contact with reality.

Thread Thread
 
eugeniya_ivanova_4a58eadc profile image
Eugeniya Ivanova •

Thanks. Honestly, most of what bites us six months later isn’t a bug, it’s a past decision nobody wrote the reason for. The comment is cheap insurance against our own confidence.

Thread Thread
 
sgaggjhkjh profile image
Arjun Sharma •

Cheap insurance against our own confidence is a great line. I wonder whether the dated comment also works as a social signal, telling the next person it is acceptable to question the decision rather than treating it as sacred. Did rolling back to the key and token change how your team reviews auth decisions now?