diff --git a/README.md b/README.md
index e8549bb7..10bd3ee4 100644
--- a/README.md
+++ b/README.md
@@ -80,7 +80,7 @@ The following config options are supported:
| redirectURL | | OIDC Redirect URL |
| rekor | https://rekor.sigstore.dev | Address of Rekor server |
| connectorID | | Optional Connector ID to auto-select to pre-select auth flow to use. For the public sigstore instance, valid values are:
- `https://github.com/login/oauth`
- `https://accounts.google.com`
- `https://login.microsoftonline.com` |
-| tokenProvider | | Optional OIDC token provider to use to fetch tokens. If not set, any available providers are used. valid values are:
- `interactive`
- `spiffe`
- `google-workload-identity`
- `google-impersonation`
- `github-actions`
- `filesystem`
- `buildkite-agent` |
+| tokenProvider | | Optional OIDC token provider to use to fetch tokens. If not set, any available providers are used. valid values are:
- `interactive`
- `device` (OAuth 2.0 device flow — for headless / remote-SSH workflows)
- `spiffe`
- `google-workload-identity`
- `google-impersonation`
- `github-actions`
- `filesystem`
- `buildkite-agent` |
| timestampServerURL | | Address of timestamping authority. If set, a trusted timestamp will be included in the signature. |
| timestampCertChain | | Path to PEM encoded certificate chain for RFC3161 Timestamp Authority verification. |
| autoclose | true | If true, autoclose the browser window after `autocloseTimeout`. In order for autoclose to work you must also set `connectorID`. |
@@ -93,7 +93,7 @@ The following config options are supported:
| ---------------------------- | ------------------ | -------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| GITSIGN_CREDENTIAL_CACHE | | | Optional path to [gitsign-credential-cache](cmd/gitsign-credential-cache/README.md) socket. |
| GITSIGN_CONNECTOR_ID | ✅ | | Optional Connector ID to auto-select to pre-select auth flow to use. For the public sigstore instance, valid values are:
- `https://github.com/login/oauth`
- `https://accounts.google.com`
- `https://login.microsoftonline.com` |
-| GITSIGN_TOKEN_PROVIDER | ✅ | | Optional OIDC token provider to use to fetch tokens. If not set, any available providers are used. valid values are:
- `interactive`
- `spiffe`
- `google-workload-identity`
- `google-impersonation`
- `github-actions`
- `filesystem`
- `buildkite-agent` |
+| GITSIGN_TOKEN_PROVIDER | ✅ | | Optional OIDC token provider to use to fetch tokens. If not set, any available providers are used. valid values are:
- `interactive`
- `device` (OAuth 2.0 device flow — for headless / remote-SSH workflows)
- `spiffe`
- `google-workload-identity`
- `google-impersonation`
- `github-actions`
- `filesystem`
- `buildkite-agent` |
| GITSIGN_FULCIO_URL | ✅ | https://fulcio.sigstore.dev | Address of Fulcio server |
| GITSIGN_LOG | ❌ | | Path to log status output. Helpful for debugging when no TTY is available in the environment. |
| GITSIGN_OIDC_CLIENT_ID | ✅ | sigstore | OIDC client ID for application
diff --git a/internal/config/config.go b/internal/config/config.go
index bc6149da..bd8c6881 100644
--- a/internal/config/config.go
+++ b/internal/config/config.go
@@ -77,7 +77,12 @@ type Config struct {
ConnectorID string
// TokenProviders select a OIDC token provider to use to fetch tokens. If not set, all providers are attempted.
// See https://github.com/sigstore/cosign/tree/main/pkg/providers for more details.
- // Valid values are: [interactive, spiffe, google-workload-identity, google-impersonation, github-actions, filesystem, buildkite-agent]
+ // Valid values are: [interactive, device, spiffe, google-workload-identity, google-impersonation, github-actions, filesystem, buildkite-agent]
+ //
+ // "device" runs the OAuth 2.0 device authorization grant (RFC 8628) against
+ // the configured Issuer — the user opens the printed verification URL on any
+ // browser to complete consent; the SSH session never needs a browser or
+ // port forward. Intended for headless / remote-SSH developer workflows.
TokenProvider string
// Timestamp Authority address to use to get a trusted timestamp
diff --git a/internal/fulcio/identity.go b/internal/fulcio/identity.go
index ba28505c..cc33723f 100644
--- a/internal/fulcio/identity.go
+++ b/internal/fulcio/identity.go
@@ -235,10 +235,20 @@ func (f *IdentityFactory) NewIdentity(ctx context.Context, cfg *config.Config) (
// If enabled, try OIDC token providers to get a token. unless the token provider is "interactive" (in which case always do default interactive flow).
var provider providers.Interface
- if cfg.TokenProvider == "" && providers.Enabled(ctx) {
+ switch {
+ case cfg.TokenProvider == "" && providers.Enabled(ctx):
// If no token provider is set, look for any available provider to use.
provider = defaultFlowProvider{}
- } else if cfg.TokenProvider != "" && cfg.TokenProvider != "interactive" {
+ case cfg.TokenProvider == "device":
+ // Special-case: use the OAuth 2.0 device authorization grant (RFC 8628)
+ // directly via the sigstore oauthflow package. The user opens the
+ // printed verification URL on any browser (phone, laptop, anywhere) to
+ // complete OAuth consent; the SSH session itself never needs a browser,
+ // port forward, or localhost callback. Useful on remote/headless dev
+ // hosts where the interactive flow can't open a browser locally.
+ fmt.Fprintln(f.out, "using device authorization flow") // nolint:errcheck
+ authFlow = oauthflow.NewDeviceFlowTokenGetterForIssuer(cfg.Issuer)
+ case cfg.TokenProvider != "" && cfg.TokenProvider != "interactive":
fmt.Fprintln(f.out, "using token provider", cfg.TokenProvider) // nolint:errcheck
// If a token provider is explicitly set always use it, unless it's "interactive",