Basic user authentication function using Devise
I want to use one-click registration for SNS authentication without going through a temporary registration email.
When editing user information by default in Devise, you will be asked for a password every time, but since it is not user-friendly, I want to edit user information without entering a password
memorandum. Keep the source code for this project on GitHub.
https://github.com/zizynonno/devise_omniauth
Create a new project.
$ rails new devise_omniauth
$ cd devise_omniauth
Add the following gem to the Gemfile.
Gemfile
source 'https://rubygems.org'
(abridgement)...
# Devise
gem 'devise'
gem 'devise-i18n'
gem 'omniauth-twitter'
gem 'omniauth-facebook'
gem 'dotenv-rails'
Gemfile
gem 'devise' #User authentication
gem 'devise-i18n' #devise i18n
gem 'omniauth-twitter' #twitter authentication
gem 'omniauth-facebook' #facebook authentication
gem 'dotenv-rails' #Setting environment variables
Install gem.
$ bundle install
Added devise related files.
$ rails g devise:install
When you run this command, the terminal will tell you about devise settings in English. Let's run from 1 to 4.
** 2.1 Specify default URL **
config/environments/development.rb
Rails.application.configure do
# Settings specified here will take precedence over those in config/application.rb.
(abridgement)...
# mailer setting
config.action_mailer.default_url_options = { host: 'localhost', port: 3000 }
end
** 2.2 Specify root_url ** Specify the page to be displayed when accessing * http: // localhost: 3000 / * specified in No. 1. I haven't created any pages in this project, so I'll add them first.
Let's add a Pages controller and an index page and a show page.
$ rails g controller Pages index show
Specify the following in routes.rb.
config/routes.rb
Rails.application.routes.draw do
root 'pages#index'
get 'pages/show'
(abridgement)...
end
** 2.3 Add flash message **
When you log in, a message like "You have logged in" will appear at the top.
Inserts the specified tag just below the <body>
tag in the following file.
erb:app/views/layouts/application.html.erb
<!DOCTYPE html>
<html>
<head>
<title>DeviseRails5</title>
<%= csrf_meta_tags %>
<%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %>
<%= javascript_include_tag 'application', 'data-turbolinks-track': 'reload' %>
</head>
<body>
<%#from here%>
<p class="notice"><%= notice %></p>
<p class="alert"><%= alert %></p>
<%#So far%>
<%= yield %>
</body>
</html>
** 2.4 Generate Devise View **
$ rails g devise:views
Then, the following file will be generated.
app/views/devise/shared/_links.html.erb (Partial for link)
app/views/devise/confirmations/new.html.erb (Authentication email resend screen)
app/views/devise/passwords/edit.html.erb (Password change screen)
app/views/devise/passwords/new.html.erb (Screen to send an email when you forget your password)
app/views/devise/registrations/edit.html.erb (User information change screen)
app/views/devise/registrations/new.html.erb (User registration screen)
app/views/devise/sessions/new.html.erb (Login screen)
app/views/devise/unlocks/new.html.erb (Unlock email resend screen)
app/views/devise/mailer/confirmation_instructions.html.erb (Email account verification statement)
app/views/devise/mailer/password_change.html.erb (password change completion statement for email)
app/views/devise/mailer/reset_password_instructions.html.erb (Email password reset statement)
app/views/devise/mailer/unlock_instructions.html.erb (Unlock text for email)
$ rails g devise User
When you execute, the migration file and user file will be created.
db/migrate/20200912194315_devise_create_users.rb
class DeviseCreateUsers < ActiveRecord::Migration[5.2]
def change
create_table :users do |t|
## Database authenticatable
t.string :email, null: false, default: ""
t.string :encrypted_password, null: false, default: ""
## Recoverable
t.string :reset_password_token
t.datetime :reset_password_sent_at
## Rememberable
t.datetime :remember_created_at
# ## Trackable
# t.integer :sign_in_count, default: 0, null: false
# t.datetime :current_sign_in_at
# t.datetime :last_sign_in_at
# t.string :current_sign_in_ip
# t.string :last_sign_in_ip
# ## Confirmable
# t.string :confirmation_token
# t.datetime :confirmed_at
# t.datetime :confirmation_sent_at
# t.string :unconfirmed_email # Only if using reconfirmable
# ## Lockable
# t.integer :failed_attempts, default: 0, null: false # Only if lock strategy is :failed_attempts
# t.string :unlock_token # Only if unlock strategy is :email or :both
# t.datetime :locked_at
t.timestamps null: false
end
end
end
app/models/user.rb
class User < ApplicationRecord
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
end
I will comment out only those that use this.
db/migrate/20200912194315_devise_create_users.rb
class DeviseCreateUsers < ActiveRecord::Migration[5.2]
def change
create_table :users do |t|
## Database authenticatable
t.string :email, null: false, default: ""
t.string :encrypted_password, null: false, default: ""
## Recoverable
t.string :reset_password_token
t.datetime :reset_password_sent_at
## Rememberable
t.datetime :remember_created_at
## Trackable
t.integer :sign_in_count, default: 0, null: false
t.datetime :current_sign_in_at
t.datetime :last_sign_in_at
t.string :current_sign_in_ip
t.string :last_sign_in_ip
## Confirmable
t.string :confirmation_token
t.datetime :confirmed_at
t.datetime :confirmation_sent_at
t.string :unconfirmed_email # Only if using reconfirmable
## Lockable
t.integer :failed_attempts, default: 0, null: false # Only if lock strategy is :failed_attempts
t.string :unlock_token # Only if unlock strategy is :email or :both
t.datetime :locked_at
t.timestamps null: false
end
add_index :users, :email, unique: true
add_index :users, :reset_password_token, unique: true
add_index :users, :confirmation_token, unique: true
add_index :users, :unlock_token, unique: true
end
end
In addition to what you put in the migration file, add ʻomniauth_providers: [: twitter ,: facebook] `to perform OAuth authentication.
app/models/user.rb
class User < ApplicationRecord
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable,
:confirmable, :lockable, :timeoutable, :omniauthable, omniauth_providers: [:twitter,:facebook]
end
Then add provider
, ʻuid, ʻusername
used in omniauth-twitter, omniauth-facebook to the User table.
$ rails g migration add_columns_to_users provider uid username
The following migration file will be created.
db/migrate/20200912194427_add_columns_to_users.rb
class AddColumnsToUsers < ActiveRecord::Migration[5.2]
def change
add_column :users, :provider, :string
add_column :users, :uid, :string
add_column :users, :username, :string
end
end
Once you have done this, do the following:
$ rake db:migrate
Create an application from the following.
When the creation is completed, select "Add Platform"-> "Website" from the settings.
Enter the URL in the site URL (eg http: // localhost: 3000
).
Create an application from the following.
After the creation is completed, make the following settings from "Settings".
http: // ~ / users / auth / twitter
Copy and paste each API key and secret key to the corresponding places below.
config/initializers/devise.rb
Devise.setup do |config|
# The secret key used by Devise. Devise uses this key to generate
(abridgement)...
config.omniauth :facebook, 'Enter your App ID', 'Enter App Secret' #I will correct it soon
config.omniauth :twitter, 'Enter API key', 'Enter API secret' #Don't commit to GitHub as we'll fix it soon
end
You need to define a method with the same name as provider
.
However, since the callback processing is basically common to each provider, it is unified to the callback_from
method.
$ rails generate devise:controllers users
app/controllers/users/omniauth_callbacks_controller.rb
class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController
def facebook
callback_from :facebook
end
def twitter
callback_from :twitter
end
private
def callback_from(provider)
provider = provider.to_s
@user = User.find_for_oauth(request.env['omniauth.auth'])
if @user.persisted?
flash[:notice] = I18n.t('devise.omniauth_callbacks.success', kind: provider.capitalize)
sign_in_and_redirect @user, event: :authentication
else
session["devise.#{provider}_data"] = request.env['omniauth.auth']
redirect_to new_user_registration_url
end
end
end
Set the routing for OAuth callback as follows.
config/routes.rb
Rails.application.routes.draw do
devise_for :users, controllers: { omniauth_callbacks: 'users/omniauth_callbacks' }
# ...
end
We have obtained each API key and secret key for Twitter authentication and Facebook authentication. This information is extremely confidential and should never be leaked to the outside. Since there is a risk of misuse, it is necessary to take measures to prevent the API key and secret key from being accidentally released to the public in the remote repository of GitHub or the production environment.
Since we have bundle install`` gem'dotenv-rails'
to set environment variables at the beginning, we will learn how to use the API key and secret key using dotenv-rails
.
** 5.1 Installation of .env file **
Next, place the .env file that defines the environment variables in the ** project root of the app **.
The .env file is not automatically generated even if you bundle install`` dotenv-rails
, so you need to manually create the file using the touch command as shown below.
$ touch .env
** 5.2 Put the following in the .env file **
.env
TWITTER_API_KEY="Obtained Twitter API key"
TWITTER_SECRET_KEY="Obtained Twitter secret key"
FACEBOOK_API_ID="Obtained Facebook API key"
FACEBOOK_API_SECRET="Obtained Facebook secret key"
** 5.3 Use environment variables ** Assign environment variables to the hard-coded file using a description method such as ʻENV ['SECRET_KEY']`.
config/initializers/devise.rb
Devise.setup do |config|
# The secret key used by Devise. Devise uses this key to generate
(abridgement)...
config.omniauth :twitter, ENV['TWITTER_API_KEY'], ENV['TWITTER_API_SECRET_KEY']
config.omniauth :facebook, ENV['FACEBOOK_API_ID'], ENV['FACEBOOK_API_SECRET']
end
Create ** self.from_omniauth ** and ** self.new_with_session ** in the User model. In self.from_omniauth, if you search by uid and provider, it will be searched, and if not, a record will be created. For self.new_with_session, if you do not add this method, even if you register on the sign-up page after Twitter authentication, the uid and provider etc. that you got as authentication information will not be registered. Since they are not registered, even if you authenticate with Twitter, you will be skipped to the sign-up page every time as an unregistered user.
app/models/user.rb
class User < ApplicationRecord
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable,
:confirmable, :lockable, :timeoutable, :omniauthable, omniauth_providers: [:twitter]
def self.from_omniauth(auth)
find_or_create_by(provider: auth["provider"], uid: auth["uid"]) do |user|
user.provider = auth["provider"]
user.uid = auth["uid"]
user.username = auth["info"]["nickname"]
end
end
def self.new_with_session(params, session)
if session["devise.user_attributes"]
new(session["devise.user_attributes"]) do |user|
user.attributes = params
end
else
super
end
end
end
The combination of ʻuidand
provider` is unique, which gets the user.
If it does not exist in the record, create it.
app/models/user.rb
class User < ActiveRecord::Base
# ...
def self.find_for_oauth(auth)
user = User.where(uid: auth.uid, provider: auth.provider).first
unless user
user = User.create(
uid: auth.uid,
provider: auth.provider,
email: User.dummy_email(auth),
password: Devise.friendly_token[0, 20]
)
end
user.skip_confirmation! #Immediate registration without going through a temporary registration email
user
end
private
def self.dummy_email(auth)
"#{auth.uid}-#{auth.provider}@example.com"
end
end
If you also implement email address authentication, you need to save your email address when authenticating with OAuth.
Here, taking advantage of the unique combination of ʻuidand
provider, it is generated as
self.dummy_email`.
Edit the following file so that the controller you created earlier is called as the controller for the callback. If you do not write this, the controller on the devise side will be called.
config/routes.rb
Rails.application.routes.draw do
devise_for :users, controllers: { omniauth_callbacks: 'users/omniauth_callbacks' }
root 'pages#index'
get 'pages/show'
(abridgement)...
end
You can now authenticate with Twitter. When you authenticate with Twitter for the first time, you will be taken to the sign-up page, where you can enter your email address and password to register and your user information will be registered. Since I have included the confirmable function this time, I get a message saying that I sent a confirmation message after registering, and I can not log in as it is. If you do not include this function, you will be logged in immediately after registering.
app/model/user.rb
class User < ActiveRecord::Base
# ...
def self.find_for_oauth(auth)
user = User.where(uid: auth.uid, provider: auth.provider).first
unless user
user = User.create(
uid: auth.uid,
provider: auth.provider,
email: User.dummy_email(auth),
password: Devise.friendly_token[0, 20]
)
end
######Add this!######
user.skip_confirmation!
#######################
user
end
private
def self.dummy_email(auth)
"#{auth.uid}-#{auth.provider}@example.com"
end
end
routes.rb
devise_for :users, controllers: { }
Rails.application.routes.draw do
devise_for :users, controllers: { omniauth_callbacks: 'users/omniauth_callbacks',
registrations: 'users/registrations' }
root 'pages#index'
get 'pages/show'
(abridgement)...
end
registrations_controller.rb
class RegistrationsController < Devise::RegistrationsController
protected
#Append
def update_resource(resource, params)
resource.update_without_password(params)
end
end
erb:views/devise/registrations/edit.html.erb
<div class="field">
<%= f.label :current_password %> <i>(we need your current password to confirm your changes)</i><br />
<%= f.password_field :current_password, autocomplete: "current-password" %>
</div>
Let's delete this form.
Now you can enter your user registration information without having to enter your password It is now possible to edit!
** In addition to the basic user authentication function, build a mechanism that allows one-click registration with SNS authentication. ** ** [* Rails *] How to use devise (rails5 version) Procedure to implement user authentication with Devise + OmniAuth in Rails
** I want to use environment variables to hide the API key and push it remotely (I don't want to hard-code App Secret etc.) ** [Rails] Let's manage environment variables by understanding how to install and use dotenv-rails! Environment variable settings
** I want to bring the email registered on twitter or facebook instead of the dummy (it takes about 3 days) ** Omniauth-Get email information on twitter Try using twitter oauth (also get email) Try using oauth on facebook (also get email) Problems and countermeasures for not being able to get an email address with Facebook authentication after July 9, 2015
** I want to be able to register immediately without using a temporary registration email for authentication using external sites such as Twitter, Google, and Github ** Records are not stored even if omniauth twitter authentication is completed in Devise Skip email authentication and email transmission when logging in to devise's Twitter [Rails5] Implementation that allows you to register and log in without using an email address with SNS authentication
** I want to translate devise into Japanese with i18n ** Japanese localization with i18n
** I'm tired of being asked for a password every time I edit user information ** [Devise] Edit user information without entering password
** Register as a user using only your email address. ** ** How to register as a user with devise only with an email address and set a password later How To: Email only sign up
** How to sign in with a different email address when signing in ** How To: Allow users to sign in with something other than their email address
** A method that does not require confirmation when updating an email address (I want to skip it) ** Do you need to check for email address updates with devise? Skip email address verification in Devise
** Other ** [Rails] What to do if "Unauthorized 403 Forbidden" appears in devise's Twitter authentication devise documentation
Recommended Posts