You are on page 1of 11

Password-Less Authentication in Rails

23/10/16 8)05 p.m.

(/)
" Search

Ruby(https://www.sitepoint.com/ruby/)

# Forums(https://www.sitepoint.com/community/)

Premium(https://www.sitepoint.com/premium

Article

10 free SitePoint ebooks


Just because we like you
Password-Less Authentication
in Rails
By Vinoth (https://www.sitepoint.com/author/avinoth/)

October
2016
Claim20,
Your
Ebooks Now!
Totally free. No credit card required.

Authentication is one of the key elements of many web


applications. It is the stone wall between the application
and its users, so its important that the authentication
approach is secure and easy to use. But, what is this
authentication? Its a way of ensuring only users
authorized by our application are allowed to use the
application. As I am sure you know, there are many ways
to authenticate a user, such as Email/Password, OpenID
Connect, SAML, SSO, and so on.

RECOMMENDED
1

(https://www.sitepoint.com/better-nested-a
the-cocoon-gem/?

utm_source=sitepoint&utm_medium=relate

Today, were going to take a look at another approach:


Password-less authentication.

Better Nested Attributes in Rails with the C

DRY Off Your Rails Code with ActiveSuppor

(https://www.sitepoint.com/dry-off-your-ra
activesupportconcerns/?

What is Password-less Authentication?


When a user registers for a website, the application allows the user to chose their credentials, usually
a username or email/password. The user can then enter those credentials anytime to login to the
application. Password-less authentication is basically eliminating the password part and using just the

utm_source=sitepoint&utm_medium=relate
3

(https://www.sitepoint.com/how-to-createlocations-recorder-with-couchdb/?

email to both register and login.


How this works is, when a user registers to our application, an email is sent to activate their account.
This allows us to verify if the email belongs to the user. Now that we have a veriYed email. The next
time that user tries to login, we will send them an email with the token for the user to use to sign into
the app. Once the user clicks on the link with the token, the application will authenticate the user.
Lets get started. Initialize your rails application:

How to Create a Pokemon Spawn Locations

utm_source=sitepoint&utm_medium=relate
4

Phpseclib: Securely Communicating with R

(https://www.sitepoint.com/phpseclib-secu
remote-servers-via-php/?

utm_source=sitepoint&utm_medium=relate
5

Tools for a Modern Ruby Development Setu

(https://www.sitepoint.com/tools-for-a-mo
setup/?

$ rails new passwordless


$ cd passwordless

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

Creating the Model


Lets start with creating the model necessary for our application. We are going to call this model User
since its users we are authenticating. But you are free to use anything that works.
% 00:17:01
https://www.sitepoint.com/password-less-authentication-in-rails/

RUBY

PasswordLess
Authentication
in Rails

RUBY

(https
less-a

(https://ww

Pgina 1 de 11

Password-Less Authentication in Rails

23/10/16 8)05 p.m.

% 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:

validates :email, :username, uniqueness: true, presence: true

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:

LEARN CODING ONLINE

& Learn Web


Development
Start learning web
development and design for
free with SitePoint Premium!

...
before_save :format_email_username

Start Learning Now (Https://Www.site


Utm_source=Sitepoint&Utm_medium=S

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:

$ rails g controller static home

Add the below route to conZg/routes.rb:

https://www.sitepoint.com/password-less-authentication-in-rails/

Pgina 2 de 11

Password-Less Authentication in Rails

23/10/16 8)05 p.m.

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>
...

Generate a users controller:

$ rails g controller users

In conZg/routes.rb, add the following routes which well use for user registration:

resources :users, only: [:create]


get '/register', to: 'users#register'

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

Password-Less Authentication in Rails

23/10/16 8)05 p.m.

<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

Password-Less Authentication in Rails

23/10/16 8)05 p.m.

...
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

Password-Less Authentication in Rails

23/10/16 8)05 p.m.

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.

$ rails g controller session auth

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

Password-Less Authentication in Rails

23/10/16 8)05 p.m.

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:

resources :session, only: [:new, :create]

and add the below code to the /app/controllers/session_controller.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:

<%= form_tag "/session" do %>


<label> Email / Username </label>
<%= text_field_tag "value" %>
<%= submit_tag "Login" %>
<% end %>

Its just a simple form that does the job for us.

Conclusion
With that, we have arrived at the conclusion of

' More from this author

the tutorial. Password-less login is really picking

Authenticate Your Rails API with JWT

up these days and it provides our users with a

from Scratch

less distracting and more convenient way to


authenticate. Oh, and it also provides less

(https://www.sitepoint.com/authenticat

overhead for everyone involved. I encourage you

utm_source=sitepoint&utm_medium=re

to try the password-less login approach for your


application, at least as a supplemental method

latedinline&utm_term=&utm_campaign

for login. That would be a start!

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

Password-Less Authentication in Rails

23/10/16 8)05 p.m.

All the sample code used in this tutorial is

utm_source=sitepoint&utm_medium=re

available on Github

latedinline&utm_term=&utm_campaign

(https://github.com/avinoth/passwordless-app-

=relatedauthor)

sample), feel free to fork and play with it. I thank


you for reading the tutorial and I hope it served
your purposes.

Was this helpful?


* More:

authentication (https://www.sitepoint.com/tag/authentication/), Ruby on Rails


(https://www.sitepoint.com/tag/ruby-on-rails/)

Meet the author


(htt Vinoth (https://www.sitepoint.com/author/avinoth/) +
ps:/
/ww (https://twitter.com/avinoth_) , (https://linkedin.com/in/avinoth) w.si
(https://github.com/avinoth)
tep
oint.
co
Vinoth is a Server Administrator turned Full stack web developer. He loves to try his hands on multiple
m/a
uth
programming
languages but his primary programming language of choice is Ruby. He is currently a
or/a
Software
Engineer @ Intelllex building the server side of things. You can Ynd more about him at
vino
th/)
avinoth.com (http://www.avinoth.com).

https://www.sitepoint.com/password-less-authentication-in-rails/

Pgina 8 de 11

Password-Less Authentication in Rails

3 Comments

23/10/16 8)05 p.m.

1
!

SitePoint

Recommend 1

Login

Sort by Best

Share

Join the discussion


William Hitoshi Kurosawa a day ago

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}"`

Method login_token_expired? should be reversed logic:

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

I think two factor authentication is the best choice today.

Reply Share
Subscribe d Add Disqus to your site Add Disqus Add

& Privacy

LATEST COURSES

Browse all 4 courses (/premium/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

Password-Less Authentication in Rails

/ 48m

23/10/16 8)05 p.m.

(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

Browse all 5 books (/premium/books/)

(/premium/books/)

(https://www.sitepoint.com/premium/books/jumpstart-git)

0 PREMIUM BOOK

Jump Start Git

(https://www.sitepoint.com/premium/books/jumpstart-rails)

0 PREMIUM BOOK

Jump Start Rails

(https://www.sitepoint.com/premium/books/jumpstart-sinatra)

0 PREMIUM BOOK

Jump Start Sinatra

Get the latest in Ruby, once a week,


for free.
Enter your email

Subscribe

Facebook

LinkedIn

Twitter

https://www.sitepoint.com/password-less-authentication-in-rails/

Pgina 10 de 11

Password-Less Authentication in Rails

23/10/16 8)05 p.m.

About

Visit

Connect

Our Story (/about-us/)

SitePoint Home (/)

Advertise (/advertise/)

Forums (https://www.sitepoint.com/community/)

Press Room (/press/)

Newsletters (/newsletter/)

Reference (http://reference.sitepoint.com/css/)

Premium (/premium/)

Terms of Use (/legals/)

References (/sass-reference/)

Privacy Policy (/legals/#privacy)

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/)

2000 2016 SitePoint Pty. Ltd.

https://www.sitepoint.com/password-less-authentication-in-rails/

Pgina 11 de 11

You might also like