Skip to content

Instantly share code, notes, and snippets.

@sebastiangeiger
Created December 23, 2013 18:43
Show Gist options
  • Save sebastiangeiger/8102377 to your computer and use it in GitHub Desktop.
Save sebastiangeiger/8102377 to your computer and use it in GitHub Desktop.

Email only signup with Rails 4 and Devise 3

The copy and paste version

Follow this guide, then we need change some little things.

Create a User.find_by_public_confirmation_token method.

class User < ActiveRecord::Base
  devise :database_authenticatable, :confirmable, ...
  ...
  def self.find_by_public_confirmation_token(public_confirmation_token)
     confirmation_token = Devise.token_generator.digest(self, :confirmation_token, public_confirmation_token)
     find_by_confirmation_token(confirmation_token)
  end
end

Then use this new method instead of User.find_by_confirmation_token.

class ConfirmationsController < Devise::ConfirmationsController

  def show
    ...
    self.resource = resource_class.find_by_public_confirmation_token(params[:confirmation_token]) if params[:confirmation_token].present?
    ...
  end

  def confirm
    ...
    self.resource = resource_class.find_by_public_confirmation_token(params[:confirmation_token]) if params[:confirmation_token].present?
    ...
      self.resource = resource_class.confirm_by_token(params[:confirmation_token])
    ...
  end
end

Then change the hidden field to pass the confirmation token in app/views/confirmations/show.html.erb.

<%= hidden_field_tag :confirmation_token, params[:confirmation_token] %>

Background

Devise uses two confirmation tokens. A public one that is sent to your user and a second confirmation token that is stored in the database and used in the confirm_by_token method. The trouble starts when you pass the public confirmation token to devise methods like find_by_confirmation_token that expect the private confirmation token.

In my case the email template was in app/views/devise/mailer/confirmation_instructions.html.erb:

 <%= link_to 'Confirm my account', confirmation_url(@resource, confirmation_token: @token) %>

When you set a breakpoint in that mailer method, you'll see those two tokens in action.

[10] pry(#<#<Class:0x007fbb7f5690e0>>)> @resource.confirmation_token
=> "d1115ad6d5f8e59fd6416f54d2651141294ae93d394f5dc1e958cafa19625132"
[11] pry(#<#<Class:0x007fbb7f5690e0>>)> @token
=> "RUx9NoTws175xHR6xK47"

TODO: Find out where @token is set, how it is generated from the confirmation_token

Looking at the confirm_by_token method in /devise/models/confirmable.rb [TODO: Link to gh] shows you how the two tokens are connected.

def confirm_by_token(confirmation_token)
  original_token     = confirmation_token
  confirmation_token = Devise.token_generator.digest(self, :confirmation_token, confirmation_token)

  confirmable = find_or_initialize_with_error_by(:confirmation_token, confirmation_token)
  confirmable.confirm! if confirmable.persisted?
  confirmable.confirmation_token = original_token
  confirmable
end

With Devise.token_generator.digest you can convert the public confirmation token to the internal confirmation token.

Wrapping up

The User.find_by_public_confirmation_token method that I defined earlier is a simple hack that translates the public confirmation token into a private confirmation token using the token_generator and then uses find_by_confirmation_token that comes with devise.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment