← Back to Blog
pullmdsecurityoauthauthossMay 7, 2026 · 4 min read

PullMD Part 10: Locking It Down, Properly – Simply Explained

API LOCKED · WEB + PWA OPEN → HISTORYAI-generated image
Editorial diagram: a cutaway building with three entrances, labelled API, WEB and PWA. Only the API entrance has a heavy bolted door with a lock, the other two stand open and lead straight to a filing cabinet labelled HISTORY. Above, a dashed line AUTH wraps around the whole building and runs via OAUTH to three user symbols. On the left a panel /authorize with the red finding REVIEW: BLOCKED.Editorial diagram: a cutaway building with three entrances, labelled API, WEB and PWA. Only the API entrance has a heavy bolted door with a lock, the other two stand open and lead straight to a filing cabinet labelled HISTORY. Above, a dashed line AUTH wraps around the whole building and runs via OAUTH to three user symbols. On the left a panel /authorize with the red finding REVIEW: BLOCKED.

The Reddit post from Part 9 had a side effect I should have seen coming. From that moment on, quite a lot of people knew there was a service you could call without any kind of account. For me that was no problem, because it only ran on my own network. But now somebody else could set it up on a rented server and wonder one day why strangers are browsing through it. That moved authentication (the question of “who is allowed in?”) a good way up the list.

Three Quick Fixes on the Same Day

On April 28 three things landed in the code. First, a token: a secret word you set once in the configuration and then send along with every request, like a password for the front gate. On top of that, two routes for OAuth, a protocol that lets you prove your identity through a trusted middleman instead of handing over your password directly. That was meant for cases where a simple token isn’t enough. A bolt on the door, so there’s some peace for now. But a quick answer carries the risk that you mistake it for a finished one.

My Own Review Said No

The next morning a review ran over the OAuth branch, and it came back with a blocking finding. The endpoint (the specific address where a client picks up its permission) wasn’t itself protected. On top of that, a list of allowed return addresses was missing. Both together mean: the secret a client uses to identify itself can be walked around.

That’s the uncomfortable part about reviews you order yourself. They find exactly what you didn’t want to find, and they find it before anyone else does. At that point I could simply have worked through the findings. I didn’t, though.

The Question That Changed Everything

Instead, something else came to me while I was looking at the code:

“So I need to think about this properly at some point… what would it look like, assuming we fix all the bugs… I start my Docker and I’ve already got a secret configured in the env and auth turned on… when I open the website, what happens then? When I open the PWA, what happens then?”

(PWA in the quote stands for Progressive Web App: a website that behaves like a real app on your phone.)

That question is where the quick fix stopped working. The token only guarded the API (the interface other programs use to talk to the service). The web front end and the phone app were still standing wide open. Anyone who knew the address could open a browser, page through the history (the log of everything that had ever run through the service), and see what I had been reading. One bolt on one of three doors.

So, Properly Then

The answer to my own question came pretty quickly:

“Option 1: that hardly makes sense, if the rest is open, why would I secure MCP at all? Just so I can use it via claude.ai? But then the server has to be reachable from the internet… without protecting the rest? Well, have fun ;-)

Option 2: not complete either, if we do it then do it right! So user/password login on web and PWA, everything that’s an agent via API key, no?

So: lock it down, but properly“

(MCP in the quote stands for Model Context Protocol: the connection that lets the AI assistant use the service directly as a tool.)

What that meant was clear right there. It was a good deal more work than I had planned for that day. If there’s going to be a login, there have to be users. If there are users, there have to be sessions (the memory that keeps you logged in while you browse, so you don’t have to re-enter your password on every click). Users need passwords and keys. And then the history doesn’t belong to everyone any more: everyone gets their own. That turns a bolt into a rebuild.

The Constraint That Decided Everything After It

One point mattered enough that I raised it the same morning:

“Back to the API key auth for agents, because you just took it as given: would claude.ai access via MCP work with that too, or is OAuth mandatory there? Because I don’t want to block that off for us…”

That was the right question, even if I didn’t like the answer. A key is enough for tools you configure yourself. For the route through the claude.ai web interface it isn’t: OAuth is mandatory there, and there’s no way around it.

That settled the order before a single line had been built. Users first, then OAuth on top. The other way round doesn’t work, because OAuth without users is pointless.

What came of it is in Part 11. And that OAuth still didn’t work afterwards, for a reason I first suspected somewhere else entirely, is in Part 12 and Part 13.


Part 10 of 15 in the PullMD series.

← PullMD Part 9: Reddit Day and the First Issues | PullMD Part 11: The Multi-User Pivot →

Comments

Commenting takes an email confirmation or a GitHub login. Comments run on my own server, not on an ad provider's — details in the privacy policy.