cloudflare/cloudflared · error

failed to parse JWT

Error message

failed to parse JWT

What it means

sshgen.SignCert parses the Cloudflare Access short-lived JWT (cf-access token) supplied by the user using go-jose's jwt.ParseSigned with the allowed signature algorithms. If the token string is not a well-formed JWS (wrong format, truncated, tampered, or empty-looking), the parse fails and the error is wrapped as 'failed to parse JWT'. No signature verification has happened yet at this stage — this is purely a syntax/structure failure.

Source

Thrown at sshgen/sshgen.go:95

// handleCertificateGeneration takes a JWT and uses it build a signPayload
// to send to the Sign endpoint with the public key from the keypair it generated
func handleCertificateGeneration(token, fullName string) (string, error) {
	pub, err := generateKeyPair(fullName)
	if err != nil {
		return "", err
	}

	return SignCert(token, string(pub))
}

func SignCert(token, pubKey string) (string, error) {
	if token == "" {
		return "", errors.New("invalid token")
	}

	parsedToken, err := jwt.ParseSigned(token, signatureAlgs)
	if err != nil {
		return "", errors.Wrap(err, "failed to parse JWT")
	}

	claims := jwt.Claims{}
	err = parsedToken.UnsafeClaimsWithoutVerification(&claims)
	if err != nil {
		return "", errors.Wrap(err, "failed to retrieve JWT claims")
	}

	buf, err := json.Marshal(&signPayload{
		PublicKey: pubKey,
		JWT:       token,
		Issuer:    claims.Issuer,
	})
	if err != nil {
		return "", errors.Wrap(err, "failed to marshal signPayload")
	}
	var res *http.Response
	if mockRequest != nil {

View on GitHub (pinned to 2253eeeb25)

Solutions

  1. Re-copy the full Cloudflare Access JWT (the CF_Authorization cookie value) — ensure nothing was truncated or had whitespace added.
  2. Verify the token has the three dot-separated JWS parts (header.payload.signature).
  3. Re-authenticate to Cloudflare Access in the browser to obtain a fresh token before running the command.
  4. Confirm you are passing the token to the right flag/env var for the sshgen command, not a different credential.

Example fix

// before
$ export ACCESS_TOKEN=eyJhbGciOi (truncated paste)
$ cloudflared access ssh-gen --token $ACCESS_TOKEN
// after
$ export ACCESS_TOKEN=$(cat ~/cf_access_token)  # full JWT, no newlines
$ cloudflared access ssh-gen --token "$ACCESS_TOKEN"
Defensive patterns

Strategy: validation

Validate before calling

// validate token shape before invoking SignCert
func validJWSShape(token string) bool {
    token = strings.TrimSpace(token)
    if token == "" {
        return false
    }
    parts := strings.Split(token, ".")
    return len(parts) == 3 && parts[0] != "" && parts[1] != "" && parts[2] != ""
}
if !validJWSShape(token) {
    return errors.New("token is not a well-formed JWT (expected header.payload.signature)")
}

Type guard

func isLikelyJWT(s string) bool {
    s = strings.TrimSpace(s)
    parts := strings.Split(s, ".")
    return len(parts) == 3
}

Try / catch

token, err := signCert(ctx, token)
if err != nil {
    if strings.Contains(err.Error(), "failed to parse JWT") {
        return fmt.Errorf("the provided Cloudflare Access token is malformed; re-copy the full CF_Authorization value: %w", err)
    }
    return err
}

Prevention

When it happens

Trigger: jwt.ParseSigned(token, signatureAlgs) errors: the token passed to cloudflared ssh-gen / certificate generation is empty, truncated, base64-corrupt, or not a JWS compact serialization at all.

Common situations: User pasted only part of the Access token, copied the wrong header (e.g. a CSP nonce or session cookie instead of CF_Authorization), environment variable with the token unset or containing whitespace/newlines, or an old token format from a deprecated Access flow.

Understand the failure class

Related errors


AI-assisted analysis of cloudflare/cloudflared@2253eeeb25 (2026-09-06). Data as JSON: /api/errors/d8eaa704bcaead55. Report an issue: GitHub.