Authentication with SignalR and OAuth Bearer Token
Authentication with SignalR and OAuth Bearer Token
Authenticating connections to SignalR is not as easy as you would expect. In many scenarios authentication mechanisms use the Authorize header in HTTP request. The problem is, that SignalR does not explicitly support headers, because Web Sockets – one of the transports used in browsers – does not support them. So if your authentication mechanism requires any form of headers being sent, you need to go another way with SignalR.
In this post I would like to describe a way to use the OAuth Bearer Token authentication with SignalR by passing the token over a cookie into SignalR pipeline. To show how to do this, I’m going to create a chat application (as if world needs another SignalR chat sample!) The application requires user to authenticate in order to send messages. If not authenticated, the user can see only fragments of messages sent by other people, and he is not able to see who the sender is.
When anonymous:
After signing in:
You can find full sample code here.
We’re going to use the Microsoft’s OWIN Security and ASP.NET Identity libraries for this sample. The easiest way to get started is to create new ASP.NET project in VS 2013 and include the WebAPI and Individual Accounts security option. Then add SignalR NuGet package. This gives us:
- User management system with REST API access
- Bearer OAuth flow for authentication
- Possibility to use external login providers (although I’m not going to cover this scenario in the sample)
- And of course possibility to use SignalR
Now let’s implement the SignalR hub:
public class ChatHub : Hub
{
public override Task OnConnected()
{
AssignToSecurityGroup();
Greet(); return base.OnConnected();
} private void AssignToSecurityGroup()
{
if (Context.User.Identity.IsAuthenticated)
Groups.Add(Context.ConnectionId, "authenticated");
else
Groups.Add(Context.ConnectionId, "anonymous");
} private void Greet()
{
var greetedUserName = Context.User.Identity.IsAuthenticated ?
Context.User.Identity.Name :
"anonymous"; Clients.Client(Context.ConnectionId).OnMessage(
"[server]", "Welcome to the chat room, " + greetedUserName);
} public override Task OnDisconnected()
{
RemoveFromSecurityGroups();
return base.OnDisconnected();
} private void RemoveFromSecurityGroups()
{
Groups.Remove(Context.ConnectionId, "authenticated");
Groups.Remove(Context.ConnectionId, "anonymous");
} [Authorize]
public void SendMessage(string message)
{
if(string.IsNullOrEmpty(message))
return; BroadcastMessage(message);
} private void BroadcastMessage(string message)
{
var userName = Context.User.Identity.Name; Clients.Group("authenticated").OnMessage(userName, message); var excerpt = message.Length <= 3 ? message : message.Substring(0, 3) + "...";
Clients.Group("anonymous").OnMessage("[someone]", excerpt);
}
}
The hub utilizes the OnConnect and OnDisconnect methods to assign a connection to one of the security groups: authenticated or anonymous. When broadcasting a message, anonymous connections get limited information, while authenticated ones get the whole thing. User context can be accessed via Context.User property which retrieves current IPrincipal from OWIN context. The SendMessage hub method is only available to authenticated connections, because of the Authorize attribute.
Please note, that when using SignalR groups for security purposes, you may have to handle reconnect scenarios as described here.
We’re going to assume, that some users are already registered in the application. See the auto-generated API help page for information on how to do this. Now, in order to authenticate the connection, we need to:
- Call the token endpoint to get the bearer token for username / password combination.
- Use the token when connecting to SignalR hub.
We’re going to implement the second part by using a cookie. The cookie approach has a problem: when opening a new browser tab, the same cookie will be used. You can’t sign in as two different users with one browser by default. Another approach would be to pass the token in a query string. Query string removes the cookie limitation, but can pose a security threat: query strings tend to be stored in web server logs (or exposed in other ways even when using SSL). There is a risk of someone intercepting the tokens. You need to select an approach that fits your scenario best.
The default OAuth bearer flow implementation you get from OWIN Security only looks at the Authorization HTTP header to find the bearer token. Fortunately you can plug some additional logic by implementing the OAuthBearerAuthenticationProvider.
public class ApplicationOAuthBearerAuthenticationProvider
: OAuthBearerAuthenticationProvider
{
public override Task RequestToken(OAuthRequestTokenContext context)
{
if (context == null) throw new ArgumentNullException("context"); // try to find bearer token in a cookie
// (by default OAuthBearerAuthenticationHandler
// only checks Authorization header)
var tokenCookie = context.OwinContext.Request.Cookies["BearerToken"];
if (!string.IsNullOrEmpty(tokenCookie))
context.Token = tokenCookie;
return Task.FromResult<object>(null);
} }
In the RequestToken method override we look for cookie named “BearerToken”. This cookie will be set by the client code, which I’ll show in a bit.
// EDIT: There is also ValidateIdentity override copied over from ApplicationOAuthBearerProvider private class within OWIN Security components (not sure why it is private in the first place). So basically we replicate the functionality of validating the identity, while including additional token lookup in cookies. The ValidateIdentity method is not relevant for the presented scenario
Now, to use the new component, in Startup.Auth.cs we will change implementation of the ConfigureAuth method:
public void ConfigureAuth(IAppBuilder app)
{
//app.UseCookieAuthentication(new CookieAuthenticationOptions());
//app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie); //app.UseOAuthBearerTokens(OAuthOptions); app.UseOAuthAuthorizationServer(OAuthOptions); app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions
{
Provider = new ApplicationOAuthBearerAuthenticationProvider(),
});
}
First, we turn off cookie based authentication. You may want to leave it on for MVC support. For this example, it’s not needed. Then we turn off external sign in providers, as this is not supported by the sample. In order to pass our own instance of OAuthBearerAuthenticationProvider, we have to ommit the UseOAuthBearerToken helper method, as it uses the private class implementation I mentioned earlier by default. Instead, we do two things:
- configure authorization server (it is responsible for issuing tokens)
- and configure bearer authentication passing in a new instance of our provider class
We’re good on the server side now. What about the client side?
First the function that is responsible for getting the token:
function signIn(userName, password) {
return $.post("/token", { grant_type: "password", username: userName, password: password })
.done(function(data) {
if (data && data.access_token) {
chatHub.useBearerToken(data.access_token);
toastr.success("Login successful");
}
})
.fail(function(xhr) {
if (xhr.status == 400) {
toastr.error("Invalid user name or password");
} else {
toastr.error("Unexpected error while signing in");
}
});
}
We need to do an URL encoded form post with grant_type field set to “password” and also include the username and password. If login fails, we get HTTP 400 status. If it is successful however, we get JSON response with access_token containing the bearer token. That token is passed into the chat hub wrapper class, which looks like this:
var ChatHubWrapper = function() {
var self = this;
var chat = null;
var isStarted = false;
var onMessageCallback = function() {};
self.start = function() {
chat = $.connection.chatHub;
chat.client.onMessage = onMessageCallback;
return $.connection.hub.start()
.done(function() { isStarted = true; });
};
self.stop = function() {
isStarted = false;
chat = null;
return $.connection.hub.stop();
};
self.sendMessage = function(message) {
if (isStarted) {
chat.server.sendMessage(message);
};
};
self.onMessage = function(callback) {
onMessageCallback = callback;
if (isStarted)
chat.client.onMessage = onMessageCallback;
};
self.useBearerToken = function (token) {
var wasStarted = isStarted;
if (isStarted)
// restart, so that connection is assigned to correct group
self.stop();
setTokenCookie(token);
if (wasStarted)
self.start();
};
function setTokenCookie(token) {
if (token)
document.cookie = "BearerToken=" + token + "; path=/";
}
self.clearAuthentication = function () {
document.cookie = "BearerToken=; path=/; expires=" + new Date(0).toUTCString();
}
};
This class wraps chat hub functionality and facilitates creation of the authentication cookie with the bearer token. OWIN Security components will take care of authenticating connections based on the token. As you can see, the connection to the hub needs to be restarted, so that it is put into “authenticated” group. There is one small problem: here we don’t actually wait for the hub to stop before we start it again. That may case some internal HTTP 403 errors in SignalR since it detects change of authentication status on an existing connection. However this error is not surfaced to the user. In order to get rid of this, you need to implement waiting for the disconnect to complete.
And this is pretty much it. We have an authenticated connection to SignalR using a OAuth Bearer Token. The same token can be used to authenticate WebAPI calls (in this case you can use the Authorization HTTP header). Please bear in mind, that in this sample I used the default token expiration time, which is 14 days (this is what new project wizard generates in VS 2013). Also refresh token is not supported in this sample. For more advanced scenarios you may want to implement refresh token support and shorten the expiration period.
You can find the full sample on Github.
Authentication with SignalR and OAuth Bearer Token的更多相关文章
- OAuth 2.0: Bearer Token Usage
Bearer Token (RFC 6750) 用于HTTP请求授权访问OAuth 2.0资源,任何Bearer持有者都可以无差别地用它来访问相关的资源,而无需证明持有加密key.一个Bearer代表 ...
- The OAuth 2.0 Authorization Framework: Bearer Token Usage
https://tools.ietf.org/html/rfc6750 1.2. Terminology Bearer Token A security token with the property ...
- Bearer Token & OAuth 2.0
Bearer Token & OAuth 2.0 access token & refresh token http://localhost:8080/#/login HTTP Aut ...
- OAuth 2.0:Bearer Token、MAC Token区别
Access Token 类型介绍 介绍两种类型的Access Token:Bearer类型和MAC类型 区别项 Bearer Token MAC Token 1 (优点) 调用简单,不需要对请求进行 ...
- Bearer Token的加密解密规则(OAuth中间件)
在OAuthBearerAuthenticationMiddleware中使用Microsoft.Owin.Security.DataHandler. SecureDataFormat<TDat ...
- Postman 发送 Bearer token
Bearer Token (RFC 6750) 用于OAuth 2.0授权访问资源,任何Bearer持有者都可以无差别地用它来访问相关的资源,而无需证明持有加密key.一个Bearer代表授权范围.有 ...
- OpenShift 如何获取bearer Token以便进行各种API调用
Openshift 需要通过bearer token的方式和API进行调用,比如基于Postman就可以了解到,输入bearer token后 1.如何获取Bearer Token 但Bearer T ...
- 接口认证方式:Bearer Token
因为HTTP协议是开放的,可以任人调用.所以,如果接口不希望被随意调用,就需要做访问权限的控制,认证是好的用户,才允许调用API. 目前主流的访问权限控制/认证模式有以下几种: 1),Bearer T ...
- Spring Security OAuth 格式化 token 输出
个性化token 背景 上一篇文章<Spring Security OAuth 个性化token(一)>有提到,oauth2.0 接口默认返回的报文格式如下: { "ac ...
随机推荐
- 独立IP与共享IP的区别
做网站选择独立IP还是共享IP,相信很多站长都在此纠结过,自己不使用服务器的时候从来没有关心过独立IP和共享IP的究竟有什么具体的差别.但当自己真正用到的时候,才发现:同样都是 IP,差别不是一般的大 ...
- lintcode :Remove Duplicates from Sorted List 删除排序链表中的重复元素
题目: 删除排序链表中的重复元素 给定一个排序链表,删除所有重复的元素每个元素只留下一个. 您在真实的面试中是否遇到过这个题? 样例 给出1->1->2->null,返回 1-& ...
- nginx的健康检查功能将挂掉的Tomcat舍弃
1.Ngninx自带健康检查功能,能将挂掉的服务器舍弃,不在访问路径里 2.Nginx还有URL重写功能,能将接收到的请求,进行改写,再将新的URL分发到后端服务器上
- 从一点儿不会开始——Unity3D游戏开发学习(二) ——GUI控件之Button
一些废话 我在上一篇“一点儿不会”的系列随笔中说大概一周会发个2~3篇关于Unity的学习笔记.可这就两周过去了,我还停留在一篇的进度上,主要是这两周发生了一些事情导致我更新缓慢.其实截至目前为止,上 ...
- Xamarin.Android 入门之:Android API版本设置
一.引言 Xamarin.Android有几个Android API级别设置,确定多个版本的Android应用程序的兼容性.本博客解释了这些设置意味着什么,如何配置它们,以及它们在运行时对您的应用程序 ...
- 64位下好神奇啊(增加了PatchGuard技术保护自己,SSDT是相对地址,参数通过寄存器与rdi来传递)
近期可能会有一个64位平台的驱动开发任务,找了些资料,对64位平台下的驱动开发略知一二了,好神奇. 一.在64位系统下,有一项PatchGuard技术,它是微软为了防止自己的代码被Patch,进而影响 ...
- esriFeatureType与esriGeometryType的区别与联系
esriFeatureType通常用来表示数据的存储结构,即物理层: esriGeometryType通常用来表示数据的几何形状,即表现层. esriGeometryType枚举类型详解 常量 值 对 ...
- 基于Struts2框架实现登录案例 之 程序国际化
国际化牵涉的知识非常多,这里只能简单的介绍,程序国际化的一般做法是:在jsp页面时, 不是直接输出信息,而是输出一个key值,该key值在不同语言环境下找到对应资源文件下的 对应信息,因此首先要创建满 ...
- jQuery选择器最佳实践--来自jQ官网
1.基于ID选择器进行查询,并且使用find方法. //快速 $("#container div.footer"); //超快 $("#container"). ...
- PHP大神的十大优良习惯
概述:通往PHP大神的道路上,应该保持优良的传统和习惯. 1.多阅读手册和源代码 没什么比阅读手册更值得强调的事了–仅仅通过阅读手册你就可以学习到很多东西,特别是很多有关于字符串和数组的函数.就在这些 ...