{"record":{"id":"9612dec2fafff0d0","repo":"grpc/grpc-go","slug":"unable-to-transfer-jwtaccess-perrpccredentials-v","errorCode":null,"errorMessage":"unable to transfer jwtAccess PerRPCCredentials: %v","messagePattern":"unable to transfer jwtAccess PerRPCCredentials: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"credentials/oauth/oauth.go","lineNumber":107,"sourceCode":"\t// Remove RPC service name from URI that will be used as audience\n\t// in a self-signed JWT token. It follows https://google.aip.dev/auth/4111.\n\taud, err := removeServiceNameFromJWTURI(uri[0])\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\t// TODO: the returned TokenSource is reusable. Store it in a sync.Map, with\n\t// uri as the key, to avoid recreating for every RPC.\n\tts, err := google.JWTAccessTokenSourceFromJSON(j.jsonKey, aud)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\ttoken, err := ts.Token()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tri, _ := credentials.RequestInfoFromContext(ctx)\n\tif err = credentials.CheckSecurityLevel(ri.AuthInfo, credentials.PrivacyAndIntegrity); err != nil {\n\t\treturn nil, fmt.Errorf(\"unable to transfer jwtAccess PerRPCCredentials: %v\", err)\n\t}\n\treturn map[string]string{\n\t\t\"authorization\": token.Type() + \" \" + token.AccessToken,\n\t}, nil\n}\n\nfunc (j jwtAccess) RequireTransportSecurity() bool {\n\treturn true\n}\n\n// oauthAccess supplies PerRPCCredentials from a given token.\ntype oauthAccess struct {\n\ttoken oauth2.Token\n}\n\n// NewOauthAccess constructs the PerRPCCredentials using a given token.\n//\n// Deprecated: use oauth.TokenSource instead.","sourceCodeStart":89,"sourceCodeEnd":125,"githubUrl":"https://github.com/grpc/grpc-go/blob/03255a9237b6eb32710f6bc4f2de9a675b99fe36/credentials/oauth/oauth.go#L89-L125","documentation":"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.","triggerScenarios":"Dialing with insecure.NewCredentials() (or no transport creds) while attaching jwtAccess per-RPC credentials from a service-account key.","commonSituations":"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.","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."],"exampleFix":"// before\ncreds, _ := oauth.NewJWTAccessFromFile(keyPath)\nconn, _ := grpc.NewClient(addr,\n    grpc.WithTransportCredentials(insecure.NewCredentials()),\n    grpc.WithPerRPCCredentials(creds),\n)\n\n// after\ncreds, _ := oauth.NewJWTAccessFromFile(keyPath)\nconn, _ := grpc.NewClient(addr,\n    grpc.WithTransportCredentials(credentials.NewClientTLSFromCert(caPool, \"\")),\n    grpc.WithPerRPCCredentials(creds),\n)","handlingStrategy":"validation","validationCode":"// Pair jwtAccess creds with TLS.\ntlsCreds := credentials.NewClientTLSFromCert(caPool, \"\")\njwtCreds, err := oauth.NewJWTAccessFromFile(keyPath)\nif err != nil { return err }\nconn, err := grpc.NewClient(addr,\n    grpc.WithTransportCredentials(tlsCreds),\n    grpc.WithPerRPCCredentials(jwtCreds),\n)","typeGuard":null,"tryCatchPattern":"if st, ok := status.FromError(err); ok && st.Code() == codes.Unavailable {\n    if strings.Contains(st.Message(), \"jwtAccess PerRPCCredentials\") {\n        // re-dial with TLS transport credentials\n    }\n}","preventionTips":["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."],"tags":["oauth","jwt","security","transport","call-credentials","go"],"analyzedSha":"03255a9237b6eb32710f6bc4f2de9a675b99fe36","analyzedAt":"2026-08-07T00:29:34.215Z","schemaVersion":2},"datasetVersion":"2026-08-07T03:17:09.362Z"}