---
title: "I did something naughty: Circumventing Authorized-Fetch as implemented by GoToSocial"
date: 2024-12-11T06:10:10+02:00
draft: false
image: "uploads/fediproxy/fediproxy.png"
categrories: ['English']
tags: ['gotosocial', 'fediverse', 'mastodon', 'authorized fetch', 'rss', 'FastAPI',]
---

Yes the title is correct, but I had nothing malicious in mind! 

## What this is about

For [@qzt@queereszentrumtuebingen.de](https://social.queereszentrumtuebingen.de/@qzt)  we include the public feed [in a sidbar on the homepage](https://queereszentrumtuebingen.de/). Initially this was done using the standard API to fetch statuses `/api/v1/accounts/{account_id}/statuses` and worked like a charm. The problem started when [GoToSocial](https://gotosocial.org/) (the fediverse server we use, similar to mastodon) implemented authorized fetch. This is a a good thing! Authorized fetch means, that every call to a endpoint needs to be authorized by an `access_token`. You get an access token from a fedi account. It's what fediverse clients like Tusky or Phanpy do on your behalf to get the posts that make up you timeline. 

Authorized fetch has major advantages as

* data scraping can only be done by other fediaccounts
* blocking can not be circumvented by using the public API

and much more. Sadly it also broke our website integration.

## Possible Solutions

So what now? I initially wanted to turn of authorized fetch for [@qzt@queereszentrumtuebingen.de](https://social.queereszentrumtuebingen.de/@qzt) by messing with the GoToSocial code and turning it off for the whole server. This would have been possible as this is the only user on the server. The GoToSocial devs helped me manage to find where to do that. But it's not ideal and would make me build a custom docker image fore each update.

Next idea: The whole point of authorized fetch is, that only fedi-accounts (and apps they authorized) can access the API. So lets do that! Set  up a new account, add app and authorize it [as described in the GoToSocial documentation](https://docs.gotosocial.org/en/latest/api/authentication/). I used #Bruno for that, that was much more comfortable than using curl for me.
With that authorization code you can now get an access token for your app. Put that in the Javascript that loads posts and we are good right? Sadly no. It would totally work. But it would also allow anyone to read and post on behalf of the account. That calls for malicious actors using this for scraping or spamming.

So instead, we need a proxy that stores the access token securely and restricts the actions.

## The proxy

Such a proxie must

* offer the endpoint that provides the same data as the FediverseAPI
* authorize itself to the FediverseAPI via `access_token`
* restrict to read access of consenting accounts

The last point is really important, as we don't want to allow others to use this endpoint to scrape data unauthorized.

I wrote a short FastAPI server that offers this. It only implements one method

```
@app.get("/api/v1/accounts/{account_id}/statuses")
async def fetch_data(account_id):
    if account_id not in ALLOWED_ACCOUNTS:
        raise HTTPException(status_code=401, detail="You can only use this proxy to access configured accounts")

    headers = {"Authorization": f"Bearer {ACCESS_TOKEN}"}
    response = requests.get(f"{EXTERNAL_API_BASE_URL}/api/v1/accounts/{account_id}/statuses", headers=headers)
    return response.json()
```

Basically this is the whole API code, I only trimmed a few checks and error handling.

## Deployment

To deploy, I put it in a docker container and started it via docker-compose. Reverse proxing is handled by Traefik, I won't go into detail here.

```
services:
  fediproxy.example.org:
    image: docker.io/moanos/fediproxy
    container_name: "fediproxy.example.org"
    restart: unless-stopped
    environment:
      EXTERNAL_API_BASE_URL: ${EXTERNAL_API_BASE_URL}
      ACCESS_TOKEN: ${ACCESS_TOKEN}
      ALLOWED_ACCOUNTS: ${ALLOWED_ACCOUNTS}
    labels:
      - "traefik.enable=true"
      - "traefik.docker.network=traefik"
      - "traefik.http.routers.fediproxy.rule=Host(`fediproxy.example.org`)"
      - "traefik.http.routers.fediproxy.service=fediproxy-service"
      - "traefik.http.routers.fediproxy.entrypoints=web-secure"
      - "traefik.http.routers.fediproxy.tls=true"
      - "traefik.http.routers.fediproxy.tls.certResolver=default"
      - "traefik.http.services.fediproxy-service.loadbalancer.server.port=8000"
    networks:
      - traefik

networks:
  traefik:
    name: "traefik"
    external: true

```

I added a short `.env` to configure:

```
ACCESS_TOKEN=VERYSECRETTOKENTHATISDEFINETLYREAL
EXTERNAL_API_BASE_URL=https://gay-pirate-assassins.de
ALLOWED_ACCOUNTS=ZGGZF4G8NNOTREAL81Z8G7RTC
```

## Results

Now I can again use something like [the wordpress plugin Include Mastodon Feed](https://wordpress.org/plugins/include-mastodon-feed/#installation) just by pointing to the proxy: `[include-mastodon-feed instance="fediproxy.example.org.de" account="ZGGZF4G8NNOTREAL81Z8G7RTC"]`

Hope you enjoyed the read. Source code for the proxy can be found here: https://git.hyteck.de/moanos/FediProxy
If you want to play around a bit you can use https://git.hyteck.de/moanos/include-fedi


Sloth logo of GTS by [Anna Abramek](https://abramek.art/), [Creative Commons BY-SA license](http://creativecommons.org/licenses/by-sa/4.0/).