How to Add an Invitation Code to Devise and Rails
April 20, 2016
Ever wonder how to restrict account sign up to invited guests using Devise and Rails? This easy-to-implement technique adds an invitation code field to the Devise gem’s sign-up form, ensuring only visitors who were sent an invitation code can create an account.
Customize the Devise Sign-up View
The first step is to create an :invite
field in the Devise sign-up view. To do that I need to generate the Devise views by hitting rails generate devise:views
and then add the :invite
field to the form:
<%= form_for(resource, as: resource_name, url: registration_path(resource_name)) do |f| %>
<%= devise_error_messages! %>
<div class="form-group">
<%= f.label :email %>
<%= f.email_field :email, autofocus: true %>
</div>
<div class="form-group">
<%= f.label :invite, "Invitation Code" %>
<%= f.text_field :invite, placeholder: "Input invitation code" %>
</div>
<div class="form-group">
<%= f.label :password %>
<% if @minimum_password_length %>
<em class="devise-label">(<%= @minimum_password_length %> characters minimum)</em>
<% end %><br />
<%= f.password_field :password, autocomplete: "off" %>
</div>
<div class="form-group">
<%= f.label :password_confirmation %>
<%= f.password_field :password_confirmation, autocomplete: "off" %>
</div>
<div class="actions form-group">
<%= f.submit "Sign up" %>
</div>
<% end %>
<%= render "devise/shared/links" %>
Usually I’d create a migration and add the :invite
attribute to the Users table, but I don’t need to go through all of that trouble here. I just need to validate what the visitor enters in the :invite
field is indeed the invitation code I sent to them. And to do that, all I need is a virtual attribute.
Virtual Attribute Validation
Next, I add the virtual attribute :invite
to the User model via attr_accessor
:
class User < ActiveRecord::Base
attr_accessor :invite
end
Now, I need a method I can call to validate the :invite
field and I only want to trigger this validation on the create action:
class User < ActiveRecord::Base
validate :validate_invite, :on => :create
attr_accessor :invite
def validate_invite
if self.invite != “my_invite_code"
self.errors[:base] << "The Invitation Code is Incorrect"
end
end
end
If the value of self.invite
is not equal to “myinvitecode”, an exception is raised, the visitor sees the error message “The Invitation Code is Incorrect”, and a new User will not be created. If the two values are the same, the new User will be created.
Of course, I can’t hardcode the invitation code into the conditional and push it to GitHub. For situations like this, I like to use the Figaro gem to keep my passwords or API keys out of version control. Figaro creates an application.yml
file were I can store the invitation code as a key-value pair: invitation_code: "my_invitation_code"
. Figaro automatically adds application.yml
to .gitignore
, ensuring my sensitive data is not pushed to GitHub. The ready-for-production method now looks like this:
def validate_invite
if self.invite != Figaro.env.invitation_code
self.errors[:base] << "The Invitation Code is Incorrect"
end
end
Configure Strong Params
Last and certainly not least, I need to update the strong parameters for the Devise sign-up form so the :invite
field will be permitted. I’ll use the before_action
to configure Devise’s standard parameters:
class ApplicationController < ActionController::Base
# Prevent CSRF attacks by raising an exception.
# For APIs, you may want to use :null_session instead.
protect_from_forgery with: :exception
before_action :configure_permitted_parameters, if: :devise_controller?
protected
def configure_permitted_parameters
devise_parameter_sanitizer.for(:sign_up) { |u| u.permit(:email, :password, :password_confirmation, :invite) }
end
end
Admittedly, this technique might not be suited for all apps since every new user will be sent the same invitation code. Some apps may require dynamic invitation codes that are different for every user (hey, another blog post!) but this technique is more than capable of handling simple invitation-only app sign ups.
Hi, I’m Ryan McMahon—a software developer who lives and works in Buffalo, NY. I build things for the web using React, Ruby, Rails, and .NET. Connect with me on Twitter.