SignedForm
SignedForm brings new convenience and security to your Rails 4 or Rails 3 application.
How It Works
Traditionally, when you create a form with Rails you enter your fields using something like f.text_field :name
and so
on. Once you're done making your form you need to make sure that you've either set those parameters as accessible in
the model (Rails 3) or use permit
(Rails 4). This is redundant. Why would you make a form for a user to fill out and
then not accept their input? You need to always maintain this synchronization.
SignedForm generates a list of attributes that you have in your form and attaches them to be submitted with the form
along with a HMAC-SHA1 signature of those attributes to protect them from tampering. That means no more permit
and
no more attr_accessible
. It just works.
What this looks like:
<%= signed_form_for(@user) do |f| %>
<% f.add_signed_fields :zipcode, :state # Optionally add additional fields to sign %>
<%= f.text_field :name %>
<%= f.text_field :address %>
<%= f.submit %>
<% end %>
UsersController < ApplicationController
def create
@user = User.find params[:id]
@user.update_attributes params[:user]
end
end
That's it. You're done. Need to add a field? Pop it in the form. You don't need to then update a list of attributes.
signed_form_for
works just like the standard form_for
.
Of course, you're free to continue using the standard form_for
. SignedForm
is strictly opt-in. It won't change the
way you use standard forms.
Requirements
SignedForm requires:
- Ruby 1.9 or later
- Rails 4 or Rails 3.1+ (strong_parameters gem required for Rails 3)
Installation
Add this line to your application's Gemfile:
gem 'signed_form'
And then execute:
$ bundle
If you're using Rails 3, you'll also need to install the strong_parameters gem. Please set it up as instructed on the linked GitHub repo.
If you're using Rails 4, it works out of the box.
You'll need to include SignedForm::ActionController::PermitSignedParams
in the controller(s) you want to use
SignedForm with. This can be done application wide by adding the include
to your ApplicationController.
ApplicationController < ActionController::Base
include SignedForm::ActionController::PermitSignedParams
# ...
end
You'll also need to create an initializer:
$ echo 'SignedForm::HMAC.secret_key = SecureRandom.hex(64)' > config/initializers/signed_form.rb
IMPORTANT Please read below for information regarding this secret key.
Support for other Builders
Special Considerations
If you're running only a single application server the above initializer should work great for you, with a couple of
caveats. If a user is in process of filling out a form and you restart your server, their form will be invalidated.
You could pick a secret key using rake secret
and put that in the initializer instead, but then in the event you
remove a field someone could still access it using the old signature if some malicious person were to keep it around.
If you're running multiple application servers, the above initializer will not work. You'll need to keep the key in sync between all the servers. The security caveat with that is that if you ever remove a field from a form without updating that secret key, a malicious user could still access the field with the old signature. So you'll probably want to choose a new secret in the event you remove access to an attribute in a form.
My above initializer example errs on the side of caution, generating a new secret key every time the app starts up. Only you can decide what is right for you with respect to the secret key.
Multiple Access Points
Take for example the case where you have an administrative backend. You might have /admin/users/edit
. Users can also
change some information about themselves though, so there's /users/edit
as well. Now you have an admin that gets
demoted, but still has a user account. If that admin were to retain a form signature from /admin/users/edit
they could
use that signature to modify the same fields from /users/edit
. As a means of preventing such access SignedForm provides
the sign_destination
option to signed_form_for
. Example:
<%= signed_form_for(@user, sign_destination: true) do |f| %>
<%= f.text_field :name %>
<!-- ... -->
<% end %>
With sign_destination
enabled, a form generated with a destination of /admin/users/5
for example will only be
accepted at that end point. The form would not be accepted at /users/5
. So in the event you would like to use
SignedForm on forms for the same resource, but different access levels, you have protection against the form being used
elsewhere.
Caching
Another consideration to be aware of is caching. If you cache a form, and then change the secret key that form will perpetually submit parameters that fail verification. So if you want to cache the form you should tie the cache key to something that will be changed whenever the secret key changes.
Contributing
- Fork it
- 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 new Pull Request