grpc / grpc-web

gRPC for Web Clients
https://grpc.io
Apache License 2.0
8.58k stars 763 forks source link

Cannot set an httpOnly cookie from python GRPC server. Envoy configs not correct? #1018

Open kurnal-volt opened 3 years ago

kurnal-volt commented 3 years ago

Title: Cannot set an httpOnly cookie from python GRPC server. Envoy configs not correct?

Description: I am trying to set an httpOnly Authorization cookie from my gRPC python server through an envoy proxy and back to my ReactJS front-end client running grpc-web. Currently, I can send a JWT back in a response, set it into local storage, and append it onto subsequent calls using a client side grpc-web-interceptor; however, I do know that using local storage for a JWT is not best practice. How can I make it so that running context.set_trailing_metadata('set-cookie', 'mycookie') from my python gRPC server actually set the cookie, because currently I do not see anything in the storage tag of development tools but I also simultaneously see the response cookie when checking the API call in the response tab.

Also, would it be possible to make envoy grab the authorization cookie on every request if it is there? I have read so many posts, but none of them match what I need to achieve.

swuecho commented 3 years ago

check: https://github.com/grpc/grpc-web/issues/351#issuecomment-445565737

cookie will send to grpc server automatically if it is same domain. and you could get the cookie from the metadata.

L0mchansky commented 6 days ago

It's 2024, the question is still relevant)

I have the same configuration. Client - react-app (grpc-web) (192.168.3.2:3000) Proxy server envoy (is on a virtual machine, 192.168.233.129:8080) Backend - java-auth-ms (grpc) (192.168.3.2:8101)

Call method from client:

const grpcPromise = new Promise((resolve, reject) => {
    global.grpcAPI.AccountService.loginUser(
      request,
      { withCredentials: "true" },
      (err, response) => {
        if (!err) {
          resolve({
            status: response.getStatus(),
            msg: response.getMsg(),
          });
        } else {
          reject(err);
        }
      }
    );
  });

envoy configuration:

admin:
  access_log_path: /tmp/admin_access.log
  address:
    socket_address:
      address: 0.0.0.0
      port_value: 9901

static_resources:
  listeners:
    - name: listener_0
      address:
        socket_address:
          address: 0.0.0.0
          port_value: 8080
      filter_chains:
        - filters:
            - name: envoy.filters.network.http_connection_manager
              typed_config:
                "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
                codec_type: auto
                stat_prefix: ingress_http
                route_config:
                  name: local_route
                  virtual_hosts:
                    - name: local_service
                      domains: ["*"]
                      routes:
                        - match:
                            prefix: "/"
                          route:
                            cluster: grpc_backend
                            timeout: 0s
                            max_stream_duration:
                              grpc_timeout_header_max: 0s
                      cors:
                        allow_origin_string_match:
                          - prefix: "*"
                        allow_methods: GET, PUT, DELETE, POST, OPTIONS
                        allow_headers: keep-alive,user-agent,cache-control,content-type,content-transfer-encoding,custom-header-1,x-accept-content-transfer-encoding,x-accept-response-streaming,x-user-agent,x-grpc-web,grpc-timeout,withcredentials,set-cookie
                        allow_credentials: true
                        max_age: "1728000"
                        expose_headers: custom-header-1,grpc-status,grpc-message,set-cookie
                http_filters:
                  - name: envoy.filters.http.grpc_web
                    typed_config:
                      "@type": type.googleapis.com/envoy.extensions.filters.http.grpc_web.v3.GrpcWeb
                  - name: envoy.filters.http.cors
                    typed_config:
                      "@type": type.googleapis.com/envoy.extensions.filters.http.cors.v3.Cors
                  - name: envoy.filters.http.router
                    typed_config:
                      "@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
  clusters:
    - name: grpc_backend
      connect_timeout: 2.25s
      type: logical_dns
      typed_extension_protocol_options:
        envoy.extensions.upstreams.http.v3.HttpProtocolOptions:
          "@type": type.googleapis.com/envoy.extensions.upstreams.http.v3.HttpProtocolOptions
          explicit_http_config:
            http2_protocol_options: {}
      lb_policy: round_robin
      load_assignment:
        cluster_name: grpc_backend
        endpoints:
          - lb_endpoints:
              - endpoint:
                  address:
                    socket_address:
                      address: 192.168.3.2 #host-machine (java-auth-ms)
                      port_value: 8101

grpc interceptor on java-auth-ms: (Set "30ha" token manually for testing)

@Override
public <ReqT, RespT> ServerCall.Listener<ReqT> interceptCall(
        ServerCall<ReqT, RespT> call,
        Metadata headers,
        ServerCallHandler<ReqT, RespT> next) {

        ServerCall<ReqT, RespT> wrappedCall = new ForwardingServerCall.SimpleForwardingServerCall<>(call) {
            @Override
            public void sendHeaders(Metadata responseHeaders) {

                Metadata.Key<String> SET_COOKIE_HEADER = Metadata.Key.of("Set-Cookie", Metadata.ASCII_STRING_MARSHALLER);
                String jwtToken = getTokenFromContext(); // Получаем токен из контекста
                String cookie = "token=" + "30ha" + "; HttpOnly; Path=/; Max-Age=" + 24 * 3600; //24

                responseHeaders.put(SET_COOKIE_HEADER, cookie);

                super.sendHeaders(responseHeaders);
            }
        };

        return next.startCall(wrappedCall, headers);
    }

private String getTokenFromContext() {
    return TokenConstants.TOKEN_KEY.get();
}

Request and response: image

As we can see, the header contains "Set-Cookie" with the token value.

But it still doesn't fit in the storage.

image

Why can't I set cookies?

p.s. If add the "SameSite" flag, nothing changes either.