browserid-auth-rails 0.5.3
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +22 -0
- data/README.md +194 -0
- data/app/assets/javascripts/browserid.js.coffee +84 -0
- data/app/views/shared/_browserid.html.erb +12 -0
- data/lib/browserid-auth-rails.rb +1 -0
- data/lib/browserid-rails.rb +56 -0
- data/lib/browserid/rails/base.rb +131 -0
- data/lib/browserid/rails/helpers.rb +67 -0
- data/lib/browserid/rails/version.rb +5 -0
- data/lib/browserid/verifier/persona.rb +85 -0
- metadata +148 -0
data/LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2012 Greg Look
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,194 @@
|
|
1
|
+
Modified fork of [https://github.com/mvxcvi/browserid-rails](https://github.com/mvxcvi/browserid-rails) to be compatible with Devise.
|
2
|
+
|
3
|
+
# BrowserID::Rails
|
4
|
+
|
5
|
+
This gem provides a lightweight single-sign-on authentication structure to a
|
6
|
+
Rails application based on
|
7
|
+
[Mozilla's Persona service](https://login.persona.org/about). Persona
|
8
|
+
authenticates clients uniquely by their email address using the BrowserID
|
9
|
+
protocol, without exposing clients' browsing behaviors to the identity provider.
|
10
|
+
This also frees the application from needing to securely handle and store user
|
11
|
+
credentials.
|
12
|
+
|
13
|
+
## Overview
|
14
|
+
|
15
|
+
BrowserID affords a very easy SSO experience for clients. A simplified version
|
16
|
+
of the authentication flow goes like this:
|
17
|
+
|
18
|
+
1. The user clicks a login link on the site.
|
19
|
+
2. A pop-up window directs the user to authenticate with their identity
|
20
|
+
provider. This will either be their email provider or Mozilla's fall-back
|
21
|
+
Persona service.
|
22
|
+
3. If the authentication is successful, the browser acquires a certificate
|
23
|
+
proving that the client owns the email address in question. This only needs
|
24
|
+
to be done once for an email address across any number of domains; after
|
25
|
+
that the user can just click a button to use that address to authenticate.
|
26
|
+
4. The browser then uses the certificate to sign an authentication assertion
|
27
|
+
for the site (given by the `audience` parameter).
|
28
|
+
5. The browser POSTs the signed assertion to a session creation URL for
|
29
|
+
verification. If the assertion is valid, the authenticated email is stored
|
30
|
+
in the client's session and the page is reloaded.
|
31
|
+
|
32
|
+
At this point, the `browserid_email` method will return the stored email
|
33
|
+
address, and `browserid_current_user` will look up the authenticated user model.
|
34
|
+
See below for more detailed documentation of the available controller and helper
|
35
|
+
methods.
|
36
|
+
|
37
|
+
Logging out is also straightforward:
|
38
|
+
|
39
|
+
1. The (authenticated) user clicks a logout link on the site.
|
40
|
+
2. The browser clears its stored assertion for the site and POSTs a
|
41
|
+
request to the server to clear its login state.
|
42
|
+
3. The server removes the authenticated email stored in the client's session
|
43
|
+
and reloads the page.
|
44
|
+
|
45
|
+
## Installation
|
46
|
+
|
47
|
+
Add this line to your application's Gemfile:
|
48
|
+
|
49
|
+
gem 'browserid-rails'
|
50
|
+
|
51
|
+
And then execute:
|
52
|
+
|
53
|
+
$ bundle
|
54
|
+
|
55
|
+
Or install it yourself as:
|
56
|
+
|
57
|
+
$ gem install browserid-rails
|
58
|
+
|
59
|
+
## Usage
|
60
|
+
|
61
|
+
To use this gem once it is installed, it must be integrated into your Rails
|
62
|
+
application. The following sections cover the gem configuration, controller
|
63
|
+
integration, and view integration.
|
64
|
+
|
65
|
+
### Configuration
|
66
|
+
|
67
|
+
There are several configuration options available. There are a number of default
|
68
|
+
assumptions about the application, which may be overridden as needed.
|
69
|
+
Configuration settings are properties of `config.browserid`.
|
70
|
+
|
71
|
+
* `user_model` - The name of the ActiveModel class for application users.
|
72
|
+
The default is `"User"`.
|
73
|
+
* `email_field` - The attribute of the user model which contains the user's
|
74
|
+
email. The default is `:email`.
|
75
|
+
* `session_variable` - The location the authenticated email is stored in the
|
76
|
+
client's session. The default is `:browserid_email`.
|
77
|
+
* `verifier` - The type of verifier to use to authenticate client BrowserID
|
78
|
+
assertions. The default is `:persona`, which sends the request to Mozilla's
|
79
|
+
Persona verification service. In the future, `:local` will enable local
|
80
|
+
verification code. Alternately, this configuration option may be set to any
|
81
|
+
object which responds to `#verify(assertion, audience)` with the verified
|
82
|
+
email and identity provider on success and raises an error on failure.
|
83
|
+
* `audience` - The BrowserID audience to authenticate to. This should consist
|
84
|
+
of a URI string containing the scheme (protocol), authority, and port of the
|
85
|
+
service (e.g., `"https://app.example.com:443"`). By default, the audience is
|
86
|
+
not hardcoded and the properties of the request object are used to construct
|
87
|
+
it dynamically. This gives greater flexibility while developing, but is also
|
88
|
+
a minor security risk. In production, this should be configured to a fixed
|
89
|
+
value.
|
90
|
+
|
91
|
+
Additionally, there are two sub-structures `login` and `logout` for configuring
|
92
|
+
the associated paths and default link text. They have the following properties:
|
93
|
+
|
94
|
+
* `text` - The default text to give login and logout links.
|
95
|
+
* `path` - The target to give links and the path to `POST` authentication
|
96
|
+
requests to. Defaults to `"/login"` and `"/logout"` respectively.
|
97
|
+
|
98
|
+
So, if you wanted the application to use 'signin' and 'signout' instead, you
|
99
|
+
could do the following:
|
100
|
+
|
101
|
+
config.browserid.login.text = "Sign-in"
|
102
|
+
config.browserid.login.path = '/signin'
|
103
|
+
config.browserid.logout.text = "Sign-out"
|
104
|
+
config.browserid.logout.path = '/signout'
|
105
|
+
|
106
|
+
### Controller Integration
|
107
|
+
|
108
|
+
The `BrowserID::Rails::Base` module makes several controller methods available
|
109
|
+
to interact with the authentication system. To access information, use one of:
|
110
|
+
|
111
|
+
* `browserid_email` - Returns the BrowserID-authenticated email address, if any.
|
112
|
+
* `browserid_current_user` - Retrieves the model for the currently authenticated
|
113
|
+
user, if there is an authenticated email and a matching user exists.
|
114
|
+
* `browserid_authenticated?` - Returns true if there is a current user.
|
115
|
+
|
116
|
+
These methods are also available in views as helpers.
|
117
|
+
|
118
|
+
To control authentication, the app should have a `SessionsController` which
|
119
|
+
connects the in-browser authentication code to the server. The gem provides
|
120
|
+
these methods:
|
121
|
+
|
122
|
+
* `login_browserid` - Sets the given string as the authenticated email.
|
123
|
+
* `logout_browserid` - Clears the current authenticated email.
|
124
|
+
* `verify_browserid` - Uses the configured verifier to confirm a BrowserID
|
125
|
+
assertion is correct for the service audience.
|
126
|
+
* `respond_to_browserid` - Wraps `verify_browserid` in logging and error
|
127
|
+
handling logic and generates controller responses to a `POST` assertion.
|
128
|
+
|
129
|
+
Implementing the required methods for `SessionsController` is straightforward:
|
130
|
+
|
131
|
+
# POST /login
|
132
|
+
def create
|
133
|
+
respond_to_browserid
|
134
|
+
end
|
135
|
+
|
136
|
+
# POST /logout
|
137
|
+
def destroy
|
138
|
+
logout_browserid
|
139
|
+
head :ok
|
140
|
+
end
|
141
|
+
|
142
|
+
### Layout Integration
|
143
|
+
|
144
|
+
The BrowserID javascript library needs to be loaded on your application pages.
|
145
|
+
There are two steps to accomplish this:
|
146
|
+
|
147
|
+
First, the coffeescript asset file needs to be loaded. In the
|
148
|
+
`app/assets/javascripts/application.js` manifest, add the following line:
|
149
|
+
|
150
|
+
//= require browserid
|
151
|
+
|
152
|
+
Second, the scripts need to be setup in your pages' `<head>` section. The
|
153
|
+
`setup_browserid` helper method takes care of this for you and gives a couple
|
154
|
+
of ways to control its behavior:
|
155
|
+
|
156
|
+
<!-- Perform basic BrowserID setup in the head section -->
|
157
|
+
<%= setup_browserid %>
|
158
|
+
|
159
|
+
<!-- Setup BrowserID with alert debugging -->
|
160
|
+
<%= setup_browserid debug: true %>
|
161
|
+
|
162
|
+
<!-- Setup BrowserID with a custom handler -->
|
163
|
+
<%= setup_browserid do %>
|
164
|
+
browserid.onLogin = function (data, status, xhr) {
|
165
|
+
// ...
|
166
|
+
}
|
167
|
+
<% end %>
|
168
|
+
|
169
|
+
Once that's accomplished, the app is ready to use BrowserID for authentication.
|
170
|
+
To add login and logout links to the site, use the `login_link` and
|
171
|
+
`logout_link` helpers. These accept an optional link text as a parameter:
|
172
|
+
|
173
|
+
<%= logout_link %>
|
174
|
+
|
175
|
+
<%= login_link "Login with Persona" %>
|
176
|
+
|
177
|
+
The coffeescript asset adds on-click handlers to the links which trigger the
|
178
|
+
Persona code to request new assertions or destroy existing ones.
|
179
|
+
|
180
|
+
## Contributing
|
181
|
+
|
182
|
+
1. Fork it
|
183
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
184
|
+
3. Commit your changes (`git commit -am 'Added some feature'`)
|
185
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
186
|
+
5. Create new Pull Request
|
187
|
+
|
188
|
+
## Future Work
|
189
|
+
|
190
|
+
* In the future, it would be nice to have a generator to create routes and
|
191
|
+
session controller skeletons. This would simplify setup and integration with
|
192
|
+
new apps quite a bit.
|
193
|
+
* Another to-do item is to incorporate the Persona branding assets and add more
|
194
|
+
helpers for generating login buttons.
|
@@ -0,0 +1,84 @@
|
|
1
|
+
# BrowserID javascript functions
|
2
|
+
|
3
|
+
@browserid = browserid =
|
4
|
+
|
5
|
+
### PROPERTIES ###
|
6
|
+
|
7
|
+
# Public: Path used to verify browserID authentication assertions. Assertions
|
8
|
+
# are POSTed to this path.
|
9
|
+
loginPath: '/login'
|
10
|
+
|
11
|
+
# Public: Path used to unset persisted authentication state when logging out.
|
12
|
+
# This should clear the currently-logged-in user email.
|
13
|
+
logoutPath: '/logout'
|
14
|
+
|
15
|
+
# Internal: Debugging toggle - if true, the results of logins will be alert
|
16
|
+
# dialogs instead of page refreshes. This is useful to set if the application
|
17
|
+
# starts going into a refresh loop.
|
18
|
+
debug: false
|
19
|
+
|
20
|
+
|
21
|
+
|
22
|
+
### HANDLERS ###
|
23
|
+
|
24
|
+
# Public: This method is called when a user successfully authenticates. By
|
25
|
+
# default, it reloads the current page.
|
26
|
+
onLogin: (data, status, xhr) ->
|
27
|
+
if @debug
|
28
|
+
alert("Login: #{status}\n#{data}")
|
29
|
+
else
|
30
|
+
window.location.reload()
|
31
|
+
|
32
|
+
# Public: This method is called when a user fails to authenticate.
|
33
|
+
onLoginError: (xhr, status, err) ->
|
34
|
+
alert("Login: #{status} #{err}\n#{xhr.responseText}")
|
35
|
+
|
36
|
+
# Public: This method is called when a user clears their authentication. By
|
37
|
+
# default, it reloads the current page.
|
38
|
+
onLogout: (data, status, xhr) ->
|
39
|
+
if @debug
|
40
|
+
alert("Logout: #{status}\n#{data}")
|
41
|
+
else
|
42
|
+
window.location.reload()
|
43
|
+
|
44
|
+
# Public: This method is called when a user fails to clear their
|
45
|
+
# authentication.
|
46
|
+
onLogoutError: (xhr, status, err) ->
|
47
|
+
alert("Logout: #{status} #{err}\n#{xhr.responseText}")
|
48
|
+
|
49
|
+
|
50
|
+
### INITIALIZATION ###
|
51
|
+
|
52
|
+
# Public: Watches the authentication state and takes action when the user
|
53
|
+
# logs in or logs out. This method MUST be called on every page of the
|
54
|
+
# application.
|
55
|
+
setup: (currentUser = null) ->
|
56
|
+
navigator.id.watch
|
57
|
+
loggedInUser: currentUser
|
58
|
+
onlogin: (assertion) =>
|
59
|
+
$.ajax
|
60
|
+
type: 'POST'
|
61
|
+
url: @loginPath
|
62
|
+
data: { assertion: assertion }
|
63
|
+
success: (data, status, xhr) => @onLogin(data, status, xhr)
|
64
|
+
error: (xhr, status, err) => @onLoginError(xhr, status, err)
|
65
|
+
onlogout: =>
|
66
|
+
$.ajax
|
67
|
+
type: 'POST'
|
68
|
+
url: @logoutPath
|
69
|
+
success: (data, status, xhr) => @onLogout(data, status, xhr)
|
70
|
+
error: (xhr, status, err) => @onLogoutError(xhr, status, err)
|
71
|
+
|
72
|
+
|
73
|
+
|
74
|
+
### Behavior Binding ###
|
75
|
+
|
76
|
+
jQuery ->
|
77
|
+
$('.browserid_login').click ->
|
78
|
+
navigator.id.request()
|
79
|
+
false
|
80
|
+
|
81
|
+
jQuery ->
|
82
|
+
$('.browserid_logout').click ->
|
83
|
+
navigator.id.logout()
|
84
|
+
false
|
@@ -0,0 +1,12 @@
|
|
1
|
+
<script src="https://login.persona.org/include.js"></script>
|
2
|
+
<script type="text/javascript">
|
3
|
+
<% if options[:login_path] %>browserid.loginPath = "<%= options[:login_path] %>";<% end %>
|
4
|
+
<% if options[:logout_path] %>browserid.logoutPath = "<%= options[:logout_path] %>";<% end %>
|
5
|
+
<% if options[:debug] %>browserid.debug = true;<% end %>
|
6
|
+
<%= yield :browserid_setup %>
|
7
|
+
<% if browserid_email %>
|
8
|
+
browserid.setup("<%= browserid_email %>");
|
9
|
+
<% else %>
|
10
|
+
browserid.setup();
|
11
|
+
<% end %>
|
12
|
+
</script>
|
@@ -0,0 +1 @@
|
|
1
|
+
require 'browserid-rails'
|
@@ -0,0 +1,56 @@
|
|
1
|
+
require 'browserid/rails/base'
|
2
|
+
require 'browserid/rails/helpers'
|
3
|
+
require 'browserid/rails/version'
|
4
|
+
|
5
|
+
module BrowserID
|
6
|
+
module Rails
|
7
|
+
# This class defines a Rails engine which extends the base controller with
|
8
|
+
# the library methods. The presence of this engine also causes assets to
|
9
|
+
# be included when the gem is added as a dependency.
|
10
|
+
class Engine < ::Rails::Engine
|
11
|
+
# Initialize the engine configuration.
|
12
|
+
config.before_configuration do
|
13
|
+
BrowserIDConfig = Struct.new :user_model, :email_field, :session_variable, :verifier, :audience, :login, :logout
|
14
|
+
BrowserIDLinkConfig = Struct.new :text, :path
|
15
|
+
|
16
|
+
config.browserid = BrowserIDConfig.new.tap do |cfg|
|
17
|
+
cfg.user_model = 'User'
|
18
|
+
cfg.email_field = :email
|
19
|
+
cfg.session_variable = :browserid_email
|
20
|
+
cfg.verifier = :persona
|
21
|
+
# audience should only be set in production
|
22
|
+
|
23
|
+
cfg.login = BrowserIDLinkConfig.new.tap do |link|
|
24
|
+
link.text = "Login"
|
25
|
+
link.path = '/login'
|
26
|
+
end
|
27
|
+
|
28
|
+
cfg.logout = BrowserIDLinkConfig.new.tap do |link|
|
29
|
+
link.text = "Logout"
|
30
|
+
link.path = '/logout'
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
# Mix in the controller and view helper methods.
|
36
|
+
config.before_initialize do
|
37
|
+
ActionController::Base.send :include, BrowserID::Rails::Base
|
38
|
+
ActionView::Base.send :include, BrowserID::Rails::Helpers
|
39
|
+
end
|
40
|
+
|
41
|
+
# Create the assertion verifier.
|
42
|
+
config.after_initialize do
|
43
|
+
cfg = config.browserid
|
44
|
+
|
45
|
+
# Replace type symbol with constructed verifier.
|
46
|
+
if cfg.verifier == :persona
|
47
|
+
cfg.verifier = BrowserID::Verifier::Persona.new
|
48
|
+
elsif cfg.verifier == :local
|
49
|
+
raise "Local BrowserID verification is not supported yet" # TODO
|
50
|
+
elsif !cfg.verifier.respond_to?(:verify)
|
51
|
+
raise "Unknown BrowserID verifier type #{cfg.verifier}"
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,131 @@
|
|
1
|
+
require 'browserid/verifier/persona'
|
2
|
+
|
3
|
+
module BrowserID
|
4
|
+
module Rails
|
5
|
+
# Public: Base module for inclusion into a controller. This module includes
|
6
|
+
# methods for dealing with BrowserID user authentication.
|
7
|
+
module Base
|
8
|
+
|
9
|
+
##### INTERNAL METHODS #####
|
10
|
+
|
11
|
+
# Internal: Modifies the controller this module is included in to provide
|
12
|
+
# authentication-related helper methods
|
13
|
+
#
|
14
|
+
# base - The Class this module is being included in.
|
15
|
+
def self.included(base)
|
16
|
+
base.send :helper_method, :browserid_config, :browserid_email, :browserid_current_user, :browserid_authenticated?
|
17
|
+
end
|
18
|
+
|
19
|
+
# Internal: Gets the application configuration for this gem.
|
20
|
+
#
|
21
|
+
# Returns the app config structure.
|
22
|
+
def browserid_config
|
23
|
+
::Rails.application.config.browserid
|
24
|
+
end
|
25
|
+
|
26
|
+
|
27
|
+
|
28
|
+
##### HELPER METHODS #####
|
29
|
+
|
30
|
+
# Public: Gets the email address of the currently-authenticated user.
|
31
|
+
#
|
32
|
+
# Returns the authenticated email address String.
|
33
|
+
def browserid_email
|
34
|
+
session[browserid_config.session_variable]
|
35
|
+
end
|
36
|
+
|
37
|
+
# Public: Retrieves the user for the authenticated email address. This
|
38
|
+
# method uses the `browserid.user_model` and `browserid.email_field`
|
39
|
+
# config settings, which default to `User` and `email`.
|
40
|
+
#
|
41
|
+
# Returns the current authenticated user, or nil if no user exists.
|
42
|
+
def browserid_current_user
|
43
|
+
if browserid_email.nil?
|
44
|
+
nil
|
45
|
+
elsif @browserid_current_user
|
46
|
+
@browserid_current_user
|
47
|
+
else
|
48
|
+
config = browserid_config
|
49
|
+
user_model = config.user_model.constantize
|
50
|
+
find_method = "find_by_#{config.email_field}".intern
|
51
|
+
|
52
|
+
@browserid_current_user = user_model.send find_method, browserid_email
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
# Public: Determines whether the current client is authenticated as a
|
57
|
+
# registered User.
|
58
|
+
#
|
59
|
+
# Returns true if the client is authenticated and registered.
|
60
|
+
def browserid_authenticated?
|
61
|
+
!browserid_current_user.nil?
|
62
|
+
end
|
63
|
+
|
64
|
+
|
65
|
+
|
66
|
+
##### AUTHENTICATION METHODS #####
|
67
|
+
|
68
|
+
# Public: Sets the given email address as the currently-authenticated user.
|
69
|
+
# The address is saved in the client's session.
|
70
|
+
#
|
71
|
+
# email - The String email address to consider authenticated.
|
72
|
+
def login_browserid(email)
|
73
|
+
session[browserid_config.session_variable] = email
|
74
|
+
end
|
75
|
+
|
76
|
+
# Public: Clears the saved email address for the currently-authenticated
|
77
|
+
# user. It is important to note that this does not remove the BrowserID
|
78
|
+
# assertion in the client's browser.
|
79
|
+
def logout_browserid
|
80
|
+
session[browserid_config.session_variable] = nil
|
81
|
+
end
|
82
|
+
|
83
|
+
# Public: Uses the configured verifier to check that a provided assertion
|
84
|
+
# is correct for the site audience.
|
85
|
+
#
|
86
|
+
# Returns the verified email, identity issuer, and audience on success.
|
87
|
+
# Raises an error with a failure message if the client was not
|
88
|
+
# successfully authenticated.
|
89
|
+
#
|
90
|
+
# Examples
|
91
|
+
#
|
92
|
+
# verify_browserid(assertion)
|
93
|
+
# # => "user@example.com", "persona.mozilla.com", "https://app.example.com:443"
|
94
|
+
#
|
95
|
+
def verify_browserid(assertion)
|
96
|
+
audience = browserid_config.audience
|
97
|
+
audience ||= "%s%s:%d" % [request.protocol, request.host, request.port]
|
98
|
+
browserid_config.verifier.verify(assertion, audience)
|
99
|
+
end
|
100
|
+
|
101
|
+
# Public: Handles a POST-ed BrowserID assertion, responding appropriately
|
102
|
+
# to the request. If successful, this logs-in the authenticated email and
|
103
|
+
# returns an OK status. If unsuccessful, it returns FORBIDDEN and an
|
104
|
+
# error message in the response body.
|
105
|
+
#
|
106
|
+
# Returns nothing.
|
107
|
+
#
|
108
|
+
# Examples
|
109
|
+
#
|
110
|
+
# # POST /login
|
111
|
+
# def create
|
112
|
+
# respond_to_browserid
|
113
|
+
# end
|
114
|
+
#
|
115
|
+
def respond_to_browserid
|
116
|
+
if params[:assertion].blank?
|
117
|
+
head :bad_request
|
118
|
+
else
|
119
|
+
email, issuer, audience = verify_browserid params[:assertion]
|
120
|
+
logger.info "Verified BrowserID assertion for #{email} issued by #{issuer} on #{audience}"
|
121
|
+
login_browserid email
|
122
|
+
head :ok
|
123
|
+
end
|
124
|
+
rescue StandardError => e
|
125
|
+
# TODO: distinguish between process failures and invalid assertions
|
126
|
+
logger.warn "Failed to verify BrowserID assertion: #{e.message}"
|
127
|
+
render status: :forbidden, text: e.message
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
module BrowserID
|
2
|
+
module Rails
|
3
|
+
# Public: Rails view helpers for use with BrowserID code.
|
4
|
+
module Helpers
|
5
|
+
# Public: Renders a layout partial which initializes the BrowserID
|
6
|
+
# system. This should be called in the head of the application layout.
|
7
|
+
#
|
8
|
+
# options - Hash used to adjust the browserid asset setup (default: {}).
|
9
|
+
# :login_path - String giving the path to POST assertions to
|
10
|
+
# for verification. Defaults to the configured
|
11
|
+
# `browserid.login.path`.
|
12
|
+
# :logout_path - String giving the path to POST logout
|
13
|
+
# notifications to. Defaults to the configured
|
14
|
+
# `browserid.logout.path`.
|
15
|
+
# :debug - Boolean determining whether the browserid
|
16
|
+
# javascript will refresh the page or show an
|
17
|
+
# alert dialog.
|
18
|
+
# block - An optional block which can be used to provide additional
|
19
|
+
# content to be rendered inside the browserid setup script tag.
|
20
|
+
#
|
21
|
+
# Examples
|
22
|
+
#
|
23
|
+
# <!-- Perform basic BrowserID setup in the head section -->
|
24
|
+
# <%= setup_browserid %>
|
25
|
+
#
|
26
|
+
# <!-- Setup BrowserID with alert debugging -->
|
27
|
+
# <%= setup_browserid debug: true %>
|
28
|
+
#
|
29
|
+
# <!-- Setup BrowserID with a custom handler -->
|
30
|
+
# <%= setup_browserid do %>
|
31
|
+
# browserid.onLogin = function (data, status, xhr) {
|
32
|
+
# // ...
|
33
|
+
# }
|
34
|
+
# <% end %>
|
35
|
+
#
|
36
|
+
def setup_browserid(options={}, &block)
|
37
|
+
defaults = { login_path: browserid_config.login.path, logout_path: browserid_config.logout.path }
|
38
|
+
content_for :browserid_setup, capture(&block) if block_given?
|
39
|
+
render 'shared/browserid', options: defaults.merge(options)
|
40
|
+
end
|
41
|
+
|
42
|
+
# Public: Renders a login link which will request a new authentication
|
43
|
+
# assertion from the BrowserID javascript code. The default link text is
|
44
|
+
# configurable with `config.browserid.login.text`. The link target is
|
45
|
+
# similarly configurable with `config.browserid.login.path`.
|
46
|
+
#
|
47
|
+
# text - Optional String to use as link text (default: configured value).
|
48
|
+
def login_link(text=nil)
|
49
|
+
text ||= browserid_config.login.text
|
50
|
+
target = browserid_config.login.path || '#'
|
51
|
+
link_to text, target, class: :browserid_login
|
52
|
+
end
|
53
|
+
|
54
|
+
# Public: Renders a logout link which will clear the current BrowserID
|
55
|
+
# authentication status. The default link text is configurable with
|
56
|
+
# `config.browserid.logout.text`. The link target is similarly
|
57
|
+
# configurable with `config.browserid.logout.path`.
|
58
|
+
#
|
59
|
+
# text - Optional String to use as link text (default: configured value).
|
60
|
+
def logout_link(text=nil)
|
61
|
+
text ||= browserid_config.logout.text
|
62
|
+
target = browserid_config.logout.path || '#'
|
63
|
+
link_to text, target, class: :browserid_logout
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
require 'json'
|
2
|
+
require 'net/https'
|
3
|
+
|
4
|
+
module BrowserID
|
5
|
+
module Verifier
|
6
|
+
# Public: This class sends the assertion to Mozilla's Persona server for
|
7
|
+
# verification.
|
8
|
+
class Persona
|
9
|
+
attr_accessor :server, :path
|
10
|
+
|
11
|
+
# Public: String defining the endpoint of the server to perform Persona
|
12
|
+
# verifications against.
|
13
|
+
VERIFICATION_SERVER = 'verifier.login.persona.org'
|
14
|
+
|
15
|
+
# Public: String defining the normal path to POST assertion verifications
|
16
|
+
# to.
|
17
|
+
VERIFICATION_PATH = '/verify'
|
18
|
+
|
19
|
+
# Public: Constructs a new Persona verifier.
|
20
|
+
#
|
21
|
+
# server - Domain String of the server to send assertions to for
|
22
|
+
# verifications (default: VERIFICATION_SERVER).
|
23
|
+
# path - Path String to POST to on the server (default:
|
24
|
+
# VERIFICATION_PATH).
|
25
|
+
#
|
26
|
+
def initialize(server=VERIFICATION_SERVER, path=VERIFICATION_PATH)
|
27
|
+
@server = server
|
28
|
+
@path = path
|
29
|
+
end
|
30
|
+
|
31
|
+
# Public: Verifies a Persona assertion for a given audience.
|
32
|
+
#
|
33
|
+
# assertion - Persona authentication assertion.
|
34
|
+
# audience - Audience String to verify assertion against. This should be
|
35
|
+
# the URI of the service with scheme, authority, and port.
|
36
|
+
#
|
37
|
+
# Returns the authenticated email address String and the issuing domain
|
38
|
+
# if the assertion is valid.
|
39
|
+
# Raises an exception with a failure message if the client was not
|
40
|
+
# successfully authenticated.
|
41
|
+
#
|
42
|
+
# Examples
|
43
|
+
#
|
44
|
+
# verify(assertion, "https://app.example.com:443")
|
45
|
+
# # => "user@example.com", "persona.mozilla.com"
|
46
|
+
#
|
47
|
+
def verify(assertion, audience)
|
48
|
+
http = Net::HTTP.new(@server, 443)
|
49
|
+
http.use_ssl = true
|
50
|
+
|
51
|
+
verification = Net::HTTP::Post.new(@path)
|
52
|
+
verification.set_form_data(assertion: assertion, audience: audience)
|
53
|
+
|
54
|
+
response = http.request(verification)
|
55
|
+
raise "Unsuccessful response from #{@server}: #{response}" unless response.kind_of? Net::HTTPSuccess
|
56
|
+
authentication = JSON.parse(response.body)
|
57
|
+
|
58
|
+
# Authentication response is a JSON hash which must contain a 'status'
|
59
|
+
# of "okay" or "failure".
|
60
|
+
status = authentication['status']
|
61
|
+
raise "Unknown authentication status '#{status}'" unless %w{okay failure}.include? status
|
62
|
+
|
63
|
+
# An unsuccessful authentication response should contain a reason string.
|
64
|
+
raise "Assertion failure: #{authentication['reason']}" unless status == "okay"
|
65
|
+
|
66
|
+
# A successful response looks like the following:
|
67
|
+
# {
|
68
|
+
# "status": "okay",
|
69
|
+
# "email": "user@example.com",
|
70
|
+
# "audience": "https://service.example.com:443",
|
71
|
+
# "expires": 1234567890,
|
72
|
+
# "issuer": "persona.mozilla.com"
|
73
|
+
# }
|
74
|
+
|
75
|
+
auth_audience = authentication['audience']
|
76
|
+
raise "Persona assertion audience '#{auth_audience}' does not match verifier audience '#{audience}'" unless auth_audience == audience
|
77
|
+
|
78
|
+
expires = authentication['expires'] && Time.at(authentication['expires'].to_i/1000.0)
|
79
|
+
raise "Persona assertion expired at #{expires}" if expires && expires < Time.now
|
80
|
+
|
81
|
+
[authentication['email'], authentication['issuer']]
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
metadata
ADDED
@@ -0,0 +1,148 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: browserid-auth-rails
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.5.3
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Greg Look
|
9
|
+
- Alex Kravets
|
10
|
+
autorequire:
|
11
|
+
bindir: bin
|
12
|
+
cert_chain: []
|
13
|
+
date: 2013-03-04 00:00:00.000000000 Z
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: railties
|
17
|
+
requirement: !ruby/object:Gem::Requirement
|
18
|
+
none: false
|
19
|
+
requirements:
|
20
|
+
- - ~>
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: '3.2'
|
23
|
+
type: :runtime
|
24
|
+
prerelease: false
|
25
|
+
version_requirements: !ruby/object:Gem::Requirement
|
26
|
+
none: false
|
27
|
+
requirements:
|
28
|
+
- - ~>
|
29
|
+
- !ruby/object:Gem::Version
|
30
|
+
version: '3.2'
|
31
|
+
- !ruby/object:Gem::Dependency
|
32
|
+
name: rspec-rails
|
33
|
+
requirement: !ruby/object:Gem::Requirement
|
34
|
+
none: false
|
35
|
+
requirements:
|
36
|
+
- - ~>
|
37
|
+
- !ruby/object:Gem::Version
|
38
|
+
version: '2.11'
|
39
|
+
type: :development
|
40
|
+
prerelease: false
|
41
|
+
version_requirements: !ruby/object:Gem::Requirement
|
42
|
+
none: false
|
43
|
+
requirements:
|
44
|
+
- - ~>
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: '2.11'
|
47
|
+
- !ruby/object:Gem::Dependency
|
48
|
+
name: simplecov
|
49
|
+
requirement: !ruby/object:Gem::Requirement
|
50
|
+
none: false
|
51
|
+
requirements:
|
52
|
+
- - ! '>='
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
type: :development
|
56
|
+
prerelease: false
|
57
|
+
version_requirements: !ruby/object:Gem::Requirement
|
58
|
+
none: false
|
59
|
+
requirements:
|
60
|
+
- - ! '>='
|
61
|
+
- !ruby/object:Gem::Version
|
62
|
+
version: '0'
|
63
|
+
- !ruby/object:Gem::Dependency
|
64
|
+
name: tzinfo
|
65
|
+
requirement: !ruby/object:Gem::Requirement
|
66
|
+
none: false
|
67
|
+
requirements:
|
68
|
+
- - ! '>='
|
69
|
+
- !ruby/object:Gem::Version
|
70
|
+
version: '0'
|
71
|
+
type: :development
|
72
|
+
prerelease: false
|
73
|
+
version_requirements: !ruby/object:Gem::Requirement
|
74
|
+
none: false
|
75
|
+
requirements:
|
76
|
+
- - ! '>='
|
77
|
+
- !ruby/object:Gem::Version
|
78
|
+
version: '0'
|
79
|
+
- !ruby/object:Gem::Dependency
|
80
|
+
name: jquery-rails
|
81
|
+
requirement: !ruby/object:Gem::Requirement
|
82
|
+
none: false
|
83
|
+
requirements:
|
84
|
+
- - ! '>='
|
85
|
+
- !ruby/object:Gem::Version
|
86
|
+
version: '0'
|
87
|
+
type: :runtime
|
88
|
+
prerelease: false
|
89
|
+
version_requirements: !ruby/object:Gem::Requirement
|
90
|
+
none: false
|
91
|
+
requirements:
|
92
|
+
- - ! '>='
|
93
|
+
- !ruby/object:Gem::Version
|
94
|
+
version: '0'
|
95
|
+
description: ! " This gem provides a lightweight single-sign-on authentication
|
96
|
+
framework to\n a Rails application based on Mozilla's Persona service (see:\n
|
97
|
+
\ https://login.persona.org/about). Persona identifies users by email address\n
|
98
|
+
\ and authenticates them using the BrowserID protocol. This lets a client\n prove
|
99
|
+
they own an email address without exposing their browsing behaviors to\n the
|
100
|
+
identity provider. This also frees the application from needing to\n securely
|
101
|
+
handle and store user credentials.\n"
|
102
|
+
email:
|
103
|
+
- alex@slatestudio.com
|
104
|
+
executables: []
|
105
|
+
extensions: []
|
106
|
+
extra_rdoc_files: []
|
107
|
+
files:
|
108
|
+
- app/assets/javascripts/browserid.js.coffee
|
109
|
+
- app/views/shared/_browserid.html.erb
|
110
|
+
- lib/browserid/rails/base.rb
|
111
|
+
- lib/browserid/rails/helpers.rb
|
112
|
+
- lib/browserid/rails/version.rb
|
113
|
+
- lib/browserid/verifier/persona.rb
|
114
|
+
- lib/browserid-auth-rails.rb
|
115
|
+
- lib/browserid-rails.rb
|
116
|
+
- LICENSE
|
117
|
+
- README.md
|
118
|
+
homepage: https://github.com/alexkravets/browserid-auth-rails
|
119
|
+
licenses: []
|
120
|
+
post_install_message:
|
121
|
+
rdoc_options: []
|
122
|
+
require_paths:
|
123
|
+
- lib
|
124
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
125
|
+
none: false
|
126
|
+
requirements:
|
127
|
+
- - ! '>='
|
128
|
+
- !ruby/object:Gem::Version
|
129
|
+
version: '0'
|
130
|
+
segments:
|
131
|
+
- 0
|
132
|
+
hash: -465273068887906201
|
133
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
134
|
+
none: false
|
135
|
+
requirements:
|
136
|
+
- - ! '>='
|
137
|
+
- !ruby/object:Gem::Version
|
138
|
+
version: '0'
|
139
|
+
segments:
|
140
|
+
- 0
|
141
|
+
hash: -465273068887906201
|
142
|
+
requirements: []
|
143
|
+
rubyforge_project:
|
144
|
+
rubygems_version: 1.8.24
|
145
|
+
signing_key:
|
146
|
+
specification_version: 3
|
147
|
+
summary: Lightweight Rails authentication framework using the BrowserID protocol.
|
148
|
+
test_files: []
|