ThreeMammals / Ocelot

.NET API Gateway
https://www.nuget.org/packages/Ocelot
MIT License
8.36k stars 1.64k forks source link

Authentication with SignalR #1040

Open mkanakis opened 5 years ago

mkanakis commented 5 years ago

Thanks for the good work!

New Feature

Is authentication for signalR on the plans? We are using signalR with IdentityServer4, to send data over to authenticated users, but I don't think Ocelot is supporting it currently, as stated in documentation.

Motivation for New Feature

I am not sure of people who would use signalR without user authentication.

JeanRessouche commented 4 years ago

Definitely interested by it, that's the only issue in my architecture :-(

mkanakis commented 4 years ago

I guess the easiest way up till now, is to abstract SignalR from your service and deploy it as a separate one, without the use of a gateway, since .netcore provides token authentication for SignalR.

JeanRessouche commented 4 years ago

I do, however, I'm forced to have a second port open on the server, which is complicated on prod. Better have only the 443 to get through the firewall :-(

Misiu commented 4 years ago

@zee85 I'm planning architecture for the next project and I want to use Ocelot and SignalR. So you removed the GetUserId method from your hub's and you are doing authentication logic inside the SignalR project? Is your code available somewhere? I'd like to see Ocelot, SignalR, IdentityServer, and RabbitMQ working together.

Misiu commented 4 years ago

@zee85 thank you for sharing 🙂 I plan to build my project on GitHub so it will be open-sourced as well :) Your code will help me a lot to get started with Ocelot, SignalR, and others. I always wanted to build a project based on microservices, now I have an opportunity.

I saw in ocelot docs that it doesn't handle authentication for SignalR (WebSockets). Hopefully, someday it will

TomPallister commented 4 years ago

Can anyone point me to how the identityserver and signal r authentication works? I thought signalr only supported basic auth? :o I might be behind the times as have not worked much in dotnet recently :)

mkanakis commented 4 years ago

Hi @TomPallister

Here is an example of what I have been using with my Hubs project:

services.AddAuthentication(IdentityServerAuthenticationDefaults.AuthenticationScheme)
                .AddIdentityServerAuthentication(options =>
                {
                    // base-address of your identityserver
                    options.Authority = Configuration["IdentityServer:Url"];

                    // name of the API resource
                    options.ApiName = Configuration["IdentityServer:APIName"];
                    options.ApiSecret = Configuration["IdentityServer:APISecret"];

                    options.EnableCaching = true;
                    options.CacheDuration = TimeSpan.FromMinutes(10); // that's the default

                    options.RequireHttpsMetadata = Convert.ToBoolean(Configuration["IdentityServer:RequireHttpsMetadata"]);

                    // For SignalR
                    options.TokenRetriever = new Func<HttpRequest, string>(req =>
                    {
                        var fromHeader = TokenRetrieval.FromAuthorizationHeader();
                        var fromQuery = TokenRetrieval.FromQueryString();
                        return fromHeader(req) ?? fromQuery(req);
                    });
                });

Taken from here

Hope this helps.

mkanakis commented 4 years ago

Any updates?

Best, Marios.

hsnassaad commented 3 years ago

@mkanakis because this feature is not available right now, and I want to implement a similar thing in my project, did you use any other methods to solve this (like using reverse proxy or other different tools?)

JeanRessouche commented 3 years ago

@hsnassaad you can use nginx as a reverse proxy, signalR is going through correctly

alex-pollan commented 2 years ago

I managed to have my Websocket endpoint authenticated by copying some extension classes from the Ocelot project to my project in order to be able to include the Authentication and Authorization middlewares in the WebSocket middleware sub-pipeline

       public static RequestDelegate BuildOcelotPipeline(this IApplicationBuilder app,
            OcelotPipelineConfiguration pipelineConfiguration)
        {
            ...

            app.MapWhen(httpContext => httpContext.WebSockets.IsWebSocketRequest,
                wenSocketsApp =>
                {
                    wenSocketsApp.UseDownstreamRouteFinderMiddleware();
                    wenSocketsApp.UseMultiplexingMiddleware();
                    wenSocketsApp.UseDownstreamRequestInitialiser();

                    //added this here
                    wenSocketsApp.UseAuthenticationMiddleware();
                    wenSocketsApp.UseAuthorisationMiddleware();

                    wenSocketsApp.UseLoadBalancingMiddleware();
                    wenSocketsApp.UseDownstreamUrlCreatorMiddleware();
                    wenSocketsApp.UseWebSocketsProxyMiddleware();  // Notice that the request processing ends here!!!
                });
                ...
        }

Also, I need to introduce some custom middleware to return 401 on Authentication/Authorization errors because the current pipeline returns 200, which is obviously not correct.

I'm curious about why this functionality is not included as the Websocket endpoint is a regular HTTP endpoint. It might be that I'm looking at it with my specific case in mind and don't get the whole picture.

MohMehrnia commented 2 years ago

@alex-pollan please upload complete code

alex-pollan commented 2 years ago

The code I'm working on below. Ocelot version 16.0.1.

ocelot.json

  "Routes": [
    {
      "UpstreamPathTemplate": "/hub/update/{deviceName}",
      "UpstreamHttpMethod": [ "GET", "POST", "PUT", "DELETE", "OPTIONS" ],
      "DownstreamScheme": "ws",
      "DownstreamPathTemplate": "/update",
      "DownstreamHostAndPorts": [ { "Host": "{SignalRService}" } ],
      "RouteIsCaseSensitive": false,
      "AuthenticationOptions": {
        "AuthenticationProviderKey": "Bearer",
        "AllowedScopes": [ "the_scope" ]
      },
      "RouteClaimsRequirement": {
        "client_id": "{deviceName}"
      }
    }]

Startup.cs

        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            ...
            app.UseAuthentication();
            app.UseAuthorization();
            ...
            app.UseOcelotWebSockets();
            app.UseOcelot().Wait();
        }

Some extension methods class

        public static IApplicationBuilder UseOcelotWebSockets(this IApplicationBuilder appBuilder)
        {
            appBuilder.UseWebSockets();

            //Override Ocelot Websockets pipeline to support authentication and authorization
            appBuilder.MapWhen(httpContext => httpContext.WebSockets.IsWebSocketRequest,
                wenSocketsApp =>
                {
                    wenSocketsApp.UseDownstreamContextMiddleware();
                    wenSocketsApp.UseExceptionHandlerMiddleware();
                    wenSocketsApp.UseResponderMiddleware();
                    wenSocketsApp.UseDownstreamRouteFinderMiddleware();
                    wenSocketsApp.UseMultiplexingMiddleware();
                    wenSocketsApp.UseHttpHeadersTransformationMiddleware();
                    wenSocketsApp.UseDownstreamRequestInitialiser();
                    wenSocketsApp.UseAuthenticationMiddleware();
                    wenSocketsApp.UseClaimsToClaimsMiddleware();
                    wenSocketsApp.UseAuthorisationMiddleware();
                    wenSocketsApp.UseClaimsToHeadersMiddleware();
                    wenSocketsApp.UseClaimsToQueryStringMiddleware();
                    wenSocketsApp.UseClaimsToDownstreamPathMiddleware();
                    wenSocketsApp.UseLoadBalancingMiddleware();
                    wenSocketsApp.UseDownstreamUrlCreatorMiddleware();
                    wenSocketsApp.UseWebSocketsProxyMiddleware();
                });
            return appBuilder;
        }
AmitManchanda commented 2 years ago

Waiting for this feature. Please provide me any alternate as I need to pass access token to my signal R app.

raman-m commented 7 months ago

@alex-pollan commented on Feb 2, 2022

Our current WS pipeline is quite short ))) No authentication at all :rofl: https://github.com/ThreeMammals/Ocelot/blob/d6eefc899f9b2d37814335c7d0bccd5f7a830bd0/src/Ocelot/Middleware/OcelotPipelineExtensions.cs#L37-L47

Could we convert your experience and solution to a PR please?

raman-m commented 7 months ago

@mkanakis commented on Mar 30, 2020

Sorry! How can we re-use your solution in Ocelot if it is based on custom setup in AddIdentityServerAuthentication method which is not a part of Ocelot code base? This solution doesn't use Ocelot functionality at all. Or, do you propose to wrap TokenRetriever life hack into an useful extension method?

alex-pollan commented 7 months ago

I just want to clarify that my solution above is for a pure APIGW with Ocelot that proxies the SignalR connection endpoint downstream to another service where I actually implement the SignalR server.

Notice that the authentication / authorization happens and it is terminated at the APIGW layer.

I capture the authenticated principal identity and pass it downstream as HTTP headers to the SignalR service to use it

Here are the final Ocelot routes I needed:

    {
      "UpstreamPathTemplate": "/signalr/negotiate",
      "UpstreamHttpMethod": [ "POST" ],
      "DownstreamScheme": "http",
      "DownstreamPathTemplate": "/devices-hub/negotiate",
      "DownstreamHostAndPorts": [ { "Host": "{DevicesHubApiService}" } ],
      "RouteIsCaseSensitive": false,
      "AuthenticationOptions": {
        "AuthenticationProviderKey": "Bearer",
        "AllowedScopes": [ "myscope" ]
      }
    },
    {
      "UpstreamPathTemplate": "/signalr",
      "UpstreamHttpMethod": [ "GET", "POST", "PUT", "DELETE", "OPTIONS" ],
      "DownstreamScheme": "ws",
      "DownstreamPathTemplate": "/devices-hub",
      "DownstreamHostAndPorts": [ { "Host": "{DevicesHubSignalRService}" } ],
      "RouteIsCaseSensitive": false,
      "AuthenticationOptions": {
        "AuthenticationProviderKey": "Bearer",
        "AllowedScopes": [ "myscope" ]
      }
    },
raman-m commented 7 months ago

@alex-pollan commented on Mar 20

Your JSON looks like a life hack to forward anonymous traffic to downstream and be authorized on Ocelot's side. I propose to developers always to define 2 routes to allow authentication on service's side.

Your previous comments with a solution is good. And I will figure out how to reuse it when started working on #1707

hogwartsdeveloper commented 4 months ago

is this implemented? If not then I'm going to do id.

raman-m commented 4 months ago

@hogwartsdeveloper You are assigned!

raman-m commented 4 months ago

@hogwartsdeveloper, best of luck to you! Just so you know, I've assigned the issue to the Summer'24 milestone as I'm uncertain if it can be completed for the Annual 2023 release, which is currently underway. The final decision regarding its delivery will depend on the readiness of the PR.

raman-m commented 4 months ago

@hogwartsdeveloper FYI #720 #718

raman-m commented 4 months ago

@hogwartsdeveloper FYI #674 #998

raman-m commented 2 days ago

Further development after upgrade to .NET 9 :exclamation: