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
- Add TLS transport credentials to the dial (credentials.NewTLS / NewClientTLSFromFile).
- On Google infrastructure use alts credentials to satisfy the security-level check.
- 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
- Treat jwtAccess creds as bearer secrets: TLS-only.
- In dev use a local CA; avoid insecure creds entirely.
- Code-review dial options to ensure tls creds accompany jwtAccess.
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
- cannot send secure credentials on an insecure connection: %v
- unable to transfer TokenSource PerRPCCredentials: %v
- unable to transfer oauthAccess PerRPCCredentials: %v
- unable to transfer serviceAccount PerRPCCredentials: %v
- unable to transfer STS PerRPCCredentials: %v
AI-assisted analysis of grpc/grpc-go@03255a9237 (2026-08-07).
Data as JSON: /api/errors/9612dec2fafff0d0.
Report an issue: GitHub.