Introduction
Recently, I ran across the problem of authorizing on a self-hosted SignalR console application. When I would ask friends, the first question was: Why not in an ASP.NET environment? Because my console application is more of a daemon than anything else. There isn't much logic behind it but I couldn't get around the login process. I've started to Google quite a bit and I still have not found a public solution available to the initial problem. This is why I am posting this article as there doesn't seem to be much information about it. Not even on the Microsoft SignalR forum.
This article will require some basic knowledge about the environment we are going to use. I will not explain MySQL nor knockoutjs and most certainly not EF. The intention of this article is to get your basic setup going and advance from there!
Background
Setup/Key functions:
- Entity Framework 6
- MySQL
- SignalR Self-hosted (server)
- Knockoutjs (client)
Using the Code
We will start off by setting up the SignalR server using the simple C# console application template in VS.
Install the following nuget package: 'MySql.Data.Entity
'.
This will install the following packages as well:
MySql.Data
EntityFramework
Then install 'Microsoft.AspNet.SignalR.SelfHost
', 'Microsoft.Owin.StaticFiles
' and 'Microsoft.Owin.Security.Cookies
'.
Quick explanation: We will serve a static authentication file for the login process. A simple form. And save a claims based identity in the cookie.
This will be our basic setup for the serverside.
Next thing we need to do is to set our db connection up with the help of EF. Add a basic user class consisting only of username and password.
public class User
{
public Int32 Id { get; set; }
public String Username { get; set; }
public String Password { get; set; }
}
Then set the context up for EF.
[DbConfigurationType(typeof(MySql.Data.Entity.MySqlEFConfiguration))]
public class GameContext : DbContext
{
public GameContext()
: base("standard")
{
}
public DbSet<user> Users { get; set; }
}
Prepare a simple user table for the logindata.
SET SQL_MODE = "NO_AUTO_VALUE_ON_ZERO";
SET time_zone = "+00:00";
;
;
;
;
CREATE TABLE IF NOT EXISTS `users` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`username` text NOT NULL,
`password` text NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=2 ;
INSERT INTO `users` (`id`, `username`, `password`) VALUES
(1, 'user', 'pass');
;
;
;
Then add the validation process in a UserManager
class.
public class UserManager
{
private GameContext zGC = new GameContext();
public User Authenticate(String pUsername, String pPassword)
{
return this.zGC.Users.FirstOrDefault
(x => x.Username == pUsername && x.Password == pPassword);
}
}
Then add the connection string to your app.config file.
<add name="standard" providerName="MySql.Data.MySqlClient.EF6"
connectionString="Server=localhost;Database=database;Uid=username;Pwd=5FttZWuS6rMnCwLX; />
Add this point we are set to add and retrieve user information. So the next task is to set SignalR up.
To fire up the server, add the following:
string lServerURL = "http://+:19221";
WebApp.Start<startup>(lServerURL);
Console.WriteLine("Server launched on => " + lServerURL + "\r\n");
Console.Read();
Now, we already get to the interesting part. The configuration.
It'll basically come down to the following:
Add the following code:
public class Startup
{
private const String _WebsiteUrl = "Http://www.website.com";
private static String _Authpage = File.ReadAllText(Path.Combine
(Path.Combine(Environment.CurrentDirectory, @"..\.."), @"Auth/Index.html"));
public void Configuration(IAppBuilder app)
{
app.UseCors(CorsOptions.AllowAll);
string lContentPath = Path.Combine(Environment.CurrentDirectory, @"..\..");
var fileOptions = new StaticFileOptions
{
FileSystem = new PhysicalFileSystem(lContentPath),
};
app.UseStaticFiles(fileOptions);
if (!String.IsNullOrEmpty(_Authpage))
_Authpage = File.ReadAllText(Path.Combine(lContentPath, @"Auth/Index.html"));
CookieAuthenticationOptions lOptions = new CookieAuthenticationOptions()
{
AuthenticationType = CookieAuthenticationDefaults.AuthenticationType,
LoginPath = new PathString("/Auth/Login"),
LogoutPath = new PathString("/Auth/Logout"),
};
app.UseCookieAuthentication(lOptions);
app.Use(async (context, next) =>
{
var redirectUri = context.Request.Query["ReturnUrl"];
if (context.Request.Path.Value.Contains(lOptions.LoginPath.Value) &&
context.Authentication.User == null)
{
if (context.Request.Method == "POST")
{
var lForm = await context.Request.ReadFormAsync();
if (!String.IsNullOrEmpty(lForm["input_login"]) &&
!String.IsNullOrEmpty(lForm["input_pass"]))
{
var lAuthenticatedUser = new UserManager().Authenticate(lForm["input_login"],
lForm["input_pass"]);
if (lAuthenticatedUser != null)
{
ClaimsIdentity lIdentity = new ClaimsIdentity(lOptions.AuthenticationType);
lIdentity.AddClaim(new Claim(ClaimTypes.Name, lAuthenticatedUser.Username));
context.Authentication.SignIn(lIdentity);
context.Response.Redirect(_WebsiteUrl);
}
}
}
else
{
context.Response.Redirect("/Auth/");
}
}
else if (context.Request.Path.Value.Contains(lOptions.LogoutPath.Value))
{
context.Authentication.SignOut(lOptions.AuthenticationType);
context.Response.Redirect("/Auth");
}
else if (context.Request.Path.Value == "/Auth/")
{
if (context.Authentication.User != null)
context.Response.Redirect(_WebsiteUrl);
context.Response.ContentType = "text/html";
await context.Response.WriteAsync(_Authpage);
}
else
{
await next();
}
});
app.MapSignalR();
}
}
Now if you visit: http://localhost:19221/Auth/, you can successfully login and you will see that a cookie will be set. We are successfully authenticated to our SignalR server.
Let's get a basic hub going which we can connect to by adding a new class and inheriting from 'hub
', we will also give it the [Authorize]
tag to make sure it's only accessible if logged in.
public class systemHub : Hub
{
[Authorize]
public void getCurrentDate()
{
Clients.Caller.updateCurrentDate("[" +
DateTime.Now.ToString("dd/MM/yyyy HH:mm:ss") + "]");
}
public Boolean IsAuthenticated(String pCallbackFunc)
{
var lLoggedIn = false;
if (Context.User != null)
{
if (Context.User.Identity.IsAuthenticated)
{
lLoggedIn = true;
}
}
Clients.Caller.updateLoginStatus(lLoggedIn);
}
}
This is pretty much it for our server side. Now we will go ahead and add a new project for our frontend that will connect to our hub and display us the date. For this purpose, we will be using the "ASP.NET Empty Web Application" template. Start it up and add the url to your Startup file.
private const String _WebsiteUrl = "http://localhost:52729/";
Let's finish this project up with a simple ViewModel
a connection to our hub and a button to check whether we are connected or not and another button to get the current server time.
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title></title>
<style>
.hidden{display: none;}
</style>
</head>
<body>
<h1 data-bind='text: loginStatus'>Loading....</h1>
<h2 data-bind='text: serverDate'></h2>
<button id="statusButton" class="hidden">getStatus</button>
<button id="timeButton" class="hidden">getDateTime</button>
<script src="//code.jquery.com/jquery-1.11.3.min.js"></script>
<script src="http://ajax.aspnetcdn.com/ajax/signalr/jquery.signalr-2.2.0.min.js"></script>
<script src="http://localhost:19221/signalr/hubs"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.0/knockout-min.js"
type="text/javascript"></script>
<script>
function ViewModel() {
var self = this;
self.loginStatus = ko.observable($('h1').text());
self.serverDate = ko.observable("");
self.initSignalr = function(){
var baseSignalrServerURL = 'http://localhost:19221/';
$.connection.hub.url = baseSignalrServerURL + 'signalr';
var lHub = $.connection.systemHub;
lHub.client.updateCurrentDate = function(pVal){
self.serverDate(pVal);
}
lHub.client.updateLoginStatus= function (pVal) {
self.loginStatus(pVal);
}
$.connection.hub.start().done(function () {
$('button').toggleClass('hidden');
});
$('#statusButton').click(function () {
lHub.server.isAuthenticated();
});
$('#timeButton').click(function () {
lHub.server.getCurrentDate();
});
};
self.initSignalr();
};
ko.applyBindings(new ViewModel());
</script>
</body>
</html>
You are now able to browse your frontend and check whether you are logged in or not.
Points of Interest
- Browse /Auth/Login if you want to log in.
- Browse /Auth/Logout if you want to log out
- Set your SignalR app as Startup project, otherwise you won't be able to load the hubs file
History
- 19th November, 2015: Initial version