grpc/grpc-go · error

unable to transfer jwtAccess PerRPCCredentials

Error message

unable to transfer jwtAccess PerRPCCredentials: %v

What it means

Returned by jwtAccess.GetRequestMetadata when CheckSecurityLevel finds the transport below PrivacyAndIntegrity. jwtAccess signs a self-signed JWT per RPC and attaches it as a bearer token, so gRPC refuses to send it over an insecure channel. The %v is the security-level error. Trigger and fix mirror errors 215/218/219.

Solutions

  1. Dial with credentials.NewTLS(&tls.Config{}).
  2. Use a self-signed cert for local testing instead of insecure.NewCredentials().
  3. Run the credential on the secure hop when behind a TLS-terminating proxy.

Example fix

// before
conn, _ := grpc.Dial(addr, grpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithPerRPCCredentials(jwtCreds))
// after
conn, _ := grpc.Dial(addr, grpc.WithTransportCredentials(credentials.NewTLS(&tls.Config{})), grpc.WithPerRPCCredentials(jwtCreds))
Defensive patterns

Strategy: validation

Validate before calling

conn, err := grpc.Dial(addr,
    grpc.WithTransportCredentials(credentials.NewTLS(&tls.Config{})),
    grpc.WithPerRPCCredentials(jwtAccessCreds),
)

Try / catch

if status.Code(err) == codes.Unauthenticated && strings.Contains(err.Error(), "jwtAccess PerRPCCredentials") {
    log.Fatal("jwtAccess credential requires TLS transport")
}

Prevention

When it happens

Trigger: Dialing with insecure.NewCredentials() while using oauth.NewJWTAccessFromFile/FromKey as the per-RPC credential; a credentials bundle whose transport negotiates below PrivacyAndIntegrity.

Common situations: Local dev with TLS disabled; service mesh stripping TLS on the hop where the credential runs.

Related errors


AI-assisted analysis of grpc/grpc-go@0c51461d27 (2026-08-11). Data as JSON: /api/errors/9612dec2fafff0d0. Report an issue: GitHub.

Appendix: source

Thrown at credentials/oauth/oauth.go:107

	// Remove RPC service name from URI that will be used as audience
	// in a self-signed JWT token. It follows https://google.aip.dev/auth/4111.
	aud, err := removeServiceNameFromJWTURI(uri[0])
	if err != nil {
		return nil, err
	}
	// TODO: the returned TokenSource is reusable. Store it in a sync.Map, with
	// uri as the key, to avoid recreating for every RPC.
	ts, err := google.JWTAccessTokenSourceFromJSON(j.jsonKey, aud)
	if err != nil {
		return nil, err
	}
	token, err := ts.Token()
	if err != nil {
		return nil, err
	}
	ri, _ := credentials.RequestInfoFromContext(ctx)
	if err = credentials.CheckSecurityLevel(ri.AuthInfo, credentials.PrivacyAndIntegrity); err != nil {
		return nil, fmt.Errorf("unable to transfer jwtAccess PerRPCCredentials: %v", err)
	}
	return map[string]string{
		"authorization": token.Type() + " " + token.AccessToken,
	}, nil
}

func (j jwtAccess) RequireTransportSecurity() bool {
	return true
}

// oauthAccess supplies PerRPCCredentials from a given token.
type oauthAccess struct {
	token oauth2.Token
}

// NewOauthAccess constructs the PerRPCCredentials using a given token.
//
// Deprecated: use oauth.TokenSource instead.

View on GitHub (pinned to 0c51461d27)