# cli/auth: add `token --jurisdiction` for jurisdictional identity tokens

`2b08ff1`·

georg·2w ago·8 files·+457 added/-69 removed

`entire auth token` printed only the control-plane bearer, which the
per-jurisdiction entire-api cells (e.g. https://aws-us-east-2.api.entire.io/api/v1)
reject. Add `--jurisdiction <slug>` to mint a jurisdictional identity token
(RFC 8693 exchange, scope=openid, aud=<jurisdiction host>) for that
jurisdiction's cells, reusing the existing exchange pipeline in
cell_data_api.go. It exchanges ENTIRE_TOKEN when set (deriving the
environment from the env token's aud so it works with no ENTIRE_API_BASE_URL,
else the active stored login.

Also surface the caller's home jurisdiction in Logged in to https://us.auth.entire.io  
User: Georg Friedrich @georg  
Identity: github/1940  
Context: us.auth.entire.io  
Token: stored in OS keychain

Active sessions (32):
NAME           CREATED       LAST USED     EXPIRES
OIDC login     2026-06-30   2026-06-30    2026-07-03   2026-08-02
OIDC login     2026-06-30   2026-06-30    2026-07-03   2026-08-02
OIDC login     2026-07-01   2026-07-01    2026-07-03   2026-08-02
Console login  2026-07-02   2026-07-02    2026-07-02   2026-07-09
Console login  2026-07-01   2026-07-01    2026-07-01   2026-07-08
OIDC login     2026-06-29   2026-06-29    2026-06-30   2026-07-30
Console login  2026-06-30   2026-06-30    2026-06-30   2026-07-07
OIDC login     2026-06-25   2026-06-25    2026-06-29   2026-07-29
Console login  2026-06-29   2026-06-29    2026-06-29   2026-07-06
OIDC login     2026-06-22   2026-06-22    2026-06-29   2026-07-29
OIDC login     2026-06-18   2026-06-18    2026-06-25   2026-07-25
OIDC login     2026-06-24   2026-06-24    2026-06-24   2026-07-24
OIDC login     2026-06-16   2026-06-16    2026-06-21   2026-07-21
OIDC login     2026-06-19   2026-06-19    2026-06-19   2026-07-19
OIDC login     2026-06-14   2026-06-14    2026-06-18   2026-07-18
OIDC login     2026-06-13   2026-06-13    2026-06-13   2026-07-13
OIDC login     2026-06-11   2026-06-11    2026-06-12   2026-07-12
OIDC login     2026-06-11   2026-06-11    2026-06-11   2026-07-11
OIDC login     2026-06-09   2026-06-09    2026-06-09   2026-07-09
OIDC login     2026-06-08   2026-06-08    2026-06-08   2026-07-08
OIDC login     2026-06-08   2026-06-08    2026-06-08   2026-07-08
OIDC login     2026-06-08   2026-06-08    2026-06-08   2026-07-08
OIDC login     2026-06-07   2026-06-07    2026-06-07   2026-07-07
OIDC login     2026-06-06   2026-06-06    2026-06-06   2026-07-06
OIDC login     2026-06-06   2026-06-06    2026-06-06   2026-07-06
OIDC login     2026-06-06   2026-06-06    2026-06-06   2026-07-06
OIDC login     2026-06-05   2026-06-05    2026-06-05   2026-07-05
OIDC login     2026-06-05   2026-06-05    2026-06-05   2026-07-05
OIDC login     2026-06-05   2026-06-05    2026-06-05   2026-07-05
OIDC login     2026-06-05   2026-06-05    2026-06-05   2026-07-05
OIDC login     2026-06-05   2026-06-05    2026-06-05   2026-07-05
OIDC login     2026-06-04   2026-06-04    2026-06-04   2026-07-04

Run 'entire logout' to end this session, or 'entire logout --everywhere' to end all of them.

2 login contexts saved; run 'entire auth contexts' to list or 'entire auth use <name>' to switch.

Update available! 0.7.9-nightly.202607020716.b58b5fe8e -> 0.7.9-nightly.202607030712.7b6b53b52  
Release notes: https://github.com/entireio/cli/releases/tag/v0.7.9-nightly.202607030712.7b6b53b52
To update, run:
brew upgrade entire@nightly so the
slug is discoverable non-interactively.

--- token ------------------------------------------------------------------

// newAuthTokenCmd prints the active control-plane bearer to stdout for
// scripting. The user-facing Long and Example carry the detail and the
// "treat the output as a secret" caveat; the token resolves the same way the
// API client's does (ENTIRE_TOKEN verbatim when set, otherwise the active
// context's login JWT, refreshed if it's near expiry), and only the token is
// printed — errors and the not-logged-in hint go to stderr so command
// substitution stays clean.
func newAuthTokenCmd() *cobra.Command {
	var insecureHTTPAuth bool
	var jurisdiction string
	cmd := &cobra.Command{
		Use:   "token",
		Short: "Print the active control-plane bearer token — a live credential, treat as a secret",
		Long: "Print the active control-plane bearer token to stdout so scripts and\n" +
		"ad-hoc curl can authenticate against the control-plane API.\n\n" +
		"The output is a live credential — treat it as a secret. It is the same\n" +
		"bearer the API client uses: ENTIRE_TOKEN verbatim when set, otherwise the\n" +
		"active context's login JWT (refreshed if it's near expiry). Only the token\n" +
		"is printed to stdout; errors and the not-logged-in hint go to stderr so\n" +
		"command substitution stays clean.",
		Example: "  curl -H \"Authorization: Bearer $(entire auth token)\" \"https://us.console.entire.io/api/v1/clusters\"",
		Args:    cobra.NoArgs,
		RunE: func(cmd *cobra.Command, _ []string) error {
			// Refresh may exchange/refresh over the network; honor the\n			// plain-HTTP opt-in before resolving so local dev cores work.
			insecure := applyInsecureHTTPAuth(insecureHTTPAuth)

// --jurisdiction mints a data-plane cell identity token instead of the
			// control-plane bearer. JurisdictionToken performs its own TLS/exchange
			// guards and returns context-rich errors.
			if strings.TrimSpace(jurisdiction) != "" {
				token, err := auth.JurisdictionToken(cmd.Context(), insecure, jurisdiction)
				if err != nil {
					cmd.SilenceUsage = true
					if errors.Is(err, auth.ErrNotLoggedIn) {
						fmt.Fprintln(cmd.ErrOrStderr(), "Not logged in. Run 'entire login' to authenticate.")
						return NewSilentError(err)
					}
					return err //nolint:wrapcheck // JurisdictionToken already returns contextual auth errors
				}
				fmt.Fprintln(cmd.OutOrStdout(), token)
				return nil
			}

target, err := resolveAuthStatusTarget(cmd.Context(), auth.Contexts, auth.RefreshedLoginToken)
			if err != nil {
				return err
			}
    
		},
	}
	addInsecureHTTPAuthFlag(cmd, &insecureHTTPAuth)
	cmd.Flags().StringVar(&jurisdiction, "jurisdiction", "", "mint a jurisdictional identity token for this jurisdiction slug (e.g. us, eu) for use against that jurisdiction's entire-api cells")
	return cmd
}....

// JurisdictionToken mints and returns a jurisdictional identity token
// (scope=openid, aud=jurisdiction host) for `jurisdiction`, for authenticating
// against that jurisdiction's entire-api cells (e.g.
// https://aws-us-east-2.api.entire.io/api/v1). Unlike NewEntireAPICellClient it
// returns the raw token string (it skips the cell-base-URL resolution, which is
// only needed to build a client) and it honours ENTIRE_TOKEN.

// Subject credential precedence:
//   - ENTIRE_TOKEN set: the env token is the exchange subject_token, and its own
//     aud core drives the environment family (so this works with only
//     ENTIRE_TOKEN set, no ENTIRE_API_BASE_URL, in prod/staging/loopback).
//     Presence is exclusive and fail-closed — a malformed/blank value errors
//     rather than falling back to a stored login. The env token must be a login
//     JWT (subject-capable); a rejected exchange surfaces the server error.
//   - otherwise: the active stored context's refreshed login JWT.

// An empty `jurisdiction` falls back to the subject token's home_jurisdiction
// claim.
func JurisdictionToken(ctx context.Context, insecureHTTP bool, jurisdiction string) (string, error) {
	subject, err := resolveCellSubject(ctx, insecureHTTP)
	if err != nil {
		return "", err
	}

j, err := resolveJurisdiction(jurisdiction, subject.loginJWT)
	if err != nil {
		return "", err
	}

coreURL := jurisdictionCoreURL(j, subject.dataOrigin, subject.discoveredCore)
	if err := requireSafeExchangeURL("entire-core", coreURL); err != nil {
		return "", err
	}

audience := jurisdictionAudience(j, subject.dataOrigin, subject.discoveredCore)
	token, err := exchangeJurisdictionToken(ctx, coreURL, subject.loginJWT, audience, subject.httpClient)
	if err != nil {
		return "", fmt.Errorf("exchange jurisdictional identity token: %w", err)
	}
	return token, nil
}
