Security Authenticating users in tools or desktop apps

In the last post, we looked at how application default credentials differ from your personal gcloud credentials and why the idea of application default credentials is particularly useful for developing server-side applications.

If your plan is to develop a tool or desktop app instead of a server-side application, the benefits of application default credentials are less obvious and reusing the user’s personal gcloud credentials instead might seem attractive. But there are some pitfalls.

Using application default credentials

When you develop a tool, script, or desktop application that needs to access Google Cloud APIs, you can rely on application default credentials for authentication. A clear advantage of relying on application default credentials is that it is easy – all you need to do is call GoogleCredential.GetApplicationDefault() (or its equivalent, like google.app.default() in Python).

Another advantage is that application default credentials are issued for the OAuth scope https://www.googleapis.com/auth/cloud-platform. That is, these credentials give you access to all Cloud APIs.

But there is also a clear disadvantage to this approach – on a user’s workstation, trying to load application default credentials is quite likely to end in an error:

  • Users usually do not (and should not!) have any service account key file on their machine.
  • It is unlikely that the user runs the tool on Compute Engine, Kubernetes Engine, Cloud Run, or App Engine.
  • The user might not have run gcloud auth application-default login (few users do), or might not even have the Cloud SDK installed.

If the tool is aimed at developers, requiring the Cloud SDK as prerequisite might be acceptable – and anybody who has the Cloud SDK installed is likely to have stored personal gcloud credentials. So how about we simply load these personal gcloud credentials instead of application default credentials instead?

Using a user’s personal gcloud credentials

The official and “right” way to access a user’s personal gcloud credentials is to run gcloud auth print-access-token or gcloud auth print-identity-token to obtain an access token or IdToken, respectively. Especially for scripts, this approach usually works well and is easy to implement. But for more complex tools, there are two limitations that can make the “right” way seem less appealing:

  • Running gcloud auth print-access-token is slow – it can easily take multiple seconds to obtain a token.
  • The access token is short-lived and will expire after an hour. Refreshing the token will require you to run gcloud auth print-access-token again.

There is a second, unofficial way to obtain a user’s personal gcloud credentials – and that is to reach into the gcloud folder structure:

  1. Look up the active account in the config_default file
  2. Load legacy_credentials\<active-account>\adc.json

This process is fast and the result is a proper GoogleCredential (if you use .NET) that will automatically take care of refreshing tokens. That sounds great – but unfortunately, there are a few problems with this approach as well.

The first problem is that you are relying on an implementation detail. At some point, gcloud might drop support for legacy credentials in which case your code might stop working.

The other problem is less obvious: If you use a custom GCP session length, then loading adc.json might fail with an invalid_rapt error, indicating that a re-authentication is required. The only way to perform such a re-authentication is to run gcloud – and because gcloud might prompt the user for credentials, this has to be done in the foreground, not in the background. However, depending on the tool that you are writing, having to run gcloud might either defeat the intent of not having to spawn gcloud processes or might not be viable at all.

Using a custom OAuth client ID

The third, and arguably the cleanest way to authenticate a user is to register a custom OAuth client ID and to perform a 3-legged OAuth authorization flow.

By using a custom client ID, you avoid any dependencies on gcloud and gain full control over how and when to authenticate a user. Once a user has authenticated, your tool will also be listed on myaccount.google.com under Apps with access to your account, giving the user the ability to revoke access at any time.

Coming along with additional flexibility afforded by using a custom OAuth client ID is the responsibility to securely store refresh tokens. If you use the .NET client library, you can use the FileDataStore class for this purpose. However, be aware that this class stores tokens in plan text, so it is far from ideal from a security perspective. A better option is to use DPAPI to encrypt the tokens, and then store them in the user’s (roaming) user profile, either as a file or registry value. For an example on how this can be implemented, take a look at the Cloud IAP plugin source code.

Depending on the OAuth scopes that your took needs access to, you also have to get your client ID whitelisted. Google classifies OAuth scopes into three sets:

  • Restricted scopes. These are scopes that allow an application to access GMail or Google Drive data, which is considered particularly delicate.
  • Sensitive scopes. These are scopes that permit access APIs and data that are not quite as delicate as GMail and Google Drive, but still somewhat risky. Pretty much all Google Cloud APIs fall into this class.
  • Other scopes. This includes all scopes that are not restricted or sensitive.

You can see whether a scope is considered restricted or sensitive when you create your OAuth client ID in the Cloud Console:

Sensitive scopes

If your tool does not need access to sensitive or restricted scopes, then you can simply create an OAuth client ID in the Cloud Console, and you are all set. For restricted or sensitive scopes, your application needs to be verified first.

The idea behind verification is to prevent applications from requesting too many permissions.

In theory, the OAuth user screen should be enough of an incentive for application authors to request as few scopes as necessary and also enough to incentivize users to be careful about authorizing applications that request more permissions than seems appropriate. In practice, users are often not that careful – so the verification process is a way to nudge (or even force) developers to do the right thing.

The exact process for verification depends on the nature of your application and the exact scopes it needs access to. Getting a client-side tool that needs access to few and narrow OAuth scopes verified is usually fairly straightforward. In contrast, verification for a server-side application that needs access to broad scopes (such as ../auth/cloud-platform) might be a bit challenging.

Takeaway

For server side applications, using application default credentials is almost always the way to go. For tools and desktop apps, application default credentials are a good choice if the tool is targeting developers who are likely to have the Cloud SDK installed and are familiar with using gcloud. In pretty much all other cases, consider registering a custom OAuth client ID, even if it means having to get your app verified.

The only legitimate cases where I think using a user’s gcloud credentials makes sense is when your tool or application integrates tightly with gcloud or if a workflow requires a user to switch back and forth between gcloud and your tool. In these cases, sharing credentials across gcloud and your application can help reduce friction and ensure a smooth user experience. In all other cases, it’s best not to even try to use the user’s personal gcloud credentials.

Any opinions expressed on this blog are Johannes' own. Refer to the respective vendor’s product documentation for authoritative information.
« Back to home