---
title: "Service-account tokens — Kubernetes API for Laravel | Roundly"
description: "Mint short-lived service-account tokens like kubectl create token — lifetime, audiences and binding, with input checked before any request."
url: https://roundly-consulting.com/open-source/docs/kubernetes-api-for-laravel/service-account-tokens
language: en
---

[All packages](https://roundly-consulting.com/open-source.md)

[Kubernetes API for Laravel](https://roundly-consulting.com/open-source/docs/kubernetes-api-for-laravel.md)

# Service-account tokens

ServiceAccount::requestToken() mints a short-lived token the way kubectl create token does: a TokenRequest (authentication.k8s.io/v1) on the service account’s token subresource. Only the fields you give go into its spec — expirationSeconds, audiences, boundObjectRef — so the apiserver fills in its own defaults (usually an hour and the cluster’s audiences) and may shorten the lifetime. You get back a ServiceAccountToken:

```php
use RoundlyConsulting\KubernetesApi\Facades\Kubernetes;

$token = Kubernetes::namespace('ci')->serviceAccounts()->setName('deployer')->requestToken(
    expirationSeconds: 900,   // 600 or more; null = the apiserver's default (usually an hour)
    audiences: ['vault'],     // empty = the apiserver's default audiences
    boundTo: $pod,            // an existing Pod, Secret or Node: deleting it invalidates the token
);

$token->token;            // the bearer token — masked in var_dump(), print_r() and dump()
$token->expiresAt;        // CarbonImmutable, UTC
$token->audiences;        // list<string>, as the apiserver answered
$token->boundObjectRef;   // ['kind' => 'Pod', 'apiVersion' => 'v1', 'name' => …, 'uid' => …] or null
$token->isExpired();      // or isExpired($at)

// Use it like any other token
Kubernetes::url('https://api.prod.example.com:6443')->withToken($token->token)->pods()->get();
```

## ServiceAccountToken

| Property · method | Holds |
| --- | --- |
| `token` | The bearer token — a #\[SensitiveParameter\], masked in var\_dump(), print\_r() and dump(). |
| `expiresAt` | When the token expires, as a CarbonImmutable in UTC. |
| `audiences` | list<string> — the audiences the apiserver answered. |
| `boundObjectRef` | kind, apiVersion, name and uid of the object the token is bound to, or null. |
| `isExpired(?CarbonInterface $at = null)` | Whether the token has expired by $at — now by default. |

## Checked before any request

Input the apiserver would refuse throws InvalidResourceException before anything is sent:

- an unnamed service account — setName() first;
- a dryRun() resource — a dry-run TokenRequest mints nothing;
- expirationSeconds under 600 (ServiceAccount::MIN\_TOKEN\_EXPIRATION\_SECONDS);
- a blank audience;
- a boundTo that isn’t a Pod, Secret or Node (ServiceAccount::TOKEN\_BINDABLE\_KINDS), or lacks metadata.name and metadata.uid — find() it first.

## Failures and permissions

- Failures are always body-free — HTTP 403 Forbidden, HTTP 404 NotFound — whatever the cluster’s redaction setting, because this is a credential endpoint. $e->apiMessage() still reads the apiserver’s Status message.
- An answer without a non-empty status.token or an RFC 3339 status.expirationTimestamp throws KubernetesException::malformedResponse().
- The request goes through the cluster’s transport, so client.options.timeout, the rate limiter and Kubernetes::fake() all apply. The cluster’s own credentials need create on serviceaccounts/token.

## From any entry point

requestToken() is a resource operation like any other, so it works from the facade, an injected manager or any cluster — including an ad-hoc one that carries a credential allowed to mint tokens:

```php
// Through an injected manager or any cluster, like every resource operation
$token = $this->kubernetes->cluster('dev')->serviceAccounts()
    ->setNamespace('ci')
    ->setName('deployer')
    ->requestToken();

// An ad-hoc cluster whose own credential may mint tokens
$token = Kubernetes::url($server)->withToken($minter)->serviceAccounts()
    ->setNamespace($namespace)
    ->setName($serviceAccount)
    ->requestToken($ttl);
```

Under Kubernetes::fake() a token request answers for seeded service accounts with a deterministic token, and assertTokenRequested() checks it — see Testing.

## Show your open-source love

This package is free and MIT-licensed. If it saves you time, a one-off donation or a Patreon membership keeps it maintained, tested and documented.

[More ways to support, including crypto](https://roundly-consulting.com/support-us.md)

By donating, you agree to our [donation terms](https://roundly-consulting.com/donation-terms.md).

[Support our open source work (opens in a new tab)](https://donate.stripe.com/dRmeVe8FX5PF1Qd9pXcEw00) [Join us on Patreon (opens in a new tab)](https://www.patreon.com/cw/roundly)

## Want this built into your product?

We integrate our packages into custom Laravel and AI builds. Tell us what you're working on and we'll reply within 48 hours.

[Get a quote in 48 hours](https://roundly-consulting.com/contact.md) [Browse all packages](https://roundly-consulting.com/open-source/docs/kubernetes-api-for-laravel.md)
