Base solution for your next web application
Open Closed

SignalR - Authentication #5181


User avatar
0
pankajmathur created

Hi,

I am using ABP 3.6.2. Could you please advise me (or provide me link), how can I implement authentication? Please be aware, in my case the client here would be a third party application.

One way, in my mind is, let client call the Authenticate API of my application which will return him a token and then let the client connect to our signalr hub by passing that token. But I don't know how should I validate that token in my signalr Hub onConnected event?

Please help....

Regards, Mahendra


9 Answer(s)
  • User Avatar
    0
    ismcagdas created
    Support Team

    You can check <a class="postlink" href="https://github.com/aspnetzero/aspnet-zero-core/blob/master/aspnet-core/src/MyCompanyName.AbpZeroTemplate.Web.Host/Startup/AuthConfigurer.cs#L64">https://github.com/aspnetzero/aspnet-ze ... rer.cs#L64</a>.

    In this case, user retrieves a encrypted token and when user sends it via query string, it is resolved here.

  • User Avatar
    0
    pankajmathur created

    Hi,

    The link you shared is not opening and throwing 404 error.

  • User Avatar
    0
    aaron created
    Support Team

    You need to login with your GitHub account to access the private repo. You can invite yourself here: <a class="postlink" href="https://aspnetzero.com/LicenseManagement">https://aspnetzero.com/LicenseManagement</a>

  • User Avatar
    0
    pankajmathur created

    Here is my complete code of SignalR. The question is when JavaScript client runs, it does not hit the breakpoint at QueryStringTokenResolver function of AuthConfigurer class.

    I am sure, I must be missing something. Please advise....

    ----------------------------Application Service iVend365ApplicationModule.cs---------------------------- [DependsOn( typeof(iVend365CoreModule), typeof(AbpAspNetCoreSignalRModule) )] public class iVend365ApplicationModule : AbpModule { …… …... } ----------------------------Application Service iVend365Hub.cs---------------------------- public override Task OnConnectedAsync() { return base.OnConnectedAsync(); }

        public override Task OnDisconnectedAsync(Exception exception)
        {
            ConnectedClient DisConnectedClient =  AppStatic.ConnectedClients.Where(p => p.ConnectionId == Context.ConnectionId).SingleOrDefault();
            AppStatic.ConnectedClients.Remove(DisConnectedClient);
            Clients.All.SendAsync("PrintConnectedPOS", "POS - " + DisConnectedClient.POSKey + " DisConnected");
            return base.OnDisconnectedAsync(exception);
        }
    
        public void RegisterPOS(Int32 TenantId, string POSKey)
        {
            
            ConnectedClient connectedClient = new ConnectedClient() { TenantId = TenantId, POSKey = POSKey, ConnectionId = Context.ConnectionId };
            AppStatic.ConnectedClients.Add(connectedClient);
            Clients.All.SendAsync("PrintConnectedPOS", "POS - " + POSKey + " Connected");
        }
    

    ----------------------------MVC Web  Startup.cs---------------------------- public IServiceProvider ConfigureServices(IServiceCollection services) { ……. services.AddCors(options => options.AddPolicy("CorsPolicy", builder => { builder .AllowAnyHeader() .AllowAnyMethod() .AllowAnyOrigin() .AllowCredentials(); }));

            services.AddSignalR(options =>
            {
                options.KeepAliveInterval = TimeSpan.FromSeconds(15);
            });
    	…………….
    	…………….
      }
    

    public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) { ………… app.UseCors("CorsPolicy");

            app.UseSignalR(routes =>
            {
                routes.MapHub&lt;iVend365Hub&gt;("/signalr");
                
            });
        ………………
        …………..
      }
    

    -----------------MVC Web  AuthConfigurer.cs---------------------------- private static Task QueryStringTokenResolver(MessageReceivedContext context) { if (!context.HttpContext.Request.Path.HasValue || !context.HttpContext.Request.Path.Value.StartsWith("/signalr")) { //We are just looking for signalr clients return Task.CompletedTask; }

            var qsAuthToken = context.HttpContext.Request.Query["enc_auth_token"].FirstOrDefault();
            if (qsAuthToken == null)
            {
                //Cookie value does not matches to querystring value
                return Task.CompletedTask;
            }
    
            //Set auth token from cookie
            context.Token = SimpleStringCipher.Instance.Decrypt(qsAuthToken, AppConsts.DefaultPassPhrase);
            return Task.CompletedTask;
        }
    

    ----------------------JavaScript Client (Another Project)-------------------- <!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <title></title> </head> <body> <script src="Scripts/signalr.js"></script> <script> const connection = new signalR.HubConnectionBuilder() //.withUrl("http://ivend365retail-signalrtest.azurewebsites.net/signalr?enc_auth_token=ABCD") .withUrl("http://localhost:62114/signalr") .configureLogging(signalR.LogLevel.Information) .build();

        connection.start().then(function () {
            connection.send("RegisterPOS", 1018, "418307399342882834");
            //connection.send("RegisterPOS", 972, "441464738128330770");
        });
    
        connection.on("GetDeltaQueue", function (message) {
            console.log(message);
        });
    
        connection.on("PrintConnectedPOS", function (message) {
            console.log(message);
        });
    &lt;/script&gt;
    

    </body> </html>

  • User Avatar
    0
    aaron created
    Support Team

    The question is when JavaScript client runs, it does not hit the breakpoint at QueryStringTokenResolver function of AuthConfigurer class.

    Show a screenshot of where your breakpoint is.


    Tip: Wrap your code in the following for formatting and readability:

    [code]
    

    [/code:2x0u0yv0] A shortcut is to highlight your code and click on the </> button in the formatting toolbar.

    You can wrap the contents of each file in a separate code block.

  • User Avatar
    0
    pankajmathur created

    Hi,

    Let me try to explain you once again. My JavaScript client was successfully able to connect to My SignalR Hub and the data exchange between client and server was happening successfully till the time there is no authentication implemented.

    Now to implement client authentication, I did the following:

    In the CitiXsys.iVend365.Web.Mvc Project's AuthConfigurer.cs class, I add the following:

    public static void Configure(IServiceCollection services, IConfiguration configuration)
            {
               .....
              if (bool.Parse(configuration["Authentication:JwtBearer:IsEnabled"]))
                {
                  .......
    
                       options.Events = new JwtBearerEvents
                        {
                            OnMessageReceived = QueryStringTokenResolver
                        };
                }
    
            }
    
    
    /* This method is needed to authorize SignalR javascript client.
             * SignalR can not send authorization header. So, we are getting it from query string as an encrypted text. */
            private static Task QueryStringTokenResolver(MessageReceivedContext context)
            {
                if (!context.HttpContext.Request.Path.HasValue ||
                    !context.HttpContext.Request.Path.Value.StartsWith("/signalr"))
                {
                    //We are just looking for signalr clients
                    return Task.CompletedTask;
                }
    
                var qsAuthToken = context.HttpContext.Request.Query["enc_auth_token"].FirstOrDefault();
                if (qsAuthToken == null)
                {
                    //Cookie value does not matches to querystring value
                    return Task.CompletedTask;
                }
    
                //Set auth token from cookie
                try
                {
                    context.Token = SimpleStringCipher.Instance.Decrypt(qsAuthToken, AppConsts.DefaultPassPhrase);
                }
                catch (Exception ex)
                {
    
                }
                return Task.CompletedTask;
            }
    

    Also In My Hub Class, I added the attribute AbpAuthorize.

    [AbpAuthorize]
        public class iVend365Hub : AbpCommonHub
        {
            
            public iVend365Hub(IOnlineClientManager onlineClientManager, IClientInfoProvider clientInfoProvider) : base(onlineClientManager, clientInfoProvider)
            {
            }
    
            public override Task OnConnectedAsync()
            {
                return base.OnConnectedAsync();
            }
    
            public override Task OnDisconnectedAsync(Exception exception)
            {
                ConnectedClient DisConnectedClient =  AppStatic.ConnectedClients.Where(p => p.ConnectionId == Context.ConnectionId).SingleOrDefault();
                AppStatic.ConnectedClients.Remove(DisConnectedClient);
                Clients.All.SendAsync("PrintConnectedPOS", "POS - " + DisConnectedClient.POSKey + " DisConnected");
                return base.OnDisconnectedAsync(exception);
            }
    
            
            public void RegisterPOS(Int32 TenantId, string POSKey)
            {
                
                ConnectedClient connectedClient = new ConnectedClient() { TenantId = TenantId, POSKey = POSKey, ConnectionId = Context.ConnectionId };
                AppStatic.ConnectedClients.Add(connectedClient);
                Clients.All.SendAsync("PrintConnectedPOS", "POS - " + POSKey + " Connected");
            }
        }
    

    Now From Postman, I called the API <a class="postlink" href="http://localhost:62114//api/TokenAuth/Authenticate">http://localhost:62114//api/TokenAuth/Authenticate</a> and get the accesstoken and encryptedAccessToken.

    Now from my client I am trying the connect to SignalR Hub by passing encryptedAccessToken as query string as below:

    const connection = new signalR.HubConnectionBuilder()
                .withUrl("http://localhost:62114/signalr?enc_auth_token=wNYmO41/48SHNstaLVXxHCCre29BZQl1NhC6NM3R3rzpXtPQxVzH6jEzA/QhXFN5tu6Fk7pO53uppm1mVXMZgxbyRVz26dnepi/FyB6axBY+6gq1GL+uRQgoiFUCjRN2p8w6LevViwKlHyWZZJZO1DGVSjAi1m2U+og9pkHw9/QZzDt3yiLMoYW1WDdaXPnlFLgt0N06nDVJ/TNUnhw0BCL43otzv+OMKuLeY+h0rGYDeO90t9A/K6VhhzUXsg1AnxPiqcPpiycwoLyZAZ53/kzTppy8m3SYqaKUsjOESlkAY8kF2r7y154j9qEUcLk9utfC3PSRp0oSuNrm4VSVguDREewfyWbN2sKRaHtlXs5UxIgBWyA348/T9keWz4iRdlPaw7R66Toe+whj4AhIwXhBIQMqxokrK1Sq2LBTHHwJPAUsTkzsTwP1v0/VcSC9oGwxy27ovrSXKN9G5/i43T5nZW5vR+X2Q4SVFS4wi03ifhySoobT6PgEUGaET9Xp62cBKgzEM589+abpKPMhAs4533XlfdfWRr6UxNUUF0LMuya3RB2NCE1h6ccRo0vujM9DMiKxU04lQ5ezjQV6k1lqFx0CjsCZEscMH91ICvjAuAnUXkHpCeNyfJl5Om+Xen1Ef57GeI0WV/5Rj6h6FJt+z8xDXCZJ8ikvpQe+tjjvX+vAfQdyR5nFgHC/vCNiab0x/qnWgJyoQOcXxMUZ43HXsRIruWCre/gpHvfEysGKsk7V3PLpdgESTQVNniZPHs7EypH8r8+T9VRiR1X964QUmqnaBMn3pNkrMBm1gEqyB1sP7HNlz3dLvjRRUX3JcD2/Pe5elhZBjnYRLu8Ay7pJaEzXgEeJ3cRGkq8DbxXZEg5O+f3+VjYssQDjesGGg8ZDwaBDriBSWjOtvnw7OFqvD7qk4xzXqFZpIRnqkJ/70mLCFfTGTxpjMWnFMTu9xh8Snm2/uPex48biHG30f4BXw0xmW2xZ521GIRdgFIE=")
                .configureLogging(signalR.LogLevel.Information)
                .build();
            connection.start().catch(err => console.error(err.toString()));
    

    But it throws the error in the following code:

    context.Token = SimpleStringCipher.Instance.Decrypt(qsAuthToken, AppConsts.DefaultPassPhrase);
    

    The error returned is: Invalid length for a Base-64 char array or string.

    Please help...

  • User Avatar
    0
    aaron created
    Support Team

    What's the value of qsAuthToken?

  • User Avatar
    0
    pankajmathur created

    Hi,

    I think, I have resolved it by using encodeURIComponent in the javascript client to encode the encrypted access token...

    Anyway...thanks for your support....

  • User Avatar
    0
    aaron created
    Support Team

    That's great :)