Authentification implementation for faye
Principle
This project implements (channel,client_id) authentication on channel subscription and publication and delegate it to an external HTTP endpoint through JWT tupple signature based on a shared secret key between Faye server and the endpoint.
On channel subscription the JS client performs an Ajax Call to an HTTP endpoint to be granted a signature that will be provided to Faye Server to connect and publish to channel. The authentication of the endpoint itself is up to you but in the general case this will be a session authenticated resource of your app and you will decide to provide the signature or not depending on your own business logic.
This signature is required for each channel and client id tupple and relies on JWT for security. The Faye server will verify the (channel,client_id) tupple signature and reject the message if the signature is incorrect or not present.
If the browser needs multiple signatures (for multiple channels), they'll automatically be batched together into one signature HTTP request to your server.
Current support
Currently Implemented :
- Javascript Client Extention (JQuery needed)
- Ruby Faye Server Extension
- Ruby Faye Client Extension
- Ruby utils to signing messages in your webapp
- Want another one ? Pull requests are welcome.
Installation
Add this line to your application's Gemfile:
gem 'faye-authentication'
And then execute:
$ bundle
Or install it yourself as:
$ gem install faye-authentication
Usage
Channels requiring authentication
All channels require authentication by default, however, it is possible to provide a lambda to the faye extensions to let them know which channels are public.
Authentication endpoint requirements
The endpoint will receive a POST request with one or more channels, and shall return a JSON document with the signatures.
The parameters sent to the endpoint are the following :
{
"messages": {
"0": {
"channel": "/foo",
"clientId": "123abc"
},
"1": {
"channel": "/bar",
"clientId": "123abc"
}
}
}
If the endpoint returns an error, the messages won't be signed and the server will reject them.
You can use Faye::Authentication.sign
to generate the signature from the message and a private key.
Example (For a Rails application)
def auth
response = params[:messages].values.map do |message|
if current_user.can?(:read, message[:channel])
message.merge(signature: Faye::Authentication.sign(message, FAYE_CONFIG['secret']))
else
message.merge(error: 'Forbidden')
end
end
render json: {signatures: response}
end
A Ruby HTTP Client is also available for publishing messages to your faye server without the hassle of using EventMachine :
Faye::Authentication::HTTPClient.publish('http://localhost:9290/faye', '/channel', 'data', 'your private key')
Javascript client extension
Add the extension to your faye client :
var client = new Faye.Client('http://my.server/faye');
client.addExtension(new FayeAuthentication(client));
By default, when sending a subscribe request or publishing a message, the extension
will issue an AJAX request to /faye/auth
If you wish to change the endpoint, you can supply it as the second argument of the extension constructor, the first one being the client :
client.addExtension(new FayeAuthentication(client, '/my_custom_auth_endpoint'));
If you want to specify some channels for which you don't want the extension to
call your endpoint, you can pass an options object with a whitelist
key mapping
to a function :
function channelWhitelist(channel) {
// Allow channels beginning with /public but disallow globbing
return (channel.lastIndexOf('/public/', 0) === 0 && channel.indexOf('*') == -1);
}
client.addExtension(new FayeAuthentication(client, '/faye/auth', {whitelist: channelWhitelist}));
Faye-authentication will retry each signatures request once if the first attempt failed or returned an invalid signature. The default retry delay is 1000 ms, so 1 second. It can be configured :
client.addExtension(new FayeAuthentication(client, '/faye/auth', {retry_delay: 200})); // 200 ms retry delay
Ruby Faye server extension
Instanciate the extension with your secret key and add it to the server :
server = Faye::RackAdapter.new(:mount => '/faye', :timeout => 15)
server.add_extension Faye::Authentication::ServerExtension.new('your shared secret key')
Faye::Authentication::ServerExtension expect that :
- a
signature
is present in the message for publish/subscribe request - this signature is a valid JWT token
- the JWT payload contains "channel", "clientId" and a expiration timestamp "exp" that is not in the past.
Otherwise Faye Server will refuse the message.
If you want to specify some channels for which you don't want the extension require authentication, you can pass an options hash with a whitelist
key mapping to a lambda :
channel_whitelist = lambda do |channel|
# Allow channels beginning with /public but disallow globbing
channel.start_with?('/public/') and not channel.include?('*')
end
server = Faye::RackAdapter.new(:mount => '/faye', :timeout => 15)
server.add_extension Faye::Authentication::ServerExtension.new('your shared secret key', {whitelist: channel_whitelist})
Ruby Faye client extension
This extension allows the ruby Faye::Client
to auto-sign its messages before sending them to the server.
client = Faye::Client.new('http://localhost:9292/faye')
client.add_extension Faye::Authentication::ClientExtension.new('your shared secret key')
Contributing
- Fork it ( https://github.com/dimelo/faye-authentication/fork )
- Create your feature branch (
git checkout -b my-new-feature
) - Commit your changes (
git commit -am 'Add some feature'
) - Push to the branch (
git push origin my-new-feature
) - Create a new Pull Request