Skip to content

feat: support full-format identity tokens on GCE - #681

Open
SNO7E-G wants to merge 2 commits into
googleapis:mainfrom
SNO7E-G:feat/gce-full-format-id-token
Open

feat: support full-format identity tokens on GCE#681
SNO7E-G wants to merge 2 commits into
googleapis:mainfrom
SNO7E-G:feat/gce-full-format-id-token

Conversation

@SNO7E-G

@SNO7E-G SNO7E-G commented Jul 27, 2026

Copy link
Copy Markdown
Contributor

Fixes #526.

On GCE/Cloud Run, the identity token returned by the metadata server omits the full payload (for example, the authorized party's email) unless format=full is requested. The Python auth library already does this; this brings PHP in line.

Adds an optional, backward-compatible $idTokenFormat argument to the GCECredentials constructor. When a target audience is set, passing 'full' appends &format=full to the metadata identity request:

use Google\Auth\Credentials\GCECredentials;
$creds = new GCECredentials(
     targetAudience: 'https://my-service.run.app',
     idTokenFormat: 'full',
 );

The value is validated to standard or full. Existing callers are unaffected (the default preserves the current request). Scoped to GCECredentials; exposing the option through ApplicationDefaultCredentials::getIdTokenCredentials is a natural follow-up kept out of this PR to keep it to one subject.

Unit tests cover the appended query string and the validation error.

SNO7E-G added 2 commits July 24, 2026 00:10
Adds an optional $idTokenFormat constructor argument to GCECredentials. When
a target audience is set, passing 'full' appends '&format=full' to the metadata
server identity request so the returned ID token includes the full VM instance
payload (for example, the authorized party's email), matching the behavior of
the Python auth library. The value is validated to 'standard' or 'full'.

Fixes googleapis#526
@SNO7E-G
SNO7E-G requested a review from a team as a code owner July 27, 2026 20:07
@bshaffer

Copy link
Copy Markdown
Contributor

This works, but it's interesting because all our auth libraries other than Python (Go, NodeJS, C#, Java, Ruby, Rust, and C++) do NOT support this, and instead align with the PHP auth library's current behavior. And even Python doesn't support this, it just has format=full hardcoded (which I believe is worse).

Could you tell me the use-case that requires this? Are you getting the ID token with Credentials::getLastReceivedToken?

Here's a way this can be done with our library without having to implement this parameter:

$gce = new GCECredentials(targetAudience: 'https://my-service.run.app');
$defaultHandler = HttpHandlerFactory::build();

$customHttpHandler = function (RequestInterface $request) use ($defaultHandler): ResponseInterface {
    $uri = $request->getUri();
    
    // Append format=full to the existing query string for identity token requests
    if (strpos($uri->getPath(), '/identity') !== false) {
        $query = $uri->getQuery();
        $uri = $uri->withQuery($query . ($query ? '&' : '') . 'format=full');
        $request = $request->withUri($uri);
    }
    
    // Pass the rewritten request to the standard HTTP transport handler
    return $defaultHandler($request);
};

$tokenData = $gce->fetchAuthToken($customHttpHandler);
$fullJwt = $tokenData['id_token']; // Contained full payload (email, project, zone, etc.)

@SNO7E-G

SNO7E-G commented Aug 4, 2026

Copy link
Copy Markdown
Contributor Author

Good question @bshaffer — I went and checked the other libraries before answering, and the picture is a bit different from what we both might have assumed:

So among Python, Node, Go, Java, and C#, PHP is currently the only one without a way to get the full-format token — and the opt-in shape in this PR is the same design Java, C#, and the new Go library settled on, which I'd agree beats Python/Node hardcoding it.

The use case is the one from #526: service-to-service calls on Cloud Run, where the receiving service verifies the incoming ID token and authorizes on the calling service account's email claim, which the standard format omits. We're not reading it back through getLastReceivedToken — the middleware attaches it to the outgoing request as usual; the format only changes what the receiver sees after verifying the JWT.

The handler workaround does work, but one subtle issue pushed me toward a first-class option: getCacheKey() is the token URI, so with the constructor arg a full-format credential naturally gets its own cache key, while with the rewriting handler the format never makes it into the key — a standard-format and a full-format credential for the same audience would collide in a shared FetchAuthTokenCache pool. It also couples user code to the /identity path, which feels like an implementation detail of this class.

Happy to also add the licenses flag for parity with Java/C#/Go, or adjust the parameter shape to whatever fits this library's conventions best.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: Missing full payload in identity token from GCECredentials

2 participants