{"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":"validation","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/0c51461d27177d997e14c642fe18c11668fc09a3/credentials/oauth/oauth.go#L89-L125","documentation":"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.","triggerScenarios":"Dialing with insecure.NewCredentials() while using oauth.NewJWTAccessFromFile/FromKey as the per-RPC credential; a credentials bundle whose transport negotiates below PrivacyAndIntegrity.","commonSituations":"Local dev with TLS disabled; service mesh stripping TLS on the hop where the credential runs.","solutions":["Dial with credentials.NewTLS(&tls.Config{}).","Use a self-signed cert for local testing instead of insecure.NewCredentials().","Run the credential on the secure hop when behind a TLS-terminating proxy."],"exampleFix":"// before\nconn, _ := grpc.Dial(addr, grpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithPerRPCCredentials(jwtCreds))\n// after\nconn, _ := grpc.Dial(addr, grpc.WithTransportCredentials(credentials.NewTLS(&tls.Config{})), grpc.WithPerRPCCredentials(jwtCreds))","handlingStrategy":"validation","validationCode":"conn, err := grpc.Dial(addr,\n    grpc.WithTransportCredentials(credentials.NewTLS(&tls.Config{})),\n    grpc.WithPerRPCCredentials(jwtAccessCreds),\n)","typeGuard":null,"tryCatchPattern":"if status.Code(err) == codes.Unauthenticated && strings.Contains(err.Error(), \"jwtAccess PerRPCCredentials\") {\n    log.Fatal(\"jwtAccess credential requires TLS transport\")\n}","preventionTips":["Pair NewJWTAccessFromKey/File with TLS transport credentials.","Use self-signed certs for local testing.","Add a lint check forbidding insecure.NewCredentials alongside jwtAccess creds."],"tags":["grpc","oauth","jwt","tls","security","credentials"],"backgroundTag":null,"analyzedSha":"0c51461d27177d997e14c642fe18c11668fc09a3","analyzedAt":"2026-08-11T14:49:15.055Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}