graphql-platform icon indicating copy to clipboard operation
graphql-platform copied to clipboard

Strawberry Shake WebSocket configuration doesn't have Options field to configure authorization header

Open fuji97 opened this issue 1 year ago • 7 comments

Is there an existing issue for this?

  • [X] I have searched the existing issues

Product

Strawberry Shake

Describe the bug

In the Authentication section of the documentation si described that, in order to configure the authentication header for WebSockets, I can use the SetRequestHeader() method under Options

services
    .AddConferenceClient()
    .ConfigureWebSocketClient(client =>
    {
        client.Uri = new Uri("ws://localhost:" + port + "/graphql");
        client.Socket.Options.SetRequestHeader("Authorization", "Bearer ...");
    });

But under client.Socket there is no Options field.

Steps to reproduce

  1. Create a sample .NET 7 Console project.
  2. Import the StrawberryShake.Server and the Microsoft.Extensions.Hosting NuGet packages.
  3. Follow the get started guide until ConferenceClient is created
  4. Try to setup the headers in Program.cs
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
IHost host = Host.CreateDefaultBuilder(args)
    .ConfigureServices((hostContext, services) => {
        services.AddConferenceClient()
            .ConfigureWebSocketClient(client => {
                client.Socket.Options.SetRequestHeader("Authorization", "Bearer ...");
            });
    })
    .Build();

host.Run();

Relevant log output

No response

Additional Context?

No response

Version

13.0.5

fuji97 avatar Apr 14 '23 17:04 fuji97

@fuji97 Have you found a wait to send the auth token? I tried ISocketConnectionInterceptor, but it is called after the connect call.

Jemy191 avatar Dec 03 '23 08:12 Jemy191

@TheJemy191 I fixed it by using the following custom ISocketConnectionInterceptor

public class WebApiSocketRequestInterceptor : ISocketConnectionInterceptor {
    private readonly IAccessTokenProvider _accessTokenProvider;
    private readonly IConfiguration _configuration;
    private readonly ILogger<WebApiSocketRequestInterceptor> _logger;
    public WebApiSocketRequestInterceptor(IAccessTokenProvider accessTokenProvider, IConfiguration configuration, ILogger<WebApiSocketRequestInterceptor> logger) {
        _accessTokenProvider = accessTokenProvider;
        _configuration = configuration;
        _logger = logger;
    }

    public async ValueTask<object?> CreateConnectionInitPayload(ISocketProtocol protocol, CancellationToken cancellationToken) {
        try {
            var scopes = _configuration.GetSection("Scopes:Admin").Get<string[]>();
            var accessTokenResult = await _accessTokenProvider.RequestAccessToken(new AccessTokenRequestOptions() {
                Scopes = scopes
            });
            if (!accessTokenResult.TryGetToken(out var token)) {
                throw new InvalidOperationException("Could not get access token.");
            }
            return new Dictionary<string, string> { ["authToken"] = token.Value };
        }
        catch (Exception e) {
            _logger.LogError(e, "Error while creating connection init payload");
            throw;
        }
    }
}

And it the Program.cs:

builder.Services.AddTransient<WebApiSocketRequestInterceptor>();

/// Other code

builder.Services.AddGraphQLClient()
    .ConfigureHttpClient((services, client) => {
        client.BaseAddress = new Uri(url);
    }, b => b.AddHttpMessageHandler<WebApiAuthorizationMessageHandler>())
    .ConfigureWebSocketClient((services, client) => {
        var logger = services.GetRequiredService<ILogger<Program>>();
        try {
            var interceptor = services.CreateScope().ServiceProvider.GetRequiredService<WebApiSocketRequestInterceptor>();
            client.Uri = new Uri(url);
            client.ConnectionInterceptor = interceptor;
        }
        catch (Exception e) {
            logger.LogError(e, "Error while creating connection init payload");
            throw;
        }
    services.CreateScope().ServiceProvider.GetRequiredService<WebApiSocketRequestInterceptor>();
    });

This is directly copied from my Blazor WASM project, so you should change the settings to works with yours (i.e. scopes and the token provider) but for the rest it should works. Let me know if it works

fuji97 avatar Dec 03 '23 14:12 fuji97

That won't work. Event with your change WebApiSocketRequestInterceptor is called after the first connection and the server never receive any token.

Jemy191 avatar Dec 04 '23 02:12 Jemy191

Found the problem. I needed to add .AddSocketSessionInterceptor<SocketAuthorizationInterceptor>() when configuring HotChocolate. This is what receive the initial payload from ISocketConnectionInterceptor

Jemy191 avatar Dec 04 '23 02:12 Jemy191

You need to put the token on the init request ....

The authorization header is not supported in browser when using websocket.

I will post how to do it later.

michaelstaib avatar Jan 18 '24 15:01 michaelstaib

@TheJemy191 exactly ... on the server you need SocketAuthorizationInterceptor we used to have a full demo ... but we need to have another look at that.

michaelstaib avatar Jan 18 '24 15:01 michaelstaib

The workaround above does not seem to be able to add headers. How can that be done?

(Our API, including the WS endpoint, requires a session ID in a cookie, with an Authorization header available for simpler smoke testing, and it's the Authorization header I want to add now.)

cmeeren avatar Feb 19 '24 11:02 cmeeren