Oauth2 Token Generation with Service Accounts

March 15, 2018

GCP service accounts are authentication and authorization units that enable you to communicate with Google APIs without the need for end users. Authentication includes knowledge of who a client is and authorization is what it can do.

In an application, service accounts are typically used when in need of access to app specific data, exluding the users but includes servers.

For example, you want to write an application that reads and writes files using Google Cloud Storage. Also, you want your files in GCS to be accessible only by your application, rather than a public access. Create a service account with the necessary roles on the GCS, add the key file of this service account to your server, and you'll be able to do read/write operations through the servers (without the need for the user). Also you would have lost the ache of hiding API keys in the client-side.

{
  "headers": {
    "normalizedNames": {},
    "lazyUpdate": null
  },
  "status": 401,
  "statusText": "Unauthorized",
  "url": "urlToService",
  "ok": false,
  "name": "HttpErrorResponse",
  "message": "Http failure response for urlToService: 401 Unauthorized",
  "error": {
    "error": {
      "errors": [
        {
          "domain": "global",
          "reason": "required",
          "message": "Anonymous caller does not have storage.objects.create access to bucket bucketName.",
          "locationType": "header",
          "location": "Authorization"
        }
      ],
      "code": 401,
      "message": "Anonymous caller does not have storage.objects.create access to bucket bucketName."
    }
  }
}
When you want to create an object inside a folder in GCS that does not have a public access, without Authorization header

User-Managed Service Accounts

User-defined service accounts, in addition to service accounts that Google automatically defines. Generally, these types of service accounts are used in environments that security is not an issue (for example, in a local or corporate environment) or on cloud servers different from Google's.

[SERVICE_ACCOUNT_NAME]@[PROJECT_ID].iam.gserviceaccount.com
User-Managed Service Account Id Format

Using the Google Cloud Platform Console, it's possible to create a service account in just a few steps.

  • Using the menu, navigate to the service accounts page.
  • Click on Create Service Account button.
  • On the dialog box
    • Enter a descriptive name for the service account.
    • Choose the roles you want that service account to execute jobs with.
    • To have a key file, click on Furnish a new private key checkbox.
      • Choose a file format (p12 or json) for your private key file.
    • Click on the create button.
  • Save the key file, you'll be using it to authorize the Google API calls.
Create Service Account Dialog BoxCreate Service Account Dialog Box

It is possible to process the key file using the Google Api Client libraries. In the following examples java language is used with Google API Java Client library.

Credential credential = GoogleCredential.fromStream(keyFileInputStream).createScoped(Collections.singleton("https://www.googleapis.com/auth/devstorage.read_write"), httpTransport, jsonFactory);
storage = new Storage.Builder(httpTransport, jsonFactory, credential).build();
Credentials processed by using json file with scope storage.dev.read_write
The service account must have Storage Object Admin role defined.

The same can be done using a key file in p12 format and the GoogleCredential.Builder method.

credential = new GoogleCredential.Builder()
  .setTransport(httpTransport)
  .setJsonFactory(jsonFactory)
  .setServiceAccountId(serviceAccountId)
  .setServiceAccountPrivateKeyFromP12File(p12File)
  .setServiceAccountScopes(scopes)
  .addRefreshListener(refreshListener)		
  .build();
storage = new Storage.Builder(httpTransport, jsonFactory, credential).build();
Credentials processed with p12 key file

The setServiceAccountUser method can be used when you want to impersonate a user. This method can be used to access a system where a common data set is kept and an account is required.

credential = new GoogleCredential.Builder()
  .setTransport(httpTransport)
  .setJsonFactory(jsonFactory)
  .setServiceAccountId(serviceAccountId)
  .setServiceAccountPrivateKeyFromP12File(p12File)
  .setServiceAccountScopes(scopes)
  .setServiceAccountUser("[email protected]")
  .build();
Credentials processed by using p12 file with impersonation.

Google-Managed Service Accounts

Service accounts created automatically by Google and assigned to projects. Each of these accounts represents different Google services, and each has access to a certain level of Google Cloud Platform projects.

If your application is running on Compute Engine, Kubernetes Engine, App Engine flexible environment, or Cloud Functions you don't need to create an service account explicity. You can just use the assigned default compute engine service account.

[PROJECT_NUMBER][email protected]
Compute Engine Service Account Id Format

GoogleCredential credential = GoogleCredential.getApplicationDefault().createScoped(gcsScopes);
storage = new Storage.Builder(httpTransport, jsonFactory, credential).build();
Credentials created by using the default compute engine service account

If you are working on Google App Engine standard environment, it is possible to access the credentials information using the App Engine App Identity API.

AppIdentityService appIdentity = AppIdentityServiceFactory.getAppIdentityService();
AppIdentityService.GetAccessTokenResult accessToken = appIdentity.getAccessToken(scopes);
String accessToken = accessToken.getAccessToken();
Credentials created by using the default app engine service account

Application Default Credentials

The default credential that is fetched by GoogleCredential.GetApplicationDefault() first checks whether the GOOGLE_APPLICATION_CREDENTIALS environment variable is set. If it is, it uses the file referenced by its value. If not, it tries to fetch the compute engine default service account on Compute Engine, Kubernetes Engine, App Engine flexible environment, or Cloud Functions systems. If identity information is not available in both cases, error occurs.

To Sum Up,

  • 1. If GOOGLE_APPLICATION_CREDENTIALS environment variable is present, the value it points to is used as the key file.
  • 2. If system provides a default compute engine service account, that is used.
  • 3. If there is no identity information found in both cases, error occurs.

If you are working in Google App Engine and want to use a manually created user-managed service account instead of the default App Engine service account, you need to define the key file path in appengine-web.xml along with specifying it as a resource as you should in all the files you want to access through your code. Otherwise, App Engine won't be able to find the file.

java.io.IOException: Error reading credential file from environment variable GOOGLE_APPLICATION_CREDENTIALS,
value 'private_key.json': File does not exist. at
com.google.api.client.googleapis.auth.oauth2.DefaultCredentialProvider.runningUsingEnvironmentVariable(DefaultCredentialProvider.java:199) at
com.google.api.client.googleapis.auth.oauth2.DefaultCredentialProvider.detectEnvironment(DefaultCredentialProvider.java:166) at
com.google.api.client.googleapis.auth.oauth2.DefaultCredentialProvider.getDefaultCredentialUnsynchronized(DefaultCredentialProvider.java:110) at
com.google.api.client.googleapis.auth.oauth2.DefaultCredentialProvider.getDefaultCredential(DefaultCredentialProvider.java:91) at
com.google.api.client.googleapis.auth.oauth2.GoogleCredential.getApplicationDefault(GoogleCredential.java:213)
Failed to find key file error received inside App Engine

<env-var name="GOOGLE_APPLICATION_CREDENTIALS" value="private_key.json" />

<resource-files>
<include path="/**.json" />
</resource-files>
appengine-web.xml to make the key file accessible within the code by showing it as the source file
Adds all the json files in the root file
The source files are closed to outside.