A friend and I were out editing for OpenStreetMap, and we wondered how we could tweak an existing open changeset. Because the client we used does not seem to provide that feature, and we are messy gremlins who like to mess around with data to do things (like adding review_requested=yes to a changeset manually).

So we defaulted to the next most reasonable thing to use on our phones in the middle of the street: curl.

So I decided to finally figure out how OAuth2 works, but also fell into the trap of forgetting that application/x-www-form-urlencoded is a thing.

OAuth2 for OSM

For the purpose of the experiment, I created a changeset with a change I had not uploaded yet, and decided to play around with the API.

OSM uses HTTP Basic Authentication, so I needed a token. The fastest way to get one seemed to be getting it through OAuth2. For that, I first registered the application.

Note that the $REDIRECT_URI you provide will be useful later. It won't really serve any technical purpose (the authorization API just redirects you to that URI with the code you get), but if you're missing it/not providing the same one given at registration, you will get errors.

With registration done you get two components: $CLIENT_ID and $CLIENT_SECRET. Next, open

https://www.openstreetmap.org/oauth2/authorize?client_id=$CLIENT_ID&redirect_uri=$REDIRECT_URI&response_type=code&scope=write_api

in your browser while logged in to OSM. It'll give you the usual "do you want to authorize this app" spiel, and you press yes. Here the scope of authorizations used here means you can only write stuff through the map API.

After authorization you will be redirected to $REDIRECT_URI?code=$CODE. Get that code, and your next step is:

curl -v -X POST -L 'https://www.openstreetmap.org/oauth2/token' \
	--data-urlencode "client_id=$CLIENT_ID" \
    --data-urlencode "redirect_uri=$REDIRECT_URI" \
    --data-urlencode "grant_type=authorization_code" \
    --data-urlencode "code=$CODE" \
    --data-urlencode "client_secret=$CLIENT_SECRET"

And if you are lucky, you get:

{"access_token":"[REDACTED]","token_type":"Bearer","scope":"write_api","created_at":[REDACTED]}

Hurray! But I was not so lucky.

It took me an hour and several unhelpful online pages to even get the code in the first place. OpenStreetMap automatically closes changesets after one hour of inactivity, meaning mine was gone.

My friend was also tweaking things at the same time. She was surprised I got the code, so I gave her the mechanism and explained what I had tried so far, and moved on. I had tried a lot of things. For some reason, some of them made OpenStreetMap spit out errors in korean or spanish.

My friend tinkered and came back with a functional method to get the bearer token. We had both found two important things:

  1. The order of query parameters in the authorization URL was important (it's 2023…)
  2. The token URL accepted neither application/json, query params, or text/xml for its input. After a while, I wondered if curl sending a Content-Type: application/x-www-form-urlencoded header even without a body of data would be the problem, but removing that header also proved useless. My friend found out that you needed to provide parameters as separate fields through --data-urlencode (and we are still not sure the order does not matter).

Eventually, after a good night's sleep, I got my token, roughly 14 hours, including a good night's sleep, after my changeset closed. At least, next time, I know how I can tinker with the API (albeit, perhaps, not the production API).