Skip to content

This issue was moved to a discussion.

You can continue the conversation there. Go to discussion →

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

How to handle authorization in subscriptions? #1297

Closed
RobertoOrtis opened this issue Aug 20, 2020 · 7 comments
Closed

How to handle authorization in subscriptions? #1297

RobertoOrtis opened this issue Aug 20, 2020 · 7 comments
Labels

Comments

@RobertoOrtis
Copy link

How Can I handle authorization in subscriptions? The current recipe is outdated. I managed to set authorization for queries and mutations. Can somebody point me into the right direction?

@razorness
Copy link

razorness commented Aug 22, 2020

  • Transmit Authorization
    • You can choose using a cookie, then it's as easy as a default middleware.
    • Otherwise you can use InitFunc of transport.Websocket and then making your check of credentials sent as transport.InitPayload (f.e. JWT/Token).
  • Then you can pack that stuff into a context.Context together with a cancel func. Later, in your handler, you can verify authorization again and if not valid, exec cancel func and stop pushing non-nil values. Gqlgen will stop sending keep alive messages. So unauthorized client will disconnect by itself.

@RobertoOrtis
Copy link
Author

I got it till the part of the InitFunc and checking the credentials of the user when starts listening to the subscription. If they are invalid, it closes the subscription.

I do not know how to access the cancelFunc though. Could you please tell how to access this function?

@razorness
Copy link

razorness commented Aug 25, 2020

Here is a simple example:

// appContext.go
const (
	AppContextKey = "appContext"
)

type AppContext struct {
	Token          string
	UserId         string
	Cancel         context.CancelFunc
}

func ForAppContext(ctx context.Context) *AppContext {
	c, _ := ctx.Value(AppContextKey).(*AppContext)
	return c
}
// init.go
	h := handler.New(generated.NewExecutableSchema(generated.Config{Resolvers: &resolver.Resolver{}}))
	// ...
	h.AddTransport(transport.Websocket{
		Upgrader: websocket.Upgrader{
			HandshakeTimeout: time.Minute,
			CheckOrigin: func(r *http.Request) bool {
				// we are already checking for CORS
				return true
			},
			EnableCompression: true,
		},
		InitFunc: func(ctx context.Context, initPayload transport.InitPayload) (context.Context, error) {
			if token := initPayload.Authorization(); middleware.CouldBetoken(token) {
				if intro, err := oauth2.IntrospectToken(token[7:], false); err == nil && intro != nil && oauth2.IsIntrospectionValid(intro) {
					nctx, cancel := context.WithCancel(ctx)
					return context.WithValue(nctx, middleware.AppContextKey, &middleware.AppContext{
						Token:          token[7:],
						UserId:         intro.Sub,
						Cancel:         cancel,
					}), nil
				}
			}
			return ctx, errors.New("AUTHORIZATION_REQUIRED")
		},
		KeepAlivePingInterval: viper.GetDuration(config.WebsocketKeepAliveKey),
	})
	// ...
// resolvers.go
func (r *subscriptionResolver) Notification(ctx context.Context, id string) (<-chan *model.Notification, error) {
	appContext := middleware.ForAppContext(ctx)
	if !oauth2.IsValid(appContext.Token) {
		appContext.Cancel() // stop sending keep alive
		return nil, nil
	}
	// ...
}

I would love to have the ability to close the websocket entirely instead of hoping the client disconnects. In a way like this:

// resolvers.go
func (r *subscriptionResolver) Notification(ctx context.Context, id string) (<-chan *model.Notification, error) {
	appContext := middleware.ForAppContext(ctx)
	if !oauth2.IsValid(appContext.Token) {
		conn := middleware.ForWsConnection(ctx)
		conn.Close(websocket.CloseNormalClosure, "unauthorized")
		return nil, nil
	}
	// ...
}

@stale
Copy link

stale bot commented Nov 24, 2020

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

@stale stale bot added the stale label Nov 24, 2020
@JoleMile
Copy link

Hello, I also have issues getting the authentication working with subscriptions.
I'm using the following package for auth:
github.com/go-chi/jwtauth

It works great for queries and mutations as it gets the token from the request header and stores it into the context of the resolver. Unfortunately, it doesn't work for subscriptions. Does anyone know why or how to get it working using the previously mentioned package?

Thanks

@razorness
Copy link

You have to check authentication on init and store all you need in returned context. Then you can access this data and recheck authentication and authorization in your subscription resolver.

#1297 (comment)

This example should be very similar when using jwt.

@JoleMile
Copy link

@razorness I see. That actually makes sense after reading a bit more about the websocket protocol. Thank you :)

@frederikhors frederikhors converted this issue into discussion #1825 Jan 19, 2022

This issue was moved to a discussion.

You can continue the conversation there. Go to discussion →

Labels
Projects
None yet
Development

No branches or pull requests

3 participants