Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

grpc-web: Using server streaming with authboss the call remains in pending state (and doesn't work) #1018

Open
frederikhors opened this issue Jul 6, 2021 · 5 comments

Comments

@frederikhors
Copy link

Versions of relevant software used

github.com/improbable-eng/grpc-web v0.14.0

What happened

I have been using grpc-web for a long time (it's such a pleasure, THANKS!).

I'm using it with authboss too.

Today I had to test the gRPC server streaming and after HOURS of trying I found a strange issue described below:

  • main.go:
package main

import (
	"github.com/volatiletech/authboss/v3/remember"
	"net/http"

	"github.com/go-chi/chi/v5"
	"github.com/go-chi/chi/v5/middleware"
	_ "github.com/volatiletech/authboss/v3/auth"
	_ "github.com/volatiletech/authboss/v3/logout"
)

func main() {
  // ... get config and db
  authb := InitAuthboss(config, db)
	chiRouter := chi.NewRouter()
  grpcwebServer := grpcweb.WrapServer(grpc.NewServer())

	chiRouter.Use(authb.LoadClientStateMiddleware, remember.Middleware(authb))

	chiRouter.Group(func(r chi.Router) {
		r.Use(authboss.Middleware2(authb, authboss.RequireNone, authboss.RespondUnauthorized))
		r.Post("/grpc", grpcWebServer.ServeHTTP)
	})

	chiRouter.Group(func(r chi.Router) {
		r.Mount(config.AuthURL, http.StripPrefix(config.AuthURL, authb.Config.Core.Router))
	})

	// ... startServer
}

func InitAuthboss(config *Config, dbPG *DB) *authboss.Authboss {
	var (
		ab                 = authboss.New()
		database           = newDBStorer(config, dbPG)
		cookieStoreKey, _  = base64.StdEncoding.DecodeString(config.CookieStoreKey)
		sessionStoreKey, _ = base64.StdEncoding.DecodeString(config.SessionStoreKey)
		cookieStore        = abclientstate.NewCookieStorer(cookieStoreKey, nil)
		sessionStore       = abclientstate.NewSessionStorer(config.ProjectName, sessionStoreKey, nil)
		cstore             = sessionStore.Store.(*sessions.CookieStore)
	)
	cookieStore.HTTPOnly = true
	cookieStore.Secure = config.IsProduction
	cookieStore.Domain = config.CookiesDomain
	cookieStore.SameSite = http.SameSiteStrictMode
	cookieStore.MaxAge = int(config.CookiesRemembermeMaxageTime.Seconds())
	cstore.Options.HttpOnly = true
	cstore.Options.Secure = config.IsProduction
	cstore.Options.Domain = config.CookiesDomain
	cstore.Options.SameSite = http.SameSiteStrictMode
	cstore.Options.MaxAge = int(config.CookiesSessionMaxageTime.Seconds())
	ab.Config.Paths.RootURL = config.SiteURL
	ab.Config.Storage.Server = database
	ab.Config.Storage.SessionState = sessionStore
	ab.Config.Storage.CookieState = cookieStore
	ab.Config.Modules.LogoutMethod = "GET"
	ab.Config.Core.ViewRenderer = defaults.JSONRenderer{}
	defaults.SetCore(&ab.Config, true, false)
	ab.Config.Core.Redirector = &defaults.Redirector{Renderer: &defaults.JSONRenderer{}, CorceRedirectTo200: true}

	err := ab.Init()
	if err != nil {
    panic(err)
  }

	return ab
}

I'm trying to use protobuf-ts but I don't get messages from server until connection is closed.

I'm using Svelte 3 like this:

<script lang="ts">
  import { onDestroy, onMount } from "svelte";

  const transport = new GrpcWebFetchTransport({
    baseUrl: "/",
    format: "binary",
  });

  const service = new EventServiceClient(transport);

  const abortController = new AbortController();

  onMount(async () => {
    const call = service.flow({}, { abort: abortController.signal });

    for await (const response of call.responses) {
      console.log("got another response!", response);
    }
  });

  onDestroy(() => abortController.abort());
</script>

The call in in pending state for about 2.2 min. After that I get all messages logged in console.

image

If I change this line:

chiRouter.Use(authb.LoadClientStateMiddleware, remember.Middleware(authb))

to:

chiRouter.Use(remember.Middleware(authb))

(removing authb.LoadClientStateMiddleware) it works!

Why?

Can you suggest me something?

@frederikhors
Copy link
Author

Here is the code for that middleware: https://github.com/volatiletech/authboss/blob/master/client_state.go#L123.

@johanbrandhorst
Copy link
Contributor

Sounds like some sort of timeout is being hit. I don't know why the middleware is causing the server to wait, I can't see it doing anything suspicious with the request body at a first glance. I'd probably just try and debug it more!

@frederikhors
Copy link
Author

authboss's author, @aarondl, answered this here:

The response is always closed, it's a guarantee by the stdlib nowadays. Now because you're using grpc in some fashion that might be different maybe they're doing their own weird http things.

Authboss's middleware is pretty simple, when a request comes in it gives the request to LoadClientState which gives it to your ClientStateReadWriter to extract client state variables out. It collects session and cookie changes in memory and when someone calls WriteHeader or Write it will flush those out to the response. In essence that's how it works. I'm not sure exactly where things are going wrong here because there's quite a few layers.

But I'd suggest reading client_state.go and take a look at the ClientStateResponseWriter which is likely the trouble spot. Notably calls to hijack() will fail completely and if they're using hijack that might be a problem?

Are you using hijack here?

@johanbrandhorst
Copy link
Contributor

Most of the logic is in this file: https://github.com/improbable-eng/grpc-web/blob/master/go/grpcweb/wrapper.go. We're doing some wrapping of the body, but I don't think we're using Hijack, unless you're using websockets and it's done by our websocket library.

@frederikhors
Copy link
Author

unless you're using websockets and it's done by our websocket library

Nope.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants