[Google has questionable OAuth practices]

Jan 14, 2025

Oauth (RFC 6749) was first drafted in 2012. There are currently students studying at the University of Waterloo that are younger than OAuth, and the number of these students is only expected to increase. This is not surprising; OAuth is a key part of today’s internet, being the gold standard solution for data access management, and any given developer, user, or human on the internet has likely interacted with it in some way.

For many very large and voluptuous companies such as Google and Microsoft, Oauth is what allows external applications to access your data. The Application (henceforth named “Tim”) does this by registering with Google/Microsoft/other site as an OAuth client. Because Tim is going to access the user’s data, Tim needs permission from Google before going into your account and sending an email, or seeing your contacts. This essentially means that Tim has an “account” at Google which must be verified before giving Tim your data. (Because unverified access to your data by an arbitrary application would Totally Suck Ass.) OAuth providers give access through an “authorization code”, which is given back to Tim and then can be used to make requests at the provider.

Since OAuth is a pretty mature protocol by now most companies at the size of Google have figured out how this works pretty well. However, Google itself seems to have some pretty fundamental misunderstandings about certain problems related to Oauth, specifically the issue of “public clients”. OAuth has several different specifications about how Tim should be identified (collectively called “grants”). The reasoning for this comes down to different types of applications; if Tim is a client that can keep a secret, such as a website where the secret can be kept server-side, then we can use authorization_code flow which allows for Tim to verify their identity with client_id + client secret (analogous to username+password).

However, Tim can’t always do this. I first encountered the issue with Google OAuth when attempting to develop a Firefox extension with the ability to schedule my course assignment deadlines as Google Tasks. Browsers are “client applications”, which means they cannot keep secrets; any “secret” embedded in a browser extension can be extracted by anyone who downloads it, because the extension is a self-contained application on the user’s machine. In OAuth terminology, these are called “public clients”, and OAuth actually has provisions for handling them.

How Public Clients Should Work

For public clients, OAuth 2.0 originally recommended the Implicit Flow (RFC 6749), which skips the client_secret entirely. The authorization code is replaced with an access token delivered directly in the redirect URL. However, Implicit Flow has been deprecated since OAuth 2.1 due to security concerns; primarily the risk of token leakage through browser history and referer headers. The modern replacement is Authorization Code Flow with PKCE (Proof Key for Code Exchange, RFC 7636). PKCE was specifically designed to allow public clients to use authorization code flow securely without requiring a client_secret.

Instead of a static secret, PKCE uses a dynamically generated code_verifier and code_challenge for each authorization attempt. Tim generates a random string (the verifier), hashes it (the challenge), and sends the challenge to Google during the initial authorization request. When exchanging the authorization code for an access token, Tim proves it’s the same client by sending the original verifier. Since the verifier is unique to each flow and never exposed to the browser, it can’t be stolen or reused.

How Google does it

Google offers several OAuth client types: Web Application, Desktop Application, iOS, Android, etc. For a browser extension, the “Desktop Application” type seems like the obvious choice; it’s meant for public clients that run locally. Except there’s a problem: Desktop Application clients don’t support redirect URIs. Or rather, they only support http://localhost, which is useless for a browser extension that needs to redirect to a special moz-extension:// or chrome-extension:// URL to receive the authorization code.

I spent a few hours crawling through documentation and getting bad advice from Claude and Gemini, both of whom gaslit me with weird suggestions like “register your app as an OAuth client”, etc. The main problem with the inability to configure the redirect URL in Google Oauth is that there needs to be some way to send the authorization_code from Google to the application in some way; the standard is to use a redirect URL, but there are other techniques. For example, another method is to send the code to a loopback address on which the application should be listening to. Unfortunately every single other useable approach to do this was either incompatible with firefox extensions, or deprecated by Google.

The final solution I came up with was simple: use the Web Application type. Web applications support arbitrary redirect URIs, and with PKCE, the application should not need the client secret to verify, so I would not need to bundle it with my application.

The part where I discover I am cooked

I’d consider myself fairly knowledgeable about how Oauth works, so I was understandably pretty upset when it wasn’t working. What really confused me was the error I was getting: “Invalid client”. I crawled through my PKCE implementation and every line of code to find nothing obviously wrong. Claude Code in its infinite lack of wisdom was hindering more than helping at this point.

However, through my research, I stumbled upon one tiny little GCP forum post : Google’s implementation of PKCE requires a client secret! What is the point of using PKCE with Google then??

Essentially Google has attached a ball and chain to PKCE that basically prevents you from using it for what it’s designed for. What’s worse is that I found some forum posts that did have a solution; a solution that seemed totally wrong…

Just leak your secret dude

I found a forum thread that seemed to suggest that the client secret is not considered sensitive in these scenarios, which really surprised me because it sounds totally wrong. Even worse, I found a GitHub comment from a Google engineer supporting this. Assuming someone has the client_secret, the OAuth standard supports a grant named client_credentials that allows the retrieval of an access code directly using a client_id and client_secret which would compromise an application effectively immediately; and to my knowledge there is no method in Google OAuth to restrict the available grants for use.

However, Google does another deviation from the Oauth standard here: use of the client_credentials grant is only allowed by Google from special “service accounts”. Thus the average google account would not be susceptible to this attack.

I’m still not sure this is secure though. By the way, the part about Google not allowing you to restrict useable grants has some other security implications.

Further reading

PKCE downgrade attacks: Why OAuth 2.1 is no longer optional