Business Challenge
#41 described the exchange. This is what comes back from it, and the four shapes it can take.
There is no refresh token. When you generate an access token by impersonation, the access token comes without a refresh token, which means that when the token expires, you must repeat the impersonation process to generate a new one.
Correct approachRenewal is re-impersonation, not a refresh call. That is deliberate — it is what forces the caller to re-present their own identity, which is #41's whole argument.
Only if nothing Google validates it. The exp claim must be no more than 12 hours in the future — but if you are calling a Google API, the exp claim must be set no more than 1 hour in the future.
Pick the expiry from the audience, not from a house standard. The same token is legal for twelve hours against your own service and rejected at sixty-one minutes against Google's.
A chain does not confer anything. The intermediary only passes on the request — it does not give the caller or the final account any additional access. Each account simply grants the Service Account Token Creator role to the previous one.
Correct approachUse a chain to cross a trust boundary, never to accumulate privilege. And if one call suffices, Google says to create the credential directly.
They answer different questions. An access token carries permissions and is accepted for authentication by most Google APIs; an ID token carries identity for something that checks an audience.
Correct approachChoose by what sits at the other end. Sending the wrong one produces an authentication failure that looks like a permissions problem.
Architecture
The Service Account Credentials API mints four things, and all four are the same private key being used on your behalf.
The four types, by who checks them
| Type | What it carries | Who validates it |
|---|---|---|
| OAuth 2.0 access token | Permissions. | Google. Accepted for authentication by most Google APIs. |
| OIDC ID token | Identity, for a stated audience. | Whatever the audience names — Cloud Run, IAP, your own service. |
| Self-signed JWT | Whatever claims you write. | Your application, an API Gateway, or a Google API. |
| Self-signed blob | Arbitrary bytes. | Anything that can verify a signature. |
Both signing methods say it explicitly: they sign using the service account's system-managed private key — the same key #40 described as always held in escrow, where you can never access it directly. That reframes the escrow entirely. It is not a limitation on what you can do with the key; it is a limitation on where the key can be. You keep signing, issuing and authenticating, and you lose only the ability to copy the key onto a laptop. Which is the one capability that has ever caused an incident.
The two twelve-hour limits, which are not the same limit
| Access token | Self-signed JWT | |
|---|---|---|
| Default maximum | 1 hour (3,600 seconds). | You set exp; the ceiling is 12 hours. |
| Longer is possible by | Adding the account to the allowServiceAccountCredentialLifetimeExtension list constraint, to reach 12 hours (43,200 seconds). |
Nothing — 12 hours is the ceiling. |
| Calling a Google API | Same limits. | 1 hour maximum, regardless. |
| Who enforces it | The token issuer, at generation. | The recipient, at validation. |
The last row is the one to internalise. An over-long access token request fails immediately and visibly, at the moment you ask for it. An over-long JWT is minted happily and rejected later by whatever receives it — so the same mistake is a build-time error in one case and a runtime error in the other, against a service you may not control.
Delegation chains, and what they do not do
A delegated request involves more than two identities: the caller, one or more intermediaries, and finally the account the credential is for. Each account grants the Service Account Token Creator role to the previous one in the chain.
The property worth quoting is what the middle does not do. An intermediary only passes on the request — it does not give the caller or the final account any additional access. A chain is a route, not an accumulation. If you were hoping that passing through a privileged account would lend its privileges onward, it does not, and that is the design working correctly.
Google's own guidance is to avoid the mechanism where possible: if you can generate a token with the required permissions with a single token generation call, you should create short-lived credentials for that service account directly.
Why This Architecture Holds Up
One line in the JWT section deserves more attention than its placement suggests. Among the listed uses is treating a service account as an identity provider by signing a JWT that contains arbitrary claims about a user, account, or device.
That is a larger capability than it appears. A service account can assert facts — about a person, a machine, anything — signed with a key that is verifiable by anyone and held by nobody. It is a small identity provider with the private key problem already solved, because the key never leaves Google and you never had a copy to protect.
The caution follows from the same property. Nothing validates the content of those claims. The signature proves the service account asserted them; it proves nothing about whether they are true. So the trust you are establishing is exactly the trust you place in whatever code decided what to put in the payload — and anybody who can impersonate that service account can assert anything in its name, with a valid signature. #41's point about auditing who holds getAccessToken applies here with more force than it did for access tokens, because a forged claim outlives the token that carried it.
What to do with this
- Pick the type from the verifier. Google APIs take access tokens; audience-checking services take ID tokens; your own code takes whatever you decide to validate.
- Do not build refresh logic. There is no refresh token; renewal is another impersonation, and a library already does it.
- Set JWT expiry from the destination — one hour for Google APIs, up to twelve elsewhere.
- Expect JWT expiry errors at runtime, not at generation. The recipient enforces it.
- Use a chain only to cross a boundary, and prefer a direct credential when one call is enough.
- Treat signing authority as assertion authority. Whoever can impersonate the account can make it say anything.
Key Architecture Decisions
| Decision | Choose this | Because |
|---|---|---|
| Calling a Google API | Access token | Accepted for authentication by most Google APIs. |
| Calling a service that checks an audience | ID token | It carries identity rather than permissions. |
| Asserting claims between your own services | Self-signed JWT | The account can act as an identity provider. |
| Signing arbitrary bytes | Signed blob | It uses the system-managed private key you cannot hold. |
| Token renewal | Re-impersonate | There is no refresh token. |
| A job longer than an hour | The lifetime extension list constraint | Default maximum is 1 hour; 12 hours per named account. |
| JWT expiry for a Google API | No more than 1 hour | The 12-hour ceiling does not apply to Google APIs. |
| Reaching an account you cannot call directly | A delegation chain | Each link grants Token Creator to the previous one. |
| Hoping a chain adds privilege | It does not | The intermediary only passes on the request. |
Closing Thought
Three posts ago the argument against service account keys rested on a comparison: a static file you hold, against a managed key you do not. The obvious objection to that is capability — surely holding the key lets you do things the managed one will not. This API is the answer, and the answer is no. Sign a blob, mint a token, assert a claim, authenticate to anything: all of it is available through a key that never leaves Google, because possession was never what made the key useful.
What possession bought was the ability to use the key without asking, which is the same thing as using it without being recorded — and that is precisely what #40's non-repudiation threat was about. The escrow does not take a capability away; it inserts a step where you have to say who you are. Everything in this post is the shape of that step, repeated four times for four audiences, with two different clocks and one chain that deliberately carries nothing but a request.
#43 takes the mechanism outside Google entirely: Workload Identity Federation for AWS — how a workload holding AWS credentials gets a Google token without anyone issuing it a Google credential at all.
Comments