Home > Mobile >  Angular SignalR and Windows Authentication (NTLM)
Angular SignalR and Windows Authentication (NTLM)

Time:02-13

My question is pretty straightforward. I am using Windows Authentication in .NET Core. On the front end part of the app (Angular) I have a HTTP interceptor which sets widhcredentails: true to the all HTTP requests.

This interceptor does not work with WebSocket, so I need a solution for adding the same code from Interceptor to the SignalR service.

Here is my interceptor code:

intercept(request: HttpRequest<unknown>, next: HttpHandler): Observable<HttpEvent<unknown>>
{
    request = request.clone({ withCredentials: true });
    return next.handle(request);
}

And here is my SignalR service:

  this.requestConnection = new HubConnectionBuilder().
        withUrl(`${environment.apiUrl}/request`).
        withAutomaticReconnect([0, 2000, 30000, 60000, null]).
        build();
    this.requestConnection.
        start().
        then(() => console.log('Connection started')).
        catch(err => console.log(`Error while starting connection: ${err}`));

Keep in mind that interceptor is added in providers array in the app.module component :

providers: [
    {
        provide: HTTP_INTERCEPTORS,
        useClass: WinAuthInterceptor,
        multi: true
    }

],

SignalR service is injected in the root.

@Injectable({
    providedIn: 'root'
})

CodePudding user response:

Finally, I've realized what was the problem. Since I am using Windows Authentications, there was no need to add any kind of JWT Authorization. Also, the interceptor code I've posted above, makes no difference lol. Meaning you don't need withCredetials: true either in interceptor or SignalR.

Anyway, (if you are using IIS you need to configure it by following this documentation, locally it will work perfectly, but if you are hosting/deploying your app on the IIS you need to do this prior, otherwise your Context will be NULL) you need to set anonymousAuthentication property to false in launchSettings.json (.NET Core as you backend)

  "iisSettings": {
"windowsAuthentication": true,
"anonymousAuthentication": false,
"iisExpress": {
  "applicationUrl": "http://localhost:64152",
  "sslPort": 44385
}

Long story short, Angular code looks like this:

  this.requestConnection = new HubConnectionBuilder().
        withUrl(`${environment.apiUrl}/request`).
        withAutomaticReconnect([0, 2000, 30000, 60000, null]).
        build();
    this.requestConnection.
        start().
        then(() => console.log('Connection started')).
        catch(err => console.log(`Error while starting connection: ${err}`));

in .NET Core I've just override OnConnectAsync() class to fetch connections and do some logic:

    public override Task OnConnectedAsync()
    {
        var user = Context.User.Identity.Name.ToUpper();
        //remove domain name
        user = user[user.IndexOf("\\")..];
        //remove speacial chars, excluding .
        user = UserProvider.RemoveSpecialCharacters(user);
        var conId = Context.ConnectionId;

        if (UserProvider.UserConnections.Any(key => key.Key == user))
        {
            UserProvider.UserConnections[user] = conId;
        }
        else
        {
            UserProvider.UserConnections.Add(user, conId);
        }


        return base.OnConnectedAsync();
    }

CodePudding user response:

I have found this in the Microsoft documentation:

If Windows authentication is configured in your app, SignalR can use that identity to secure hubs. However, to send messages to individual users, you need to add a custom User ID provider. The Windows authentication system doesn't provide the "Name Identifier" claim. SignalR uses the claim to determine the user name.

Add a new class that implements IUserIdProvider and retrieve one of the claims from the user to use as the identifier. For example, to use the "Name" claim (which is the Windows username in the form [Domain][Username]), create the following class:

public class NameUserIdProvider : IUserIdProvider
{
    public string GetUserId(HubConnectionContext connection)
    {
        return connection.User?.Identity?.Name;
    }
}

Rather than ClaimTypes.Name, you can use any value from the User (such as the Windows SID identifier, and so on).

Note: The value you choose must be unique among all the users in your system. Otherwise, a message intended for one user could end up going to a different user.

Register this component in your Startup.ConfigureServices method:

public void ConfigureServices(IServiceCollection services)
{
    // ... other services ...

    services.AddSignalR();
    services.AddSingleton<IUserIdProvider, NameUserIdProvider>();
}

In the .NET Client, Windows Authentication must be enabled by setting the UseDefaultCredentials property:

var connection = new HubConnectionBuilder()
    .WithUrl("https://example.com/chathub", options =>
    {
        options.UseDefaultCredentials = true;
    })
    .Build();

Windows authentication is supported in Internet Explorer and Microsoft Edge, but not in all browsers. For example, in Chrome and Safari, attempting to use Windows authentication and WebSockets fails. When Windows authentication fails, the client attempts to fall back to other transports which might work.

As mentioned, it is not supported by all browsers. IMO the best way to authenticate the signal R connections is using bearer tokens.

Adding the token to the connection is as easy as:

this.chatHubConnection = new signalR.HubConnectionBuilder()
  .withUrl(this.endpointsService.chatHubConnection(), {
    accessTokenFactory: () => this.getCurrentJwtToken(),
  })
  .configureLogging(signalR.LogLevel.Error)
  .withAutomaticReconnect([0, 2000, 5000, 5000, 5000, 5000, 10000])
  .build();
  • Related