authboss icon indicating copy to clipboard operation
authboss copied to clipboard

BIG PROBLEM! Using gRPC streaming the call stays in pending state (and doesn't work) if I use LoadClientStateMiddleware, works if not.

Open frederikhors opened this issue 4 years ago • 7 comments

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

I'm using it with a grpc-web server too.

Today I had to test the gRPC server streaming and after HOURS of trying I realized that it is authboss that causes 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?

@aarondl, this is HUGE! Can you please help me solve it?

frederikhors avatar Jul 04 '21 14:07 frederikhors

@timostamm just answered:

For server streaming, the response is read in chunks. The server must send the response chunks out as soon as possible, not wait for the response to finish.

XMLHttpRequest has a limitation, the only way for clients to read the response as chunks is to use a text-based format. That's why grpc-web has the text format.

It is possible that the libraries you are using have better support for the text format. I guess the chance is rather slim though. It is more likely that the authboss middleware is just not written with streaming responses in mind.

frederikhors avatar Jul 05 '21 11:07 frederikhors

@aarondl can you please help me here? 🙏

frederikhors avatar Jul 07 '21 20:07 frederikhors

Hi there @frederikhors. Not sure how to help here, it's not built that way.

aarondl avatar Jul 08 '21 03:07 aarondl

it's not built that way.

What do you mean?

frederikhors avatar Jul 08 '21 06:07 frederikhors

Why is the response not closed?

frederikhors avatar Jul 08 '21 08:07 frederikhors

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?

aarondl avatar Jul 08 '21 17:07 aarondl

See the answer from @johanbrandhorst here:

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!

I'll report your answer to his amazing team.

frederikhors avatar Jul 11 '21 15:07 frederikhors