Professional Documents
Culture Documents
(/)
" Search
Ruby(https://www.sitepoint.com/ruby/)
# Forums(https://www.sitepoint.com/community/)
Premium(https://www.sitepoint.com/premium
Article
October
2016
Claim20,
Your
Ebooks Now!
Totally free. No credit card required.
RECOMMENDED
1
(https://www.sitepoint.com/better-nested-a
the-cocoon-gem/?
utm_source=sitepoint&utm_medium=relate
(https://www.sitepoint.com/dry-off-your-ra
activesupportconcerns/?
utm_source=sitepoint&utm_medium=relate
3
(https://www.sitepoint.com/how-to-createlocations-recorder-with-couchdb/?
utm_source=sitepoint&utm_medium=relate
4
(https://www.sitepoint.com/phpseclib-secu
remote-servers-via-php/?
utm_source=sitepoint&utm_medium=relate
5
(https://www.sitepoint.com/tools-for-a-mo
setup/?
utm_source=sitepoint&utm_medium=relate
SPONSORS
To get a clearer sense of whats really happening, we wont be using any libraries or gems for this
tutorial.
# 3 COMMENTS
RUBY
PasswordLess
Authentication
in Rails
RUBY
(https
less-a
(https://ww
Pgina 1 de 11
% 00:17:01
RUBY
Feature
$ rails g scaffold user fullname username:uniq email:uniq login_token token_generated_at:datetime
Tests
$ rails db:create && rails db:migrate
with
RSpec:
Simulate
User
Behavior
The fullname and username are optional. Weve added username to enable users to login via
and Test
either email or username. We also have couple of other columns, login_token and
Your
token_generated_at, which are basically the one time password we generate for our users, along
Ruby
with when it was generated.
App
tests-with-r
app)
Theres a unique constraint at the table level, but lets also add the ActiveRecord validations for the
model. Add the following to the app/models/user.rb:
Along with this, lets also add a before Ylter to format the username and email before saving the
record. These validations should also be in the client, but this is an added measure. Add the before
Ylter:
...
before_save :format_email_username
def format_email_username
self.email = self.email.delete(' ').downcase
self.username = self.username.delete(' ').downcase
end
Here we basically strip the spaces in username and email and making it lowercase before saving it
to the database.
Since were going to allow for users to login via username and email, lets add a helper method that
fetches the user record based on either:
...
def self.find_user_by(value)
where(["username = :value OR email = :value", {value: value}]).first
end
Registration
With the model done and in place, lets go ahead and create our controller Yle and the necessary
routes. Before we go about creating the registration controller, quickly create a static page to show
messages:
https://www.sitepoint.com/password-less-authentication-in-rails/
Pgina 2 de 11
root 'static#home'
Along with this, also add the following line to your app/views/layouts/application.html.erb, which is
used to display messages to the user:
...
<body>
<p id="notice"><%= notice %></p>
...
In conZg/routes.rb, add the following routes which well use for user registration:
Now, lets add the code in app/controllers/users_controller.rb that corresponds to the routes declared
above:
def register
@user = User.new
end
def create
@user = User.new(user_params)
if @user.save
redirect_to root_path, notice: 'Welcome! We have sent you the link to login to our app'
else
render :register
end
end
private
def user_params
params.require(:user).permit(:fullname, :username, :email)
end
Now, create the view Yle for registration under app/views/users/register.html.erb and add the this
form to it:
https://www.sitepoint.com/password-less-authentication-in-rails/
Pgina 3 de 11
<h1>Register</h1>
<%= form_for(@user) do |f| %>
<% if @user.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(@user.errors.count, "error") %> prohibited this @user from being saved:</h2>
<ul>
<% @user.errors.full_messages.each do |message| %>
<li><%= message %></li>
<% end %>
</ul>
</div>
<% end %>
<div class="field">
<%= f.label :fullname %>
<%= f.text_field :fullname %>
</div>
<div class="field">
<%= f.label :username %>
<%= f.text_field :username %>
</div>
<div class="field">
<%= f.label :email %>
<%= f.text_field :email %>
</div>
<div class="actions">
<%= f.submit 'Register' %>
</div>
<% end %>
Noting special in this form. This is a standard Rails, scaffole-generated form which captures the
fullname, username, and email for the user and sends it to our create endpoint. Start the Rails
server and head over to /register and see the registration is live now!
Login Link
Lets get to the meaty part of the application: sending login emails. Basically, when a new user
registers or whenever they request to login, wed have to send them a login link with a token. When the
link is clicked, the app will login the user. Begin by adding the following helper methods to our
apps/models/user.rb for sending emails:
https://www.sitepoint.com/password-less-authentication-in-rails/
Pgina 4 de 11
...
def send_login_link
generate_login_token
template = 'login_link'
UserMailer.send('emplate).deliver_now
end
def generate_login_token
self.login_token = generate_token
self.token_generated_at = Time.now.utc
save!
end
def login_link
"http://localhost:3000/auth?#{self.login_token}"
end
def login_token_expired?
(self.token_generated_at + token_validity) > Time.now.utc
end
def expire_token!
self.login_token = nil
save!
end
private
def generate_token
SecureRandom.hex(10)
end
def token_validity
2.hours
end
end
Please dont get hung up on where the code that sends the mail lives. I am trying to keep the
noise down as much as possible and focus on the higher-level concepts. I would never put a
UserMailer, for example, in a model, but this is for demonstration purposes only.
So, we have the send_login_link method which well make use of shortly to send the login link for
a user. Once we generate the login token, send them an email using ActionMailer UserMailer.
Setting up the mailing functionality is skipped in this tutorial since there are many good tutorials out
there that explains how to do them according to your email provider. Just make sure you include the
below line in your email template that you send to the user.
<%= link_to 'Please click this link to login to our app', @user.login_link %>
https://www.sitepoint.com/password-less-authentication-in-rails/
Pgina 5 de 11
The login_link is conYgured with a localhost url, but change it accordingly for your application.
Also, the token_validity duration is set to 2 hours, but you are free to change it, obviously. Finally,
add this line to the app/controllers/users_controller.rb create action right after the @user.save
line:
...
if @user.save
@user.send_login_link
...
Now that we have the necessary helper methods in place, lets add the receiving route to handle the
login link we send in the email.
Session Controller
Start by generating the controller for session.
Update in the conZg/routes.rb Yle, changing get 'session/auth' to get '/auth', to:
'session#auth'. In the generated session_controller.rb Yle, add this code:
def auth
token = params[:token].to_s
user = User.find_by(token: token)
if !user
redirect_to root_path, notice: 'It seems your link is invalid. Try requesting for a new login link'
elsif user.login_token_expired?
redirect_to root_path, notice: 'Your login link has been expired. Try requesting for a new login link.'
else
signin_user(user)
redirect_to root_path, notice: 'You have been signed in!'
end
end
Using the helper method, check whether the token is a valid or if its expired. If its not valid, redirect to
the home page with the appropriate messages. There is a helper method we have used,
sign_in_user, which we have to create. Open up app/controllers/application_controller.rb and
add:
def sign_in_user(user)
user.expire_token!
session[:email] = user.email
end
def current_user
User.find_by(email: session[:email])
end
https://www.sitepoint.com/password-less-authentication-in-rails/
Pgina 6 de 11
We basically expire the token of the user and store the users email to the session. Also, we have
added a helper method to retrieve the user from the session. The password-less functionality is ready,
go ahead and try registering for a new user for testing the login functionality.
Login
As a Ynal step, well make use of our helper methods to do the user login. Start by adding these routes
to the conZg/routes.rb Yle:
def new
end
def create
value = params[:value].to_s
user = User.find_user_by(value)
if !user
redirect_to new_session_path, notice: "Uh oh! We couldn't find the username / email. Please try again."
else
user.send_login_link
redirect_to root_path, notice: 'We have sent you the link to login to our app'
end
end
We have just made use of the send_login_link to do the heavy lifting. The Ynal piece of the app is
the login form. Create the Yle app/views/session/new.html.erb and add the following form:
Its just a simple form that does the job for us.
Conclusion
With that, we have arrived at the conclusion of
from Scratch
(https://www.sitepoint.com/authenticat
utm_source=sitepoint&utm_medium=re
latedinline&utm_term=&utm_campaign
e-your-rails-api-with-jwt-from-scratch/?
=relatedauthor)
Pry: A Simple Start
(https://www.sitepoint.com/pry-asimple-start/?
https://www.sitepoint.com/password-less-authentication-in-rails/
Pgina 7 de 11
utm_source=sitepoint&utm_medium=re
available on Github
latedinline&utm_term=&utm_campaign
(https://github.com/avinoth/passwordless-app-
=relatedauthor)
https://www.sitepoint.com/password-less-authentication-in-rails/
Pgina 8 de 11
3 Comments
1
!
SitePoint
Recommend 1
Login
Sort by Best
Share
Yep, cool tutorial to show alternatives to keep track of passwords. (even with security concerns
pointed by Brendon, it's really nice to think on alternatives)
Just let me point some typos, maybe could help anyone having trouble.
Method send_login_link, the argument being passed should be template instead of 'emplate
Method login_link should pass a key/value:
"http://localhost:3000/auth?token=#{self
self.login_token}"
instead of
"http://localhost:3000/auth?#{self.login_token}"`
see more
Reply Share
Brendon Murphy 2 days ago
Thanks for sharing this flow, it's a neat idea as an alternative to traditional passwords!
I think there's a couple pretty easy changes that could make this scheme more secure. First, since
the token is eectively a password (albeit short lived and one time) it would be good to store more
securely in the database. If you run it through bcrypt or something similar when saving, if read
access to the token column was compromised, your security is still likely intact. By storing them in
plaintext, if an attacker can read that column (for example, they compromise a fresh backup of
your database), they could potentially gain access to a user account.
Secondly, rather than finding the user by the token, you could route to auth like
/auth/:user_id/:token. Then you would find the user by user_id, and do a secure compare of the
token using bcrypt, so as to not be subject to timing attacks. Devise, for instance, used to ship
with a token authentication string mechanism, but dropped it due to the possibility of timing
attacks against a btree index on the token column. By finding via id, you are no longer subject to a
timing attack. The code would probably only be a handful of lines more, and I think would still be
pretty clear and concise.
Thanks again for sharing some outside the box thinking.
Reply Share
Jordano Moscoso > Brendon Murphy 8 hours ago
Reply Share
Subscribe d Add Disqus to your site Add Disqus Add
& Privacy
LATEST COURSES
(/premium/courses/)
/ 4h 7m
0 PREMIUM COURSE
(https://www.sitepoint.com/premium/courses/ruby2-0-2906)
Ruby 2.0
https://www.sitepoint.com/password-less-authentication-in-rails/
Pgina 9 de 11
/ 48m
(https://www.sitepoint.com/premium/courses/understandingthe-css-cascade-2874)
0 PREMIUM
COURSE
Understanding
the CSS
Cascade
/ 1h 35m
0 PREMIUM COURSE
Local Development
Environments for
Designers and
Developers
(https://www.sitepoint.com/premium/courses/localdevelopment-environments-for-designers-anddevelopers-2856)
LATEST BOOKS
(/premium/books/)
(https://www.sitepoint.com/premium/books/jumpstart-git)
0 PREMIUM BOOK
(https://www.sitepoint.com/premium/books/jumpstart-rails)
0 PREMIUM BOOK
(https://www.sitepoint.com/premium/books/jumpstart-sinatra)
0 PREMIUM BOOK
Subscribe
https://www.sitepoint.com/password-less-authentication-in-rails/
Pgina 10 de 11
About
Visit
Connect
Advertise (/advertise/)
Forums (https://www.sitepoint.com/community/)
Newsletters (/newsletter/)
Reference (http://reference.sitepoint.com/css/)
Premium (/premium/)
References (/sass-reference/)
Shop (https://shop.sitepoint.com)
FAQ (https://sitepoint.zendesk.com/hc/en-us)
Versioning (https://www.sitepoint.com/versioning/)
2
(https://www.sitepoint.
com/feed/) 3
(/newsletter/) 4
(https://www.facebook
.com/sitepoint) 5
(http://twitter.com/site
pointdotcom) +
(https://plus.google.co
m/+sitepoint)
Contact Us (mailto:feedback@sitepoint.com)
Contribute (/write-for-us/)
https://www.sitepoint.com/password-less-authentication-in-rails/
Pgina 11 de 11