PATs are usually used as a replacement for username+password when working with systems over a communication medium that does not support OAuth or similar. The most common application I’m aware of is when using Git (it’s quite telling that all references from the Wikipedia article you linked are to Git server providers).
When working via protocols that do allow for OAuth (such as REST in the case of OSM) that’s no need for that sort of “bandage”. Your step examples are somewhat exaggerated, a more comparable comparison:
OAuth 2 | PAT |
---|---|
|
|
Think I’m missing something between step 2 and 3 for OAuth 2 (steps 3-6 in your steps)? That’s because even as a developer you rarely need to care about that, provided you use a decent library (which you should do anyway).
Let’s compare the code using requests
in Python (code not tested, likely incomplete but serves the purpose of comparing):
OAuth 2 | PAT |
---|---|
|
|
There difference is a single line more (and a few more arguments) for OAuth 2. But even this isn’t the whole truth, because ideally all that should be abstracted away by a library (sadly osmapi, the most up-to-date Python lib for the OSM API I’m aware of, does not support this, though for example osm-auth (JS/TS) does) so that even for OAuth you’d just have:
from some_osm_lib import OSM
client = OSM(client_id='XXX', client_secret='XXX')
client.nodes.get(45124552)
While I won’t go as far as saying that we want some artificial barriers like Simon is implying (but also not saying that it’s bad), I think implying that supporting OAuth 2 but not PATs would be the opposite of an open and inviting platform is just plain wrong.
“Unusual approach”?
OAuth 2 is literally the industry standard for third-party API access (though it’s mowing more towards OIDC, but that’s just an extension/formalization of OAuth 2).
The OSM community is quite good at re-inventing the wheel (just look at our data model compared to what everyone else is doing in mapping/geospatial), but using OAuth 2 is one of the cases where we’re using an exact copy of the wheel blueprints everyone else is using…
Now this part is interesting, and I suspect part of the reason for this post is that you’re working on implementing that part in your Python port.
I’ve previously talked about the fact that implementing a security mechanism (be it OAuth or PAT or something else) is something the OSM community should avoid; doing that correctly and securely is just to much work. Personally, I would have solved it using some pre-existing component such as Keycloak or the Ory-suite, the Ruby port solved it using the Devise library, you could use oauthlib.
Regarding refresh tokens, much of the industry is moving towards (semi-) stateless services and JWTs, and while there’s a lot to be said about JWTs (and I encourage you to read about them regardless of if you end up using them or not) it does make refresh token handling very easy (somewhat pseudo-codey):
@app.get("/refresh_token")
async def refresh_token(refresh_token: str):
parsed = jwt.decode(refresh_token)
if not db.user_exists(parsed["uid"]) or parsed["valid_until"] < datetime.now():
raise HTTPError(401)
return jwt.encode(dict(uid=parsed["uid"], access_token=generate_token(), refresh_token=generate_token(), valid_until=datetime.now() + timedelta(minutes=5)))