A pure Rails authentication solution.
- Pure Rails implementation, uses has_secure_password, generates_token_for, find_by_token_for and authenticate_by.
- ActiveAuthentication authenticates users and only users. If you need to authenticate other models you should be asking yourself if you shouldn't handle authorization differently.
- Turn on/off the features you need by using concerns.
- Authenticatable: provides the standard email/password authentication. It's the only concern that can't be turned off.
- Confirmable: allows users to confirm their email addresses.
- Lockable: locks users after a number of failed sign in attempts.
- MagicLinkable: allows users to sign in with a magic link.
- Omniauthable: allows users to sign up and sign in using a third party service through Omniauth. Turned off by default.
- Recoverable: allows users to reset their password.
- Registerable: allows users to sign up and edit their profile.
- Timeoutable: expires sessions after a period of inactivity. Turned off by default.
- Trackable: tracks users sign in count, timestamps and ip addresses.
Planned concerns:
- Invitable: to allow users to invite other users.
Add this line to your application's Gemfile:
gem "active_authentication"
And then execute:
$ bundle
Or install it yourself as:
$ gem install active_authentication
After installing the gem, you need to generate the User
model. To generate it, run:
$ rails generate active_authentication:install
This command will generate the User
model, add the active_authentication
route, and generate an initializer (config/initializers/active_authentication.rb
) where you can configure the concerns. By default, this command enables all concerns. If you want to use a subset of the concerns, you can specify them:
$ rails generate active_authentication:install confirmable
In this example, only the confirmable concern will be enabled (along with authenticatable, which can't be turned off).
You will need to set up the default url options in your config/environments/development.rb
:
config.action_mailer.default_url_options = {host: "localhost", port: 3000}
And the root
path in config/routes.rb
.
Finally, run rails db:migrate
.
If you look at the User
model (in app/models/user.rb
), you will notice there's only a sentence:
class User < ApplicationRecord
authenticates_with :confirmable, :lockable, :recoverable, :registerable, :timeoutable, :trackable
end
Notice that :authenticatable
is not in the list. This is because you cannot turn it off.
By default, all concerns are turned on except omniauthable. But you can turn it on by adding it to the list, and similarly, you can turn any concern off by just removing them from the list. If you plan to not use any concerns, you can replace authenticates_with
with authenticates
.
ActiveAuthentication comes with filters and helpers you can use in your controllers and views.
To protect actions from being accessed by unauthenticated users, use the authenticate_user!
filter:
before_action :authenticate_user!
Then, to verify if there's an authenticated user, you can use the user_signed_in?
helper.
Similarly, you can use current_user
to access the current authenticated user.
If you want to close your application entirely, you can add the before action to your application controller, in conjunction with active_authentication_controller?
, like this:
before_action :authenticate_user!, unless: :active_authentication_controller?
ActiveAuthentication's implementation of OmniAuth allows you to sign in and/or sign up with your third party accounts or sign up with ActiveAuthentication and later connect your third party accounts to ActiveAuthentication's User. To accomplish this, ActiveAuthentication relies on an Authentication
model which can be created with the active_authentication:omniauthable
generator.
To set up the omniauthable concern you must configure your OmniAuth providers as you would do with plain OmniAuth. There's no OmniAuth config in ActiveAuthentication. For example, in config/initializers/omniauth.rb
you would set the middleware:
Rails.application.config.middleware.use OmniAuth::Builder do
provider :facebook, ENV["FACEBOOK_APP_ID"], ENV["FACEBOOK_APP_SECRET"]
provider :google_oauth2, ENV["GOOGLE_CLIENT_ID"], ENV["GOOGLE_CLIENT_SECRET"]
# ... and any other omniauth strategies
end
And then you need to run the omniauthable generator to generate the Authentication
model:
$ rails g active_authentication:omniauthable
The User model has many Authentication models associated, to allow you to connect your user with multiple third party services if required.
By adding the :omniauthable
concern to your User
model, the following routes will be added to your app:
/auth/:provider
to redirect your users to the provider consent screen/auth/:provider/callback
to actually sign in/sign up with the given providers
The sign in and sign up views will show a link to sign in or sign up with each provider you configured if and only if you set the ActiveAuthentication.omniauth_providers
setting in your ActiveAuthentication initializer.
When you run the active_authentication:install
generator, an initializer will be copied to your app at config/initializers/active_authentication.rb
. There's a section per concern where you can configure certain aspects of their behavior.
If you add extra fields to your User model, you will likely want to allow users to fill in those fields upon registration or when editing their profile. By default, only email, password and password confirmation are allowed. To change this behavior, just add these lines to your config/initializers/active_authentication.rb
file:
ActiveAuthentication.configure do |config|
config.profile_params = ->(controller) {
controller.params.require(:user).permit(:first_name, :email, :last_name, :password, :password_confirmation) # first_name and last_name were added in this example
}
config.registration_params = config.profile_params
end
We believe that the configuration of a gem should be placed in just one place. For this gem, it's the initializer. The profile_params
and registration_params
take a lambda and that lambda receives a controller. Why a lambda? and why does it take a controller? We could have allowed the params to be just an array of symbols instead of the whole params.require.permit
call, but in edge cases you might want to post-process the required params, or call tap, or whatever. And to be able to call params.require.permit
, you need to run this lambda in the context of the registrations controller. That's why the lambda receives the controller.
The default views are good enough to get you started, but you'll want to customize them sooner than later. To copy the default views into your app, run the following command:
$ rails generate active_authentication:views
If you're not using all the concerns, you might want to copy only the views you need. To do that, you can use the --views
(-v
) option:
$ rails generate active_authentication:views -v sessions
By default, ActiveAuthentication stores the provider
, uid
and auth_data
in the Authentication
model. There are some cases where you want to store, for example, the first name and last name in the User
model to avoid digging into the auth_data
hash each time. Or if you have multiple authentications, you might want to pull first and last name on registration and later allow the user to change them. To pull that data from an Authentication object at sign up, you don't really need to change the controller, instead you can add a callback to your Authentication model, like this:
class Authentication < ApplicationRecord
before_validation :update_user_attributes, if: ->(auth) { auth.auth_data.present? && auth.user.present? }
private
def update_user_attributes
first_name, last_name = auth_data.dig("info", "first_name"), auth_data.dig("info", "last_name")
user.update first_name: first_name, last_name: last_name
end
end
Note: this example assumes first_name:string
and last_name:string
have been added to the User model and are required. Optional first_name and last_name can be handled similarly.
You can open an issue or a PR in GitHub.
The gem is available as open source under the terms of the MIT License.