Security Dealing with credentials when using the Google Cloud client libraries (.NET edition)

Google APIs use OAuth 2.0 for authentication and authorization. To call an API, you first have to obtain an access token for the right scope and then pass it to the respective API by using the Authorization HTTP header:

Here is a simple bash script that illustrates the mechanics:

# Get an access token
TOKEN=$(gcloud auth print-access-token)

# Call an API
curl \
  -H "Accept: application/json" \
  -H "Authorization: Bearer ${TOKEN}" \
  https://www.googleapis.com/storage/v1/b/?project=${GOOGLE_CLOUD_PROJECT}

The trouble with access tokens is that they are short-lived. By default, an access token issued by Google is only valid for 1 hour – after that, it will be rejected by any API. That is not a problem for a little ad-hoc script like the one above, but if you are writing an application that keeps calling Google APIs over longer stretches of time, you somehow have to deal with expiring tokens.

One way to deal with expiring tokens is to put the right error handling in place: If an API call fails because of an expired token, get a new token and retry the call. That is a viable and robust approach, but often difficult to get right.

Another approach is to try to prevent the situation from arising: When you request an access token from Google, the response not only contains the token, but also the expected lifetime of the token (in seconds):

{
  "access_token": "ya29.ImCzB...",
  "expires_in": 3600,
  "scope": "https://www.googleapis.com/auth/cloud-platform",
  "token_type": "Bearer"
}

In fact, the expires_in field is nothing Google-specific, but required by the OAuth protocol.

Knowing when an access token expires allows you to proactively recycle a token before it is too late: If you are about to make an API call, check the expiration time of the token – if it is expired already or just about to expire, discard it and get a new token, then perform the API call.

This approach sounds easy enough to implement – if it was not for the little detail that there is no single way to get a new token. Instead, the right way to get a new token depends on how you got your first token.

Credential objects

To save you the effort of having to implement the logic to check and proactively recycle access tokens yourself, the .NET client library does not work with raw access tokens, but with credential objects.

Credentials implement the ICredential interface (which is itself derived from IConfigurableHttpClientInitializer and ITokenAccess, but that does not really matter). ICredential enables you to obtain a raw access token by calling the following method:

Task<string> GetAccessTokenForRequestAsync( 
   string authUri,  
   CancellationToken cancellationToken);

If you call this method, the credential object will try its best to give you an access token that is going to be valid – sometimes, that means handing out a cached token, and sometimes this means obtaining a fresh access token. And because there is no single way to implement this logic, there are multiple implementations of ICredential:

  • UserCredential is what you get after performing an authorization code flow. Internally, the UserCredential holds a refresh token and a cached access token. It will hand out the cached access token until it is about to expire in which case it uses the refresh token to obtain a new access token.
  • ComputeCredential can only be used if the code executes on a VM that is running as a service account. A ComputeCredential obtains access tokens for the underlying service account by calling the metadata server. Similar to UserCredential, it caches these access tokens until they are about to expire.
  • ServiceAccountCredential objects are created from a service account key. When you request an access token from a ServiceAccountCredential, the implementation creates an assertion based on the service account key and uses this to request an access token.
  • AccessTokenCredential simply wraps an existing access token.

ServiceAccountCredential is special

UserCredential and ComputeCredential objects have in common that their OAuth scopes are predefined: For a UserCredential, the scopes were defined when performing the authorization code flow, for a ComputeCredential, the scopes come from the VM configuration.

In contrast, a ServiceAccountCredential comes in two flavors – it can be scoped or not: When you create a service account key in the Cloud Console, you do not define any OAuth scopes. Therefore, a ServiceAccountCredential, when created from a service account key, initially also does not have a scope.

An unscoped ServiceAccountCredential is useful because it allows you to obtain ServiceAccountCredential objects for whatever scopes you need. But an unscoped credential is not useful for obtaining access tokens as these will be rejected by just about any API.

Given that there are scoped and unscoped ServiceAccountCredential objects, you might wonder why the two are not implemented as two different classes – but we’ll come to that later.

GoogleCredential as factory and wrapper

The last type of credential that I have not covered yet is the GoogleCredential. The primary purpose of this class is to serve as a factory.

By using one of the following factory methods, you can load a credential from a file structure. The file is allowed to either contain a service account key (type: 'service_account') or a cached user credential (type: 'authorized_user').

public static GoogleCredential FromFile(string path);
public static GoogleCredential FromJson(string json);
public static GoogleCredential FromStream(Stream stream);

Then there is a factory method to wrap an existing access token:

public static GoogleCredential FromAccessToken(string accessToken, IAccessMethod accessMethod = null);

And finally, there is GetApplicationDefault, which tries to load credentials by looking at GOOGLE_APPLICATION_CREDENTIALS, known file locations, and the metadata server (if executed on a VM):

public static GoogleCredential GetApplicationDefault();

What is a little peculiar about GoogleCredential is that the factory methods do not use ICredential or the more specific classes like UserCredential as return type. Instead, they all return GoogleCredential. Why is that?

Recall that a ServiceAccountCredential can be either scoped or unscoped and that you cannot really tell which one is which from looking at it. To enable you to distinguish between these two kinds of credentials, the factory methods wrap ServiceAccountCredential objects by a GoogleCredential which provides some helpful members:

public virtual bool IsCreateScopedRequired { get; }
public GoogleCredential CreateScoped(params string[] scopes)

And, I presume for consistency’s sake, all other credentials are wrapped by a GoogleCredential in the same fashion, although the enclosing object adds little extra value in these cases.

Credentials in action

Having discussed the different types of credentials and the role of GoogleCredential, we can put it all together. The following diagram shows how, depending on the factory method and input you use, you might end up with a different type of credential:

Credentials

A final pitfall

If you have been following along, you might have noticed that there is one pitfall: If you call GoogleCredential.GetApplicationDefault() or GoogleCredential.FromFile() (and variants), then you cannot always be sure whether the resulting credential is scoped or not.

Thankfully, this uncertainty is easy to get rid of – after calling any of these factory methods, add a call to IsCreateScopedRequired and CreateScoped:

var credential = GoogleCredential.GetApplicationDefault();
return credential.IsCreateScopedRequired
    ? credential.CreateScoped(ScopesMyClientNeeds)
    : credential;

This snippet ensure that whatever kind of credential was loaded, the resulting GoogleCredential is scoped, will refresh itself, and is ready to be used to make API calls.

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