grpc/grpc-go · error

unable to transfer jwtAccess PerRPCCredentials: %v

Error message

unable to transfer jwtAccess PerRPCCredentials: %v

What it means

Thrown from jwtAccess.GetRequestMetadata (oauth.go:107) when CheckSecurityLevel fails for credentials built via oauth.NewJWTAccessFromFile/NewJWTAccessFromKey. The self-signed JWT is a bearer token, so gRPC refuses to send it over a connection below PrivacyAndIntegrity. RequireTransportSecurity() returns true.

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 03255a9237)

Solutions

  1. Add TLS transport credentials to the dial (credentials.NewTLS / NewClientTLSFromFile).
  2. On Google infrastructure use alts credentials to satisfy the security-level check.
  3. Drop the per-RPC credentials if plaintext is genuinely intended.

Example fix

// before
creds, _ := oauth.NewJWTAccessFromFile(keyPath)
conn, _ := grpc.NewClient(addr,
    grpc.WithTransportCredentials(insecure.NewCredentials()),
    grpc.WithPerRPCCredentials(creds),
)

// after
creds, _ := oauth.NewJWTAccessFromFile(keyPath)
conn, _ := grpc.NewClient(addr,
    grpc.WithTransportCredentials(credentials.NewClientTLSFromCert(caPool, "")),
    grpc.WithPerRPCCredentials(creds),
)
Defensive patterns

Strategy: validation

Validate before calling

// Pair jwtAccess creds with TLS.
tlsCreds := credentials.NewClientTLSFromCert(caPool, "")
jwtCreds, err := oauth.NewJWTAccessFromFile(keyPath)
if err != nil { return err }
conn, err := grpc.NewClient(addr,
    grpc.WithTransportCredentials(tlsCreds),
    grpc.WithPerRPCCredentials(jwtCreds),
)

Try / catch

if st, ok := status.FromError(err); ok && st.Code() == codes.Unavailable {
    if strings.Contains(st.Message(), "jwtAccess PerRPCCredentials") {
        // re-dial with TLS transport credentials
    }
}

Prevention

When it happens

Trigger: Dialing with insecure.NewCredentials() (or no transport creds) while attaching jwtAccess per-RPC credentials from a service-account key.

Common situations: Local development with plaintext to avoid cert management; a downgrade to insecure during load testing that was not reverted; copying example code that omitted TLS.

Related errors


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