Websocket response code: 204 (IIS8) - websocket

I have a .NET Core 2.0 application running with the preview SignalR. I'm running on Windows Server 2012 R2 behind IIS 8.5.
Websockets are enabled in the server roles, and are also configured in IIS.
When I attempt to initiate a SignalR connection over Websocket, I get this response:
Error during WebSocket handshake: Unexpected response code: 204
Everything I've read says that this should work, and as far as I can tell I have all of the configurations set up correctly. Can anyone guide me on debugging this issue?
MORE DETAILS -------
My application has two extension methods for adding SignalR to the application and to the services:
/// <summary>
/// .NET Core 2.0-specific extensions for Signal-R.
/// </summary>
public static class SignalRExtensions
{
/// <summary>
/// Use Signal-R and set up the base hub in the application.
/// </summary>
/// <param name="app">The Builder for the .NET Core 2.0 application.</param>
/// <param name="serviceProvider">The application's service provider.</param>
/// <returns>A reference to the application builder object.</returns>
public static IApplicationBuilder UseSignalR_NETCORE20(this IApplicationBuilder app, IServiceProvider serviceProvider)
{
app.UseSignalR(routes =>
{
routes.MapHub<BaseHub>("baseHub");
});
SignalRHelper.BaseHubContext = serviceProvider.GetService<IHubContext<BaseHub>>();
return app;
}
/// <summary>
/// Add Signal-R servies to a .NET Core 20 application.
/// </summary>
/// <param name="services">The service collection.</param>
/// <returns>The Signal-R builder object.</returns>
public static ISignalRBuilder AddSignalR_NETCORE20(this IServiceCollection services)
{
var settings = new JsonSerializerSettings();
settings.ContractResolver = new SignalRContractResolver();
var serializer = JsonSerializer.Create(settings);
services.Add(new ServiceDescriptor(typeof(JsonSerializer), provider => serializer, ServiceLifetime.Transient));
services.AddSingleton<IMessageSender, MessageSender>();
services.AddSingleton<IContractResolver, SignalRContractResolver>();
return services.AddSignalR();
}
}
It has code to use web Sockets if we're running on Windows Server 2012 R2 or higher (I needed this code in .NET Core 1.1, I might not need the check in 2.0 but haven't verified yet):
// Don't enable web sockets if we're running on a Windows version lower than 6.2 (Windows 8/Windows Server 2012 R2)
Regex windowsRex = new Regex(#"Microsoft Windows ([0-9]*\.[0-9*]).*");
Match match = windowsRex.Match(RuntimeInformation.OSDescription);
if (!match.Success || double.Parse(match.Groups[1].Value) > 6.1)
{
app.UseWebSockets();
}
The client connection code looks something like this (sanitized a bit):
Publisher.signalRHubURL = /* set URL */
Publisher.options = { logging: false };
Publisher.signalRHub = new signalR.HubConnection(Publisher.signalRHubURL, Publisher.options);
Publisher.showConnectionStatus('disconnected', 'Disconnected. Real-Time updates offline.');
Publisher.signalRHub.start().then(function() {
Publisher.isInitialized = true;
Publisher.signalRHub.on("updateSubscribedDisplay", function(jsonData) {
Publisher.startMessageQueueThread(jsonData);
});
Publisher.signalRHub.onclose(Publisher.reconnect);
if (Publisher.subscriberQueue.length > 0) {
/* send some data to the server */
}
Publisher.showConnectionStatus('connected', 'Data connection is normal.');
}).catch(function(error) {
// WebSockets not supported, fall back to LongPolling
Publisher.logError(error, 1);
Publisher.options.transport = signalR.TransportType.LongPolling;
Publisher.signalRHub = new signalR.HubConnection(Publisher.signalRHubURL, Publisher.options);
Publisher.signalRHub.start().then(function() {
Publisher.isInitialized = true;
Publisher.signalRHub.on("updateSubscribedDisplay", function(jsonData) {
Publisher.startMessageQueueThread(jsonData);
});
Publisher.signalRHub.onclose(Publisher.reconnect);
if (Publisher.subscriberQueue.length > 0) {
/* send some data to the server */
}
Publisher.showConnectionStatus('connected', 'Data connection is normal.');
});
});
I am using full IIS, and webSockets are enabled:
In the network tab, I see the webSocket request fail and then the longPolling request take over:
I notice that once the connection is made, signalR is reporting WebSockets as an available transport:
MORE DETAILS -------
I added this at the very top of my Startup class's Configure method:
app.Use(async (context, next) =>
{
ServerFactory.GetLogManager().Log(string.Format("Request Scheme: {0}", context.Request.Scheme));
await next();
});
This is always logging "http" as the scheme (I expect at least one "ws" for the webSocket request). Is this a valid test? Does it indicate that IIS is somehow mangling the webSocket request before passing it off to Kestrel?
Maybe related to ANCM Issue 163 (https://github.com/aspnet/AspNetCoreModule/issues/163)?
I do have this rewrite rule in my web.config:

Related

Add shared authentication to a Classic ASP.NET Web Forms app and a Web API app

I am replacing a shared cookie authentication design with an Open Id Connect one.
Context: The two web apps currently use FormsAuthentication with a shared AuthCookie cookie and machineKeys.
Systems:
A classic Web Forms app, FormsAuthentication, cookie=AuthCookie, hosts /login.aspx
An integrated Web API app, FormsAuthentication, cookie=AuthCookie
Issue 1: Microsoft's Open Id Connect library requires OWIN. I cannot convert the classic Web Forms app to an OWIN app because OWIN requires Integrated pipeline which would break my Spring.NET library that depends on the classic pipeline.
Action 1A: Convert the Web API to OWIN and added support to Open Id Connect.
Action 1B: Move authentication from the classic site to the Web API.
Issue 2: After authentication I must be able to use both systems.
Action 2: On Open Id Connect redirection, the Web API will work with the JWT bearer token stored in an Open Id Connect cookie. The Web API will create an additional cookie (AuthCookie) which the classic app will use.
Issue 3: How do I keep these two cookies in sync? Users must be both logged in or logged out of the two systems. What happens if one cookie is accidently deleted but not the other?
Issue 4: The Web API isn't receives cookies from Microsoft Open Id Connect but subsequent requests to my Web API don't receive cookies and the HttpContext.Current.User isn't being set.
Classic Web Forms code:
<authentication mode="Forms">
<forms loginUrl="webapi/login" name="LycheeAuthCookie" slidingExpiration="true" path="/" />
</authentication>
Web API code:
Startup.cs:
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
app.UseCookieAuthentication(new CookieAuthenticationOptions { AuthenticationMode = AuthenticationMode.Passive }); //Web API should return 401 not redirect to /login. The SPA will handle that
app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions
{
ClientId = Config.ApplicationId,
Authority = authority,
RedirectUri = $"webapi/openIdRedirect",
PostLogoutRedirectUri = $"webapi/openIdLogout",
ResponseType = OpenIdConnectResponseType.IdToken,
Scope = OpenIdConnectScope.OpenIdProfile,
AuthenticationMode = AuthenticationMode.Passive, //Web API should return 401 not redirect to /login. The SPA will handle that
});
...
private Task OnSecurityTokenValidated(SecurityTokenValidatedNotification<OpenIdConnectMessage, OpenIdConnectAuthenticationOptions> context) //Called on Open Id Connect authentication success
{
var newTicket = new FormsAuthenticationTicket(1,
userId,
DateTime.Now.AddMinutes(int.Parse(claims.FindFirst("iat").Value)),
false,
DateTime.Now.AddMinutes(int.Parse(claims.FindFirst("exp").Value)),
false,
"roles and permissions",
FormsAuthentication.FormsCookiePath);
var cookie = new HttpCookie("AuthCookie");
cookie.Value = FormsAuthentication.Encrypt(newTicket);
cookie.Expires = newTicket.Expiration;
HttpContext.Current.Response.Cookies.Add(cookie);
}
Redirect(redirectUrl);
If you host your websites on the same domain, i.e. "https://localhost", and you share the same .AspNet.Cookies, then it will work!
For both websites add the OWIN middleware, set your project to IIS integrated pipeline mode, and add the following Startup.cs file:
Website 1 at "/" - active:
[assembly: OwinStartup(typeof(...MyStartup))]
namespace MyNamespace
{
public class Startup
{
public void Configuration(IAppBuilder app)
{
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
app.UseCookieAuthentication();
app.UseOpenIdConnectAuthentication();
}
}
}
Website 2 at "/api" - passive:
[assembly: OwinStartup(typeof(...MyStartup))]
namespace MyNamespace
{
public class Startup
{
public void Configuration(IAppBuilder app)
{
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
app.UseCookieAuthentication(new Microsoft.Owin.Security.Cookies.CookieAuthenticationOptions {
AuthenticationType = CookieAuthenticationDefaults.AuthenticationType
});
}
}
}

Unable to fix veracode cwe id 918 flaw (SSRF) when using API gateway pattern in a Microservices architecture

I am using API Gateway Pattern in a Micro services architecture in which the Front End Angular app makes an HTTP request to my API Gateway project which is simply a ASP.net Core 3.1 Web API project. Currently I only have 2 micro services and an API Gateway and all of them are of type ASP.net Core 3.1 Web API project. The API Gateway project has all the controllers of my micro services. The purpose of the API Gateway is just to receive the request from Front end and make an HTTP Request to the appropriate Micro service.
Now in the AccountController.cs of my API Gateway project, I have the following code
/// <summary>
/// Gets the detail of an account by its id
/// </summary>
/// <param name="organizationId">Id of the Organization of which the account belongs to</param>
/// <param name="accountId">Id of Account of which information is being requested</param>
/// <returns>Account's Details</returns>
[HttpGet("{organizationId}/{accountId}")]
public async Task<IActionResult> GetAccountAsync(Guid organizationId, Guid accountId)
{
_uri = new Uri(uriString: $"{_configurationService.AccountAPI}GetAccount/{organizationId}/{accountId}");
using var result = await _client.GetAsync(_uri);
var content = await result.Content.ReadAsStringAsync();
return Ok(content.AsObject<MessageResponse<AccountDetailVM>>());
}
After searching about the SSRF issue on stackoverflow I found the following recommendation at Veracode community.
Veracode Static Analysis will report a flaw with CWE 918 if it can
detect that data from outside of the application (like an HTTP Request
from a user, but also a file that may have been uploaded by a user,
database data, webservice data, etc) is able to change the nature of a
network request.
On Stackoverflow I found the following fix
For CWE ID 918 it is hard to make Veracode recognize your fix unless you have static URL. You need to validate all your inputs that become parts of your request URL.
That means I had to sanitize my input parameters OrganizationId and AccountId before appending them to the request URL.
Also another question on the veracode community suggested
The only thing that Veracode Static Analysis will automatically detect as a remediation for this flaw category is to change the input to be hardcoded
and they proposed a solution for the query string
The given example appears to take a model identifier and put it in the
URL used in an internal request. We would recommend validating the ID
per the rules you have for this datatype (typically this should only
be alphanumeric and less than 255 characters) and URLencode it before
appending it to a URL.
After all those stuff, I have made the following changes to my code
Made sure OrganizationId and AccountId Guid are not empty
URL Encoded the string
Here is the code after changes
/// <summary>
/// Gets the detail of an account by its id
/// </summary>
/// <param name="organizationId">Id of the Organization of which the account belongs to</param>
/// <param name="accountId">Id of Account of which information is being requested</param>
/// <returns>Account's Details</returns>
[HttpGet("{organizationId}/{accountId}")]
public async Task<IActionResult> GetAccountAsync(Guid organizationId, Guid accountId)
{
if (organizationId != Guid.Empty && accountId != Guid.Empty)
{
string url = HttpUtility.UrlEncode($"{_configurationService.AccountAPI}GetAccount/{organizationId}/{accountId}");
using var result = await _client.GetAsync(url);
var content = await result.Content.ReadAsStringAsync();
return Ok(content.AsObject<MessageResponse<AccountDetailVM>>());
}
return BadRequest();
}
Thats All I could do to sanitize my input parameters OrganizationId and AccountId but after all those changes veracode still identifies a SSRF flaw on line
using var result = await _client.GetAsync(url);
I found a hack to fix this issue, I just appended the query string parameters to the Base Address of httpClient and veracode stopped giving me error.
Here is how the solution looks like
/// <summary>
/// Gets the detail of an account by its id
/// </summary>
/// <param name="organizationId">Id of the Organization of which the account belongs to</param>
/// <param name="accountId">Id of Account of which information is being requested</param>
/// <returns>Account's Details</returns>
[HttpGet("{organizationId}/{accountId}")]
public async Task<IActionResult> GetAccountAsync(Guid organizationId, Guid accountId)
{
if (organizationId != Guid.Empty && accountId != Guid.Empty)
{
var httpClient = new HttpClient();
//Appended the parameters in base address to
//to fix veracode flaw issue
httpClient.BaseAddress = new Uri($"{_configurationService.AccountAPI}GetAccount/{organizationId}/{accountId}");
//passing empty string in GetStringAsync to make sure
//veracode doesn't treat it like modifying url
var content = await httpClient.GetStringAsync("");
return Ok(content.AsObject<MessageResponse<AccountDetailVM>>());
}
return BadRequest();
}

OAuth 2.0 token based Authentication Question on storing token values

I have implemented the OAuth token-based Authentication in our WebAPI Application and validating the username and password against the database. But we are not syncing the access tokens and refresh tokens to any type of database. Here is the code, however, I have one question where the token values are stored.
Below code for generating the Token
/// <summary>
/// Grant resource owner credentials overload method.
/// </summary>
/// <param name="context">Context parameter</param>
/// <returns>Returns when task is completed</returns>
public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
{
// Initialization.
var usernameVal = context.UserName;
var passwordVal = context.Password;
var user = _securityLogic.AuthenticateApiUser(usernameVal, passwordVal);
// Verification.
if (!user)
{
// Settings.
context.SetError("invalid_grant", "The user name or password is incorrect.");
// Return info.
return;
}
// Initialization.
var claims = new List<Claim>
{
//var userInfo = user.FirstOrDefault();
// Setting
new Claim(ClaimTypes.Name, usernameVal)
};
// Setting Claim Identities for OAUTH 2 protocol.
ClaimsIdentity oAuthClaimIdentity = new ClaimsIdentity(claims, OAuthDefaults.AuthenticationType);
ClaimsIdentity cookiesClaimIdentity = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationType);
// Setting user authentication.
AuthenticationProperties properties = CreateProperties(usernameVal);
AuthenticationTicket ticket = new AuthenticationTicket(oAuthClaimIdentity, properties);
// Grant access to authorize user.
context.Validated(ticket);
context.Request.Context.Authentication.SignIn(cookiesClaimIdentity);
}
#endregion
#region Token endpoint override method.
/// <summary>
/// Token endpoint override method
/// </summary>
/// <param name="context">Context parameter</param>
/// <returns>Returns when task is completed</returns>
public override Task TokenEndpoint(OAuthTokenEndpointContext context)
{
foreach (KeyValuePair<string, string> property in context.Properties.Dictionary)
{
// Adding.
context.AdditionalResponseParameters.Add(property.Key, property.Value);
}
// Return info.
return Task.FromResult<object>(null);
}
#endregion
This is code is for generating the refresh Token
#region GrantRefreshToken
private static readonly ConcurrentDictionary<string, AuthenticationTicket> RefreshTokens =
new ConcurrentDictionary<string, AuthenticationTicket>();
/// <summary>
/// Grants Refresh Token
/// </summary>
/// <param name="context"></param>
/// <returns></returns>
public override Task GrantRefreshToken(OAuthGrantRefreshTokenContext context)
{
// Change authentication ticket for refresh token requests
var newIdentity = new ClaimsIdentity(context.Ticket.Identity);
// newIdentity.AddClaim(new Claim("newClaim", "newValue"));
var newTicket = new AuthenticationTicket(newIdentity, context.Ticket.Properties);
context.Validated(newTicket);
return Task.FromResult<object>(null);
}
public async Task CreateAsync(AuthenticationTokenCreateContext context)
{
var guid = Guid.NewGuid().ToString();
// Copy claims from the previous token
var refreshTokenProperties = new AuthenticationProperties(context.Ticket.Properties.Dictionary)
{
IssuedUtc = context.Ticket.Properties.IssuedUtc,
ExpiresUtc = DateTime.UtcNow.AddMinutes(30)
};
var refreshTokenTicket = await Task.Run(() =>
new AuthenticationTicket(context.Ticket.Identity, refreshTokenProperties));
RefreshTokens.TryAdd(guid, refreshTokenTicket);
// Consider storing only the hash of the handle
context.SetToken(guid);
}
#endregion
So, my question is .NET/Owin/IdentityServer3 writing them to some in-memory database? If so, can they be accessed for things like viewing and deleting? And what happens if the app server is restarted, are all the tokens wiped out? Or are they persistent?
And do you recommend storing in the Database and retrieving it from the database? Any help is appreciated, by the way, this code is working perfectly fine.
From the documentation:
If not specifically configured we will always provide an in-memory
version stores for authorization codes, consent, reference and refresh
tokens.
Please note that they talk about reference tokens and refresh tokens. JWT access tokens and identity tokens are not stored.
In order to use a refresh token in IdentityServer3 (and also IdentityServer4) it has to match a stored token.
The major benefit of that is that you can control the token. You can revoke it (removes it from the store), and define how to use it: OneTime or ReUse.
I'm not familiar with IdentityServer3, but you can take a look at github and search the code where the RevocationEndpoint is implemented, the place where the refresh token is removed from the store. That may give a clue on how to access and use the store.
With in-memory stores, the tokens are lost on restart of the IdentityServer. So persisting them in a persistent store, like a database, is a good thing for production servers. For IdentityServer4 you can implement an operational store.
Please note that JWT tokens remain valid regardless a restart of the server, unless the private key is also not persisted. In that case IdentityServer can't validate the token and has no choice but to consider the JWT tokens as invalid.
So for production environments you should persist keys and data, and using a database is fine. As you can see in IdentityServer4 there is support for this.
Speaking of IdentityServer4, since (free) support of IdentityServer3 has ended I would recommend to switch to IdentityServer4, if you are in the position to do so. Since both versions implement oidc/auth2 you should be able to keep using the clients with the upgraded IdentityServer. On stackoverflow there are questions that can help you with that. And take a look at the IdentityServer4 documentation, it's very informative.

I don't get the $.connection.[hubName]

I try to add web socket functionality using signalR, based on :
the chat example.
I have 2 different project -
1: is pure server, includes web api:
I have a web method to get the request and call to hub:
[HttpGet]
[Route("api/GetData")]
public IHttpActionResult GetCorpDataApi()
{
return Ok(getResponse());
**startWebSocket();**
}
the start web socket method calls to:
private void startWebSocket()
{
MonitorHub hub = new MonitorHub();
Timer myTimer = new Timer();
myTimer.Elapsed += new ElapsedEventHandler(hub.DisplayTimeEvent);
myTimer.Interval = 3000;
myTimer.Start();
}
that calls to a method in my hub:
public class MonitorHub : Hub
{
public void DisplayTimeEvent(object source, ElapsedEventArgs e)
{
var response = MonitorUtils.GetCorpData();
Clients.All.broadcastMessage(response);
}
}
(All is in the same project, compiled with no errs).
My client code is located in the client angular project (also under VS),
I added the required functionality within the controller:
function initWebSocket() {
self.chatHub = null; // holds the reference to hub
self.chatHub = $.connection.MonitorHub; // **I dont have MonitorHub**
$.connection.hub.start();
self.chatHub.client.broadcastMessage = function (response) {
//bla bla
};
}
I want to mention that I included in the index.html the files:
<script src="scripts/Vendor/jquery/jquery-2.2.1.js"></script>
<script src="scripts/Vendor/jquery/jquery.signalR-2.2.1.js"></script>
<script src="scripts/signalr/hubs"></script>
<script src="assets/global/plugins/angularjs/angular.min.js" type="text/javascript"></script>
$.connection.MonitorHub gets nothing - undefined.
I really believe that it has something to do with the fact that the server side
is in a different project than the client side. When I run a simple example like the chat example, it works - all in the same project.
I get in the console this error:
Error: SignalR: Error loading hubs. Ensure your hubs reference is correct, e.g. .
Any idea?
I am not 100% sure but it might be a case of JQuery version mismatch. Try using a lower jquery version e.g. jquery-1.6.4.js
You did not seem to have added SignalR server to your project. You seem to be starting a WebSocket and create an instance of a hub and throwing some Web API. This is not how SignalR works. You need to install SignalR server packages to your server and then SignalR will take care of handling your calls, instantiating your hubs etc. Just follow the tutorial you posted a link to (Setting up the project section) or this one.

ServiceStack: Accessing the HttpRequest in a selfhosted application

I currently have an IIS hosted application that I would like to switch over to use the self-hosted method.
But I'm having difficulty accessing the session so I can retrieve the current users username.
This is the code I used when hosting under IIS which worked perfectly:
/// <summary>
/// A basic wrapper for the service stack session, to allow access to it lower down in the DAL layer without tying us to servicestack.
/// </summary>
public class ServiceStackAuthTokenService : IAuthTokenService
{
/// <summary>
/// GetCurrentAuthToken.
/// </summary>
/// <returns>A string representing the users auth name.</returns>
public string GetCurrentAuthToken()
{
// Grab the current request.
var req = HttpContext.Current.Request.ToRequest();
var res = HttpContext.Current.Response.ToResponse();
// Fetch the authentication service.
var authService = EndpointHost.AppHost.TryResolve<AuthService>();
authService.RequestContext = new HttpRequestContext(req, res, null);
// Grab the session.
var session = authService.GetSession(false);
// Return the username.
return session.UserName;
}
public string UserPropertyName
{
get { return "UserName"; }
}
}
This is added to the app host with the following code::
container.RegisterAutoWiredAs<ServiceStackAuthTokenService, IAuthTokenService>()
When running self-hosted the HttpContext.Current is null, how do I access the request under a self-hosted application?
Thanks!
Update
Additional things I have tried:
as per an post here: https://groups.google.com/forum/#!msg/servicestack/jnX8UwRWN8A/_XWzTGbnuHgJ
It was suggested to use:
container.Register>(c => AuthService.CurrentSessionFactory);
This just returns a newed IAuthSession.
What the user in that post is doing is exactly what I'm trying to achieve.
In the last post Mythz says:
Just to be clear, in order to form the Session Key that references the Users session you need either the ss-id or ss-pid cookies (as determined by ss-opts).
You can get cookies off the IHttpRequest object or otherwise in ASP.NET the HttpContext.Current.Request singleton, so whatever IAuthUserSession factory you inject needs to take something that can give it the cookies, i.e. either an IRequestContext, IHttpRequest, IService, etc.
But I still cant see a way to access the IHttpRequest.
For ServiceStack 3, you can share request data via the HostContext.Instance.Items Dictionary. For ServiceStack 4, you should use the HostContext.RequestContext.Items Dictionary.
For example, add a request filter in your app host configuration to save the value:
// Put the session into the hostcontext.
RequestFilters.Add((req, res, requestDto) =>
{
HostContext.Instance.Items.Add("Session", req.GetSession());
});
Then in your authentication token class pull it back out:
public string GetCurrentAuthToken()
{
var session = HostContext.Instance.Items["Session"] as AuthUserSession;
if (session != null)
{
return session.UserName;
}
throw new Exception("No attached session found.");
}

Resources