Two factor authentication extension for Devise
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

269 lines
8.6 KiB

# Two factor authentication for Devise
[![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/Houdini/two_factor_authentication?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
[![Build Status](https://travis-ci.org/Houdini/two_factor_authentication.svg?branch=master)](https://travis-ci.org/Houdini/two_factor_authentication)
[![Code Climate](https://codeclimate.com/github/Houdini/two_factor_authentication.svg)](https://codeclimate.com/github/Houdini/two_factor_authentication)
13 years ago
## Features
* Support for 2 types of OTP codes
1. Codes delivered directly to the user
2. TOTP (Google Authenticator) codes based on a shared secret (HMAC)
* Configurable OTP code digit length
* Configurable max login attempts
* Customizable logic to determine if a user needs two factor authentication
* Configurable period where users won't be asked for 2FA again
* Option to encrypt the TOTP secret in the database, with iv and salt
13 years ago
## Configuration
13 years ago
### Initial Setup
In a Rails environment, require the gem in your Gemfile:
13 years ago
gem 'two_factor_authentication'
13 years ago
Once that's done, run:
bundle install
Note that Ruby 2.1 or greater is required.
### Installation
13 years ago
#### Automatic initial setup
To set up the model and database migration file automatically, run the
following command:
13 years ago
bundle exec rails g two_factor_authentication MODEL
Where MODEL is your model name (e.g. User or Admin). This generator will add
`:two_factor_authenticatable` to your model's Devise options and create a
migration in `db/migrate/`, which will add the following columns to your table:
13 years ago
- `:second_factor_attempts_count`
- `:encrypted_otp_secret_key`
- `:encrypted_otp_secret_key_iv`
- `:encrypted_otp_secret_key_salt`
- `:direct_otp`
- `:direct_otp_sent_at`
- `:totp_timestamp`
#### Manual initial setup
If you prefer to set up the model and migration manually, add the
`:two_factor_authentication` option to your existing devise options, such as:
```ruby
devise :database_authenticatable, :registerable, :recoverable, :rememberable,
:trackable, :validatable, :two_factor_authenticatable
```
Then create your migration file using the Rails generator, such as:
```
rails g migration AddTwoFactorFieldsToUsers second_factor_attempts_count:integer encrypted_otp_secret_key:string:index encrypted_otp_secret_key_iv:string encrypted_otp_secret_key_salt:string direct_otp:string direct_otp_sent_at:datetime totp_timestamp:timestamp
```
13 years ago
Open your migration file (it will be in the `db/migrate` directory and will be
named something like `20151230163930_add_two_factor_fields_to_users.rb`), and
add `unique: true` to the `add_index` line so that it looks like this:
13 years ago
```ruby
add_index :users, :encrypted_otp_secret_key, unique: true
13 years ago
```
Save the file.
#### Complete the setup
Run the migration with:
bundle exec rake db:migrate
13 years ago
Add the following line to your model to fully enable two-factor auth:
13 years ago
has_one_time_password(encrypted: true)
13 years ago
Set config values in `config/initializers/devise.rb`:
13 years ago
```ruby
config.max_login_attempts = 3 # Maximum second factor attempts count.
config.allowed_otp_drift_seconds = 30 # Allowed TOTP time drift between client and server.
config.otp_length = 6 # TOTP code length
config.direct_otp_valid_for = 5.minutes # Time before direct OTP becomes invalid
config.direct_otp_length = 6 # Direct OTP code length
config.remember_otp_session_for_seconds = 30.days # Time before browser has to perform 2fA again. Default is 0.
config.otp_secret_encryption_key = ENV['OTP_SECRET_ENCRYPTION_KEY']
config.second_factor_resource_id = 'id' # Field or method name used to set value for 2fA remember cookie
8 years ago
config.delete_cookie_on_logout = false # Delete cookie when user signs out, to force 2fA again on login
```
The `otp_secret_encryption_key` must be a random key that is not stored in the
DB, and is not checked in to your repo. It is recommended to store it in an
environment variable, and you can generate it with `bundle exec rake secret`.
Override the method in your model in order to send direct OTP codes. This is
automatically called when a user logs in unless they have TOTP enabled (see
below):
13 years ago
```ruby
def send_two_factor_authentication_code(code)
# Send code via SMS, etc.
end
```
13 years ago
### Customisation and Usage
13 years ago
By default, second factor authentication is required for each user. You can
change that by overriding the following method in your model:
13 years ago
13 years ago
```ruby
def need_two_factor_authentication?(request)
request.ip != '127.0.0.1'
end
13 years ago
```
13 years ago
In the example above, two factor authentication will not be required for local
users.
This gem is compatible with [Google Authenticator](https://support.google.com/accounts/answer/1066447?hl=en).
To enable this a shared secret must be generated by invoking the following
method on your model:
```ruby
user.generate_totp_secret
```
This must then be shared via a provisioning uri:
```ruby
user.provisioning_uri # This assumes a user model with an email attribute
```
This provisioning uri can then be turned in to a QR code if desired so that
users may add the app to Google Authenticator easily. Once this is done, they
may retrieve a one-time password directly from the Google Authenticator app.
#### Overriding the view
The default view that shows the form can be overridden by adding a
file named `show.html.erb` (or `show.html.haml` if you prefer HAML)
inside `app/views/devise/two_factor_authentication/` and customizing it.
Below is an example using ERB:
```html
<h2>Hi, you received a code by email, please enter it below, thanks!</h2>
<%= form_tag([resource_name, :two_factor_authentication], :method => :put) do %>
<%= text_field_tag :code %>
<%= submit_tag "Log in!" %>
<% end %>
<%= link_to "Sign out", destroy_user_session_path, :method => :delete %>
```
#### Upgrading from version 1.X to 2.X
The following database fields are new in version 2.
- `direct_otp`
- `direct_otp_sent_at`
- `totp_timestamp`
To add them, generate a migration such as:
$ rails g migration AddTwoFactorFieldsToUsers direct_otp:string direct_otp_sent_at:datetime totp_timestamp:timestamp
The `otp_secret_key` is only required for users who use TOTP (Google Authenticator) codes,
so unless it has been shared with the user it should be set to `nil`. The
following pseudo-code is an example of how this might be done:
```ruby
User.find_each do |user| do
if !uses_authenticator_app(user)
user.otp_secret_key = nil
user.save!
end
end
```
#### Adding the TOTP encryption option to an existing app
If you've already been using this gem, and want to start encrypting the OTP
secret key in the database (recommended), you'll need to perform the following
steps:
1. Generate a migration to add the necessary columns to your model's table:
```
rails g migration AddEncryptionFieldsToUsers encrypted_otp_secret_key:string:index encrypted_otp_secret_key_iv:string encrypted_otp_secret_key_salt:string
```
Open your migration file (it will be in the `db/migrate` directory and will be
named something like `20151230163930_add_encryption_fields_to_users.rb`), and
add `unique: true` to the `add_index` line so that it looks like this:
```ruby
add_index :users, :encrypted_otp_secret_key, unique: true
```
Save the file.
2. Run the migration: `bundle exec rake db:migrate`
2. Update the gem: `bundle update two_factor_authentication`
3. Add `encrypted: true` to `has_one_time_password` in your model.
For example: `has_one_time_password(encrypted: true)`
4. Generate a migration to populate the new encryption fields:
```
rails g migration PopulateEncryptedOtpFields
```
Open the generated file, and replace its contents with the following:
```ruby
class PopulateEncryptedOtpFields < ActiveRecord::Migration
def up
User.reset_column_information
User.find_each do |user|
user.otp_secret_key = user.read_attribute('otp_secret_key')
user.save!
end
end
def down
User.reset_column_information
User.find_each do |user|
user.otp_secret_key = ROTP::Base32.random_base32
user.save!
end
end
end
7 years ago
```
5. Generate a migration to remove the `:otp_secret_key` column:
```
rails g migration RemoveOtpSecretKeyFromUsers otp_secret_key:string
```
6. Run the migrations: `bundle exec rake db:migrate`
If, for some reason, you want to switch back to the old non-encrypted version,
use these steps:
1. Remove `(encrypted: true)` from `has_one_time_password`
2. Roll back the last 3 migrations (assuming you haven't added any new ones
after them):
```
bundle exec rake db:rollback STEP=3
```
### Example App
[TwoFactorAuthenticationExample](https://github.com/Houdini/TwoFactorAuthenticationExample)