• Stars
    star
    1,280
  • Rank 36,774 (Top 0.8 %)
  • Language
    Ruby
  • License
    MIT License
  • Created about 7 years ago
  • Updated 23 days ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

πŸ— Authentication for your Rails app without the icky-ness of passwords

Passwordless

CI Rubygems codecov

Add authentication to your Rails app without all the icky-ness of passwords. Magic link authentication, if you will. We call it passwordless.


Installation

Add to your bundle and copy over the migrations:

$ bundle add passwordless
$ bin/rails passwordless_engine:install:migrations

Upgrading

See Upgrading to Passwordless 1.0 for more details.

Usage

Passwordless creates a single model called Passwordless::Session, so it doesn't come with its own user model. Instead, it expects you to provide one, with an email field in place. If you don't yet have a user model, check out the wiki on creating the user model.

Enable Passwordless on your user model by pointing it to the email field:

class User < ApplicationRecord
  # your other code..

  passwordless_with :email # <-- here! this needs to be a column in `users` table

  # more of your code..
end

Then mount the engine in your routes:

Rails.application.routes.draw do
  passwordless_for :users

  # other routes
end

Getting the current user, restricting access, the usual

Passwordless doesn't give you current_user automatically. Here's how you could add it:

class ApplicationController < ActionController::Base
  include Passwordless::ControllerHelpers # <-- This!

  # ...

  helper_method :current_user

  private

  def current_user
    @current_user ||= authenticate_by_session(User)
  end

  def require_user!
    return if current_user
    save_passwordless_redirect_location!(User) # <-- optional, see below
    redirect_to root_path, alert: "You are not worthy!"
  end
end

Et voilΓ :

class VerySecretThingsController < ApplicationController
  before_action :require_user!

  def index
    @things = current_user.very_secret_things
  end
end

Providing your own templates

To make Passwordless look like your app, override the bundled views by adding your own. You can manually copy the specific views that you need or copy them to your application with rails generate passwordless:views.

Passwordless has 2 action views and 1 mailer view:

# the form where the user inputs their email address
app/views/passwordless/sessions/new.html.erb
# the form where the user inputs their just received token
app/views/passwordless/sessions/show.html.erb
# the email with the token and magic link
app/views/passwordless/mailer/sign_in.text.erb

See the bundled views.

Registering new users

Because your User record is like any other record, you create one like you normally would. Passwordless provides a helper method to sign in the created user after it is saved – like so:

class UsersController < ApplicationController
  include Passwordless::ControllerHelpers # <-- This!
  # (unless you already have it in your ApplicationController)

  def create
    @user = User.new(user_params)

    if @user.save
      sign_in(create_passwordless_session(@user)) # <-- This!
      redirect_to(@user, flash: { notice: 'Welcome!' })
    else
      render(:new)
    end
  end

  # ...
end

URLs and links

By default, Passwordless uses the resource name given to passwordless_for to generate its routes and helpers.

passwordless_for :users
  # <%= users_sign_in_path %> # => /users/sign_in

passwordless_for :users, at: '/', as: :auth
  # <%= auth_sign_in_path %> # => /sign_in

Also be sure to specify ActionMailer's default_url_options.host and tell the routes as well:

# config/application.rb for example:
config.action_mailer.default_url_options = {host: "www.example.com"}
routes.default_url_options[:host] ||= "www.example.com"

Configuration

To customize Passwordless, create a file config/initializers/passwordless.rb.

The default values are shown below. It's recommended to only include the ones that you specifically want to modify.

Passwordless.configure do |config|
  config.default_from_address = "[email protected]"
  config.parent_controller = "ApplicationController"
  config.parent_mailer = "ActionMailer::Base"
  config.restrict_token_reuse = false # Can a token/link be used multiple times?
  config.token_generator = Passwordless::ShortTokenGenerator.new # Used to generate magic link tokens.

  config.expires_at = lambda { 1.year.from_now } # How long until a signed in session expires.
  config.timeout_at = lambda { 10.minutes.from_now } # How long until a token/magic link times out.

  config.redirect_back_after_sign_in = true # When enabled the user will be redirected to their previous page, or a page specified by the `destination_path` query parameter, if available.
  config.redirect_to_response_options = {} # Additional options for redirects.
  config.success_redirect_path = '/' # After a user successfully signs in
  config.failure_redirect_path = '/' # After a sign in fails
  config.sign_out_redirect_path = '/' # After a user signs out

  config.paranoid = false # Display email sent notice even when the resource is not found.
end

Delivery method

By default, Passwordless sends emails. See Providing your own templates. If you need to customize this further, you can do so in the after_session_save callback.

In config/initializers/passwordless.rb:

Passwordless.configure do |config|
  config.after_session_save = lambda do |session, request|
    # Default behavior is
    # Passwordless::Mailer.sign_in(session).deliver_now

    # You can change behavior to do something with session model. For example,
    # SmsApi.send_sms(session.authenticatable.phone_number, session.token)
  end
end

Token generation

By default Passwordless generates short, 6-digit, alpha numeric tokens. You can change the generator using Passwordless.config.token_generator to something else that responds to call(session) eg.:

Passwordless.configure do |config|
  config.token_generator = lambda do |session|
    "probably-stupid-token-#{session.user_agent}-#{Time.current}"
  end
end

Passwordless will keep generating tokens until it finds one that hasn't been used yet. So be sure to use some kind of method where matches are unlikely.

Timeout and Expiry

The timeout is the time by which the generated token and magic link is invalidated. After this the token cannot be used to sign in to your app and the user will need to request a new token.

The expiry is the expiration time of the session of a logged in user. Once this is expired, the user is signed out.

Note: Passwordless' session relies on Rails' own session and so will never live longer than that.

To configure your Rails session, in config/initializers/session_store.rb:

Rails.application.config.session_store :cookie_store,
  expire_after: 1.year,
  # ...

Redirection after sign-in

By default Passwordless will redirect back to where the user wanted to go if it knows where that is -- so you'll have to help it. Passwordless::ControllerHelpers provide a method:

class ApplicationController < ActionController::Base
  include Passwordless::ControllerHelpers # <-- Probably already have this!

  # ...

  def require_user!
    return if current_user
    save_passwordless_redirect_location!(User) # <-- this one!
    redirect_to root_path, alert: "You are not worthy!"
  end
end

This can also be turned off with Passwordless.config.redirect_back_after_sign_in = false.

Looking up the user

By default Passwordless uses the passwordless_with column to case insensitively fetch the user resource.

You can override this by defining a class method fetch_resource_for_passwordless in your user model. This method will be called with the down-cased, stripped email and should return an ActiveRecord instance.

class User < ApplicationRecord
  def self.fetch_resource_for_passwordless(email)
    find_or_create_by(email: email)
  end
end

Test helpers

To help with testing, a set of test helpers are provided.

If you are using RSpec, add the following line to your spec/rails_helper.rb:

require "passwordless/test_helpers"

If you are using TestUnit, add this line to your test/test_helper.rb:

require "passwordless/test_helpers"

Then in your controller, request, and system tests/specs, you can utilize the following methods:

passwordless_sign_in(user) # signs you in as a user
passwordless_sign_out # signs out user

Security considerations

There's no reason that this approach should be less secure than the usual username/password combo. In fact this is most often a more secure option, as users don't get to choose the horrible passwords they can't seem to stop using. In a way, this is just the same as having each user go through "Forgot password" on every login.

But be aware that when everyone authenticates via emails, the way you send those mails becomes a weak spot. Email services usually provide a log of all the mails you send so if your email delivery provider account is compromised, every user in the system is as well. (This is the same for "Forgot password".) Reddit was once compromised using this method.

Ideally you should set up your email provider to not log these mails. And be sure to turn on non-SMS 2-factor authentication if your provider supports it.

Alternatives

  • OTP JWT -- Passwordless JSON Web Tokens

License

MIT

More Repositories

1

wezterm-icon

An alternative MacOS application icon for
Makefile
69
star
2

seoul256-iTerm

🌳 A port of the vim theme
61
star
3

svgnft

Making it slightly easier to create fully on-chain SVG-based NFTs.
Solidity
57
star
4

maximum-overbusiness

βš‘οΈπŸ’Ό MAXIMUM OVERBUSINESS is a tool for building presentations as websites using React.
JavaScript
53
star
5

YosemiteInterface-qsplugin

🀑 A Quicksilver Interface
Objective-C
52
star
6

dotfiles

πŸ™ˆ oh no what have I done
Ruby
37
star
7

joof

πŸŒŽπŸ’¨ Add custom JavaScript or CSS to any webpage
JavaScript
26
star
8

cranes

Cranes (for special wallets): A fully on-chain NFT project.
JavaScript
24
star
9

LeaderKey.app

Faster than your launcher
Swift
20
star
10

sourdough-toast

Plain JS toast notifications
CSS
14
star
11

dingus

🎀 dingus listens to and emits presentation related keyboard events
JavaScript
12
star
12

YosemiteDarkInterface-qsplugin

Yosemite style Quicksilver plugin (Dark version)
Objective-C
12
star
13

NeverEndingPagingUIScrollView

Never Ending Paging UIScrollView example
Objective-C
12
star
14

my_way

A Sinatra starting point for your new app. Using rack/test and bundler.
Ruby
11
star
15

classnames

A Ruby port of JavaScript's classnames
Ruby
10
star
16

rails-creds

πŸ”§ Creds adds a shortcut and (more) conventions around Rails' Credentials
Ruby
10
star
17

class_list

Utility to create HTML classList strings from any argument. With a shorthand.
Ruby
8
star
18

5min

It's. An. Egg timer. Of sorts...
JavaScript
8
star
19

Dispatcher

Facebook Flux' Dispatcher rewritten in Swift 2
Swift
8
star
20

render_with_view

☝️ Be explicit about the things you send from your Rails controller to the view.
Ruby
7
star
21

Putter.app

Send magnet:-urls to you Put.io account
Objective-C
5
star
22

dldr.brnbw.com

A tool for downloading programs from DR NU
JavaScript
4
star
23

WhiteBezelInterface-qsplugin

A Quicksilver interface plugin
Objective-C
4
star
24

Swalt

A minimal Flux implementation written in Swift 2
Swift
4
star
25

mikkelmalmberg-next

TypeScript
4
star
26

rk.brnbw.com

Single-serving app that shows your current Rejsekort balance.
Ruby
4
star
27

dokku-opbeat

An Opbeat plugin for Dokku
Shell
4
star
28

mikker.github.com

A place for releasing stuff and explaining Gists.
SCSS
3
star
29

rf15-schedule

The music schedule for Roskilde Festival '15 in developer friendly formats
HTML
3
star
30

vim-dimcil

Color scheme based on Pencil but toned down a bit.
Vim Script
3
star
31

vim-colors-pap

A modest light/dark colorscheme for vim
Vim Script
3
star
32

Sjuft

Flux in Swift 2
Swift
3
star
33

Moves.app

Moves makes it easier than ever to position your windows juuust right
Objective-C
3
star
34

DR-Player.app

Watch DR's TV channels live on your Mac
Ruby
2
star
35

btn

A base button class for use with Tachyons
CSS
2
star
36

vim-togglebg

Vim Script
2
star
37

AfterGlowLight-iTerm

A light theme for iTerm2
2
star
38

dokku-buildpack-vendor

A BUILDPACK_VENDOR_URL for your Dokku hosted Ruby project
JavaScript
2
star
39

lazy_filler

Ruby
2
star
40

dnd

A simple command line tool to control macOS Do Not Disturb
Swift
1
star
41

computers

JavaScript
1
star
42

vim-rerunner

Make <cr> do whatever you're doing the most
Vim Script
1
star
43

blank

auto cmd+l on new Safari tabs without a toolbar I should just stop talking
Ruby
1
star
44

thecalmsideproject

HTML
1
star
45

Radio24syvRecorder

Record the public web stream of the Danish Radio Channel Radio24syv.
Ruby
1
star
46

itunes-search

CORS enabled proxy for the iTunes Search API
JavaScript
1
star
47

proxy

A general purpose http(s) proxy written in node.
JavaScript
1
star
48

seasonal-pfp

TypeScript
1
star
49

tachyons-negative-margin

DEPRECATED: Now in core Tachyons β†’
CSS
1
star
50

2sek.dk

The simplest dictionary
HTML
1
star
51

Sprinkles.app

Customize any website with your own JS and CSS
JavaScript
1
star