Аутентификация в rails-приложениях с помощью facebook, vkontakte

в 16:50, , рубрики: devise, omniauth, ruby on rails, метки: , ,

Аутентификация в rails-приложениях через facebook, vkontakte

Аутентификация в 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

Facebook

Идём на страницу https://developers.facebook.com/apps и создаём новое приложение
Аутентификация в rails приложениях с помощью facebook, vkontakte
(для отладки на локалхосте в 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
создаём приложение:
Аутентификация в rails приложениях с помощью facebook, vkontakte
В файл инициализации 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

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js