Аутентификация в rails-приложениях через facebook, vkontakte
В данной статье будет рассказано, как сделать простейшую аутентификацию в rails-приложении через социальные сети vkontakte и facebook, помогают в этом гемы omniauth, omniauth-facebook, omniauth-vkontakte. Материал рассчитан на новичка. Хоть это и будет учебное приложение, но для придания законченности используем bootstrap с помощью гема twitter-bootstrap-rails.
Каркас
Создаём новое приложение (bundle exec перед командами буду опускать):
rails new authproviders
Пишем необходимые гемы в Gemfile
source 'https://rubygems.org'
gem 'rails', '3.2.3'
gem 'sqlite3', :group => :development
gem 'pg', :group => :production
group :assets do
gem 'sass-rails', '~> 3.2.3'
gem 'coffee-rails', '~> 3.2.1'
gem 'uglifier', '>= 1.0.3'
end
gem 'jquery-rails'
gem 'haml-rails'
gem 'twitter-bootstrap-rails'
gem 'devise'
gem 'omniauth'
gem 'omniauth-facebook'
gem 'omniauth-vkontakte'
Выполняем установку:
bundle install --without production
Сделаем приложение чуть посимпотичнее, применим bootstrap:
rails g bootstrap:layout application fixed
rails g bootstrap:layout application fixed
Не забываем удалить index.html из директории public,
если остался файл application.html.erb, то тоже удаляем.
Создаём модель пользователя, где url — адрес страницы с профилем:
rails g scaffold User username:string nickname:string provider:string url:string
Настраиваем devise:
rails generate devise:install
rails generate devise User
rake db:migrate
Добавляем в модель User модуль omniauthable:
class User < ActiveRecord::Base
# Include default devise modules. Others available are:
# :token_authenticatable, :encryptable, :confirmable, :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable, :omniauthable
# Setup accessible (or protected) attributes for your model
attr_accessible :email, :password, :password_confirmation, :remember_me
attr_accessible :nickname, :provider, :url, :username
end
Создаём контроллер обратных вызовов с экшенами для каждого провайдера
rails g controller Users::OmniauthCallbacks facebook vkontakte
Наследуем его от Devise::OmniauthCallbacksController
class Users::OmniauthCallbacksControllerController < Devise::OmniauthCallbacksController
def facebook
end
def vkontakte
end
end
В routes.rb прописываем маршрутизацию:
Authproviders::Application.routes.draw do
devise_for :users, :controllers => { :omniauth_callbacks => "users/omniauth_callbacks" }
resources :users, :only => [:index, :destroy]
root :to => 'users#index'
end
Добавляем в шаблон application.html.haml ссылки на соц сети(user_omniauth_authorize_path(:facebook) и user_omniauth_authorize_path(:vkontakte)), полный код шаблона:
!!! 5
%html(lang="en")
%head
%meta(charset="utf-8")
%meta(name="viewport" content="width=device-width, initial-scale=1.0")
%title= content_for?(:title) ? yield(:title) : "Authproviders"
= csrf_meta_tags
/ Le HTML5 shim, for IE6-8 support of HTML elements
/[if lt IE 9]
= javascript_include_tag "http://html5shim.googlecode.com/svn/trunk/html5.js"
= stylesheet_link_tag "application", :media => "all"
%link(href="images/favicon.ico" rel="shortcut icon")
%link(href="images/apple-touch-icon.png" rel="apple-touch-icon")
%link(href="images/apple-touch-icon-72x72.png" rel="apple-touch-icon" sizes="72x72")
%link(href="images/apple-touch-icon-114x114.png" rel="apple-touch-icon" sizes="114x114")
%body
.navbar.navbar-fixed-top
.navbar-inner
.container
%a.btn.btn-navbar(data-target=".nav-collapse" data-toggle="collapse")
%span.icon-bar
%span.icon-bar
%span.icon-bar
%a.brand(href="#") Authproviders
.container.nav-collapse
%ul.nav
- if user_signed_in?
%li= link_to "#{current_user.username} (#{current_user.provider})", current_user.url
%li= link_to "Sign out", destroy_user_session_path, :method => :delete
.container
.content
.row
.span9
= yield
.span3
.well.sidebar-nav
%h3 Providers
%ul.nav.nav-list
- if !user_signed_in?
%li= link_to "Sign in with Facebook", user_omniauth_authorize_path(:facebook)
%li= link_to "Sign in with Vkontakte", user_omniauth_authorize_path(:vkontakte)
%footer
%p © Company 2012
= javascript_include_tag "application"
Поправим шаблон users/index.html.haml для вывода зарегистрированных пользователей
- model_class = User.new.class
%h1=t '.title', :default => model_class.model_name.human.pluralize
%table.table.table-striped
%thead
%tr
%th= model_class.human_attribute_name(:username)
%th= model_class.human_attribute_name(:nickname)
%th= model_class.human_attribute_name(:provider)
%th= model_class.human_attribute_name(:sign_in_count)
%th= model_class.human_attribute_name(:created_at)
%th=t '.actions', :default => t("helpers.actions")
%tbody
- @users.each do |user|
%tr
%td= link_to user.username, user.url
%td= user.nickname
%td= user.provider
%td= user.sign_in_count
%td= user.created_at
%td
= link_to t('.destroy', :default => t("helpers.links.destroy")), user_path(user), :method => :delete, :confirm => t('.confirm', :default => t("helpers.links.confirm", :default => 'Are you sure?')), :class => 'btn btn-mini btn-danger'
Убедимся что всё работает и приступаем к самому интересному:
rails s
Идём на страницу https://developers.facebook.com/apps и создаём новое приложение
(для отладки на локалхосте в site url можно написать localhost:3000)
В файл инициализации devise.rb дописываем строчку:
config.omniauth :facebook, 'APP_ID', 'APP_SECRET'
'APP_ID', 'APP_SECRET' меняем на значения, выданные при создании нового приложения.
В модель User добавим метод facebook, который будет искать пользователя по адресу его странички, если такого нет, то создавать нового (не самое лучшее решение с точки зрения безопасности, но это лишь пример):
def self.find_for_facebook_oauth access_token
if user = User.where(:url => access_token.info.urls.Facebook).first
user
else
User.create!(:provider => access_token.provider, :url => access_token.info.urls.Facebook, :username => access_token.extra.raw_info.name, :nickname => access_token.extra.raw_info.username, :email => access_token.extra.raw_info.email, :password => Devise.friendly_token[0,20])
end
end
В котроллер Users::OmniauthCallbacksController добавляем так же метод facebook:
def facebook
@user = User.find_for_facebook_oauth request.env["omniauth.auth"]
if @user.persisted?
flash[:notice] = I18n.t "devise.omniauth_callbacks.success", :kind => "Facebook"
sign_in_and_redirect @user, :event => :authentication
else
flash[:notice] = "authentication error"
redirect_to root_path
end
Vkontakte
Для контакта процедура аналогична: http://vk.com/developers.php
создаём приложение:
В файл инициализации devise.rb не забываем дописываем строчку:
config.omniauth :vkontakte, 'APP_ID', 'APP_SECRET'
Протестировать работу с контактом на локалхосте оказалось сложнее, можно использовать такую штуку: https://github.com/progrium/localtunnel
После установки через bundle, при первом запуске подгружаем ключ:
localtunnel -k ~/.ssh/id_rsa.pub 3000
Затем запускаем тунель, он выдаст нам адрес, по которому будет доступно приложение (его и забиваем в контакт), следом рельсы
localtunnel 3000
This localtunnel service is brought to you by Twilio.
Port 3000 is now publicly accessible from http://4v9p.localtunnel.com ...
rails s
Добавляем по методу vkontakte в модель и контроллер, по аналогии с facebook (замечу, что согласно политике ИБ контакта, адреса почты не отдаются, поэтому чтобы не конфликтовать с валидацией, я при создании пользователя создаю суррогатные адреса вида: домен + @vk.com):
class User < ActiveRecord::Base
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable, :omniauthable
attr_accessible :email, :password, :password_confirmation, :remember_me
attr_accessible :nickname, :provider, :url, :username
def self.find_for_facebook_oauth access_token
if user = User.where(:url => access_token.info.urls.Facebook).first
user
else
User.create!(:provider => access_token.provider, :url => access_token.info.urls.Facebook, :username => access_token.extra.raw_info.name, :nickname => access_token.extra.raw_info.username, :email => access_token.extra.raw_info.email, :password => Devise.friendly_token[0,20])
end
end
def self.find_for_vkontakte_oauth access_token
if user = User.where(:url => access_token.info.urls.Vkontakte).first
user
else
User.create!(:provider => access_token.provider, :url => access_token.info.urls.Vkontakte, :username => access_token.info.name, :nickname => access_token.extra.raw_info.domain, :email => access_token.extra.raw_info.domain+'@vk.com', :password => Devise.friendly_token[0,20])
end
end
end
Код контроллера:
class Users::OmniauthCallbacksController < ApplicationController
def facebook
@user = User.find_for_facebook_oauth request.env["omniauth.auth"]
if @user.persisted?
flash[:notice] = I18n.t "devise.omniauth_callbacks.success", :kind => "Facebook"
sign_in_and_redirect @user, :event => :authentication
else
flash[:notice] = "authentication error"
redirect_to root_path
end
end
def vkontakte
@user = User.find_for_vkontakte_oauth request.env["omniauth.auth"]
if @user.persisted?
flash[:notice] = I18n.t "devise.omniauth_callbacks.success", :kind => "Vkontakte"
sign_in_and_redirect @user, :event => :authentication
else
flash[:notice] = "authentication error"
redirect_to root_path
end
end
end
Конечно, используя DRY, можно и нужно обобщить этот код.
Заключение
Как мы убедились создать приложение на рельсах с аутентификацией через фейсбук и контакт — очень просто.
Работающее демо лежит тут: http://authproviders.herokuapp.com/
Код примера: https://github.com/mystdeim/Authproviders
Приведу некоторые полезные ссылки:
- http://railsapps.github.com/rails-examples-tutorials.html — демонстрационные приложения, использующие в основном devise и omniauth
- http://www.communityguides.eu/articles/11 -хорошее решение, для объединения нескольких аккаунтов одного человека использующего разные провайдеры (требуется, чтобы была одинаковая почта, к сожалению, с контактом трюк не пройдет)
P.S. Это моя первая статья, проба пера, можно сказать.
Автор: mystdeim