The Rarely Known Fact: Using Google Service Account for M2M JWT Authentication and Authorization


## TL;DR

Google service accounts can securely handle machine-to-machine (M2M) authentication and authorization. This post demonstrates how to generate and verify JSON Web Tokens (JWTs) with custom claims (e.g., roles) using NestJS, taking full advantage of Google’s key management infrastructure.

## Table of Contents
- [Overview of M2M Authentication with Google Service Accounts](#overview-of-m2m-authentication-with-google-service-accounts)
- [Setting Up the Google Service Account](#setting-up-the-google-service-account)
- [Generating a JWT in Service B](#generating-a-jwt-in-service-b)
- [Verifying the JWT in Service C](#verifying-the-jwt-in-service-c)
- [Benefits and Conclusion](#benefits-and-conclusion)

## Overview of M2M Authentication with Google Service Accounts

In microservices architectures, secure communication between services is crucial—especially when no end user is involved. Google service accounts provide a robust method for machine-to-machine (M2M) authentication by generating JWTs that include both standard and custom claims. These tokens are signed with a private key and later verified using Google’s public certificates, ensuring secure and reliable validation.

## Setting Up the Google Service Account

Before diving into code, set up your Google service account:

1. **Create a Service Account:**
   - Go to the [Google Cloud Console](https://console.cloud.google.com/).
   - Navigate to **IAM & Admin** > **Service Accounts**.
   - Create a new service account and download its JSON key file (e.g., `service-account-key.json`).

2. **Understand the Key File:**
   - **`private_key`:** Used for signing JWTs.
   - **`client_email`:** Serves as the issuer (`iss`) in JWT payloads.
   - **`private_key_id`:** Included in the JWT header (`kid`) to help identify the correct public key during verification.

## Generating a JWT in Service B

In Service B, you generate a JWT that includes a custom `role` claim. Here’s a NestJS example demonstrating how to create a JWT using the service account’s credentials:

```typescript
import * as jwt from 'jsonwebtoken';
import { ConfigService } from '@nestjs/config';
import { Injectable } from '@nestjs/common';

@Injectable()
export class AuthService {
  constructor(private configService: ConfigService) {}

  generateToken(role: string): string {
    const privateKey = this.configService.get<string>('private_key');
    const clientEmail = this.configService.get<string>('client_email');
    const privateKeyId = this.configService.get<string>('private_key_id');

    const payload = {
      iss: clientEmail,
      aud: 'http://localhost:3001', // Audience: Service C’s endpoint
      iat: Math.floor(Date.now() / 1000),
      exp: Math.floor(Date.now() / 1000) + 3600, // Token expires in 1 hour
      role, // Custom claim for authorization
    };

    return jwt.sign(payload, privateKey, {
      algorithm: 'RS256',
      keyid: privateKeyId,
    });
  }
}
```

**Key Points:**
- **Payload:** Combines standard claims (`iss`, `aud`, `iat`, `exp`) with a custom `role` claim.
- **Signing Process:** Uses RS256 with the provided private key and includes the `kid` header for easy verification.

## Verifying the JWT in Service C

Service C is responsible for verifying the JWT. It fetches Google's public certificates to validate the token and checks the custom claim to ensure proper authorization:

```typescript
import * as jwt from 'jsonwebtoken';
import * as https from 'https';
import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common';

@Injectable()
export class AuthGuard implements CanActivate {
  private serviceAccountEmail = 'your-service-account@project.iam.gserviceaccount.com';

  async canActivate(context: ExecutionContext): Promise<boolean> {
    const request = context.switchToHttp().getRequest();
    const token = request.headers.authorization?.split(' ')[1];
    if (!token) return false;

    const certs = await this.getCerts();
    const decodedHeader = jwt.decode(token, { complete: true })?.header;
    const publicKey = certs[decodedHeader?.kid];

    const decoded = jwt.verify(token, publicKey, {
      algorithms: ['RS256'],
      audience: 'http://localhost:3001',
      issuer: this.serviceAccountEmail,
    }) as { role?: string };

    return decoded.role === 'admin'; // Grant access only if the role is 'admin'
  }

  private async getCerts(): Promise<{ [key: string]: string }> {
    return new Promise((resolve, reject) => {
      https.get(
        `https://www.googleapis.com/service_accounts/v1/metadata/x509/${this.serviceAccountEmail}`,
        (res) => {
          let data = '';
          res.on('data', (chunk) => (data += chunk));
          res.on('end', () => resolve(JSON.parse(data)));
        }
      ).on('error', reject);
    });
  }
}
```

**Highlights:**
- **Certificate Retrieval:** The public keys are fetched from Google’s endpoint.
- **Token Verification:** Validates the token’s signature, audience, and issuer.
- **Authorization Logic:** Access is allowed only if the decoded token’s role is `admin`.

## Benefits and Conclusion

### Performance and Security Benefits

- **Efficiency:**  
  - Google automates key rotation and certificate distribution, reducing manual overhead.
  - JWT verification is fast and efficient due to pre-fetched certificates.

- **Security:**  
  - The private key remains secure within Service B.
  - Verification with public certificates ensures the token’s integrity.

- **Scalability:**  
  - Centralized IAM management simplifies secure communication across multiple services.

### Conclusion

Leveraging Google service accounts for M2M JWT authentication and authorization offers a secure, scalable, and efficient solution for microservices communication. By generating JWTs with custom claims in one service and verifying them in another, you enhance your system’s security without added complexity. The provided NestJS examples offer a practical starting point for integrating this approach into your projects.

Give it a try and streamline your authentication workflows with confidence!