OAuth2 và login qua Twitter

oauth2_logo.png

I. OAuth là gì?

1. Giới thiệu

Chắc hẳn nhiều bạn đã thấy những biểu tượng này khi đăng ký tài khoản tại một website nào đó Oauth.png

Oauth2.png

Và giờ ta không cần tạo một tài khoản mới nữa, chỉ cần có Account tại các ông lớn kia là đủ. Đó chính là tiện ích của OAuth đem lại (honho)

Vậy OAuth là gì?

OAuth (Open Authorization) là một phương thức chứng thực cho phép các ứng dụng của bên thứ ba có quyền truy cập tới một HTTP Service, thay mặt chủ sở hữu sử dụng tài nguyên của mình (tất nhiên cần phải có sự cho phép của người đó). Yêu cầu truy cập có thế đến từ website, mobile app,...

Ưu điểm:

  • Tiện lợi: Không cần phải tạo tài khoản để đăng nhập mỗi khi sử dụng một web service hay application khác, đỡ phải nhớ nhiều (len)
  • An toàn: Với OAuth 1.0 còn khá nhiều lỗi bảo mật nghiêm trọng, nhưng với OAuth2 thì đã khắc phục gần hết, người dùng không còn lo lắng khi phải đăng ký tài khoản ở một trang web không rõ nguồn gốc xuất xứ. (yaoming)

2. Một số khái niệm cơ bản

Roles

Với OAuth2, về cơ bản sẽ chia làm 4 roles chính:

  • Resource Owner: chủ tài nguyên.
  • Resource Server: server lưu trữ tài nguyên, vd như các ông lớn Google, Facebook, Twitter,..
  • Client: Người dùng gửi request để truy cập thông qua website hoặc mobile app.
  • Authorization Server: Server cung cấp token cho client. Token này sẽ được trả về và sử dụng khi có requets từ Client gửi tới.

Token

Là chuỗi ký tự ngẫu nhiên được tạo ra bởi Authorization Server và được cấp khi Client gửi request. Token có 2 loại:

  • Access token: Token này được gửi từ Client như một parameters hoặc headers tới Resource Server, lifetime của nó được định nghĩa bởi Authorization Server.
  • Refresh token: Song hành cùng với Access token nhưng nó chỉ đơn thuần được gửi để renewing Access token khi lifetime hết đát.

3. Work flow

oauth_flow.png

Ví dụ ta có roles trong trường hợp như sau:

  • Resource Owner: Mình
  • Resource Server: Facebook server
  • Client: Website Viblo
  • Authorization Server: Facebook server

Kịch bản

  • Từ Viblo, ta muốn login thông qua Facebook.
  • Browser redirect tới trang chứng thực của Facebook (Authorization Server).
  • Nếu ta cho phép truy cập, browser sẽ redirect ta về Viblo cùng với Access Token trên URI. Ví dụ như callback: http://viblo.asia/auth/callback&access_token=MynBTzUGYX8bHTnyLpZ4
  • Access token này sẽ được client lấy và sử dụng khi truy cập tới Facebook lấy tài nguyên. Ví dụ: https://graph.facebook.com/me?access_token=MynBTzUGYX8bHTnyLpZ4
  • Facebook dựa vào Token, nhận ra là thanh niên đã đăng ký trước đó, cho phép truy cập và trả về dữ liệu.

Về cơ bản là như vậy (yaoming).

Để hiểu rõ hơn, bạn có thể truy cập tới bài viết của anh Tùng về OAuth2, nó rất chi tiết.

II. Sử dụng OAuth2 để login qua Twitter

Dưới đây ta sẽ Demo rails app login qua Twitter bằng OAuth2 (len)

Bài viết dưới đây sử dụng

  • Rails 4.1.4
  • Mysql 5.6.27
  • Ruby 2.1.4

Roles

  • Resource owner: Yourself
  • Resource server: Twitter server
  • Client: Rails app.
  • Authorization Server: Twitter

Các bước thực hiện

  • Khởi tạo rails app, config và add gem cần thiết.
  • Đăng ký API với Twitter.
  • Add key mà Twitter cung cấp vào app.
  • Điều hướng login tới đúng callback.
  • Xử lý dữ liệu trả về (tạo session, save user).

1. Khởi tạo

Tạo một rails application mới

rails new social

Thêm bootstrap cho đẹp trai (yaoming)

gem "bootstrap-sass"
# stylesheets/application.scss
@import "bootstrap-sprockets";
@import "bootstrap";
@import 'bootstrap/theme';

Chỉnh layout

<nav class="nav navbar-inverse">
  <div class="container" >
    <div class="navbar-header">
      <%= link_to "Social", root_path, class: "navbar-brand" %>
    </div>
    <div id="navbar">
      <ul class="nav navbar-nav">
        <li><%= link_to 'Twitter', '/auth/twitter' %></li>
      </ul>
    </div>
  </div>
</nav>

<div class="container">
  <% flash.each do |message_type, message| %>
    <div class="alert alert-<%= message_type %>">
      <%= message %>
    </div>
  <% end %>

  <%= yield %>

</div>

Tạo controller mới tên HomeController

rails g controller home index

Chỉnh root tới method index trong HomeController làm trang chủ

root "home#index"

Setup xong (dance2)

2. Login thông qua Twitter

Ta sử dụng thêm gem OmniAuth để kết nối với Twitter dễ dàng hơn

gem 'omniauth-twitter'

Tạo file để config.

# config/initializers/omniauth.rb
Rails.application.config.middleware.use OmniAuth::Builder do
  provider :twitter, ENV['TWITTER_KEY'], ENV['TWITTER_SECRET']
end

Ở đây ta thấy có 2 biến là ENV['TWITTER_KEY']ENV['TWITTER_SECRET'] là key của Twitter cung cấp cho website sử dụng để đăng nhập thông qua nó.

Để lấy key ta truy cập ta thực hiện những bước sau:

  • Truy cập apps.twitter.com
  • Login và chọn Create New App
  • Điền thông tin vào form đăng ký
  • Mục Callback URL sẽ = Domain + /auth/twitter/callback/ (nếu ở localhost thì thay bằng 127.0.0.1:3000)
  • Sau khi đăng ký xong, mở tab Keys and Access Tokens ở đây ta sẽ thấy mục API KeyAPI Secret, chính là tương ứng với 2 biến ENV['TWITTER_KEY']ENV['TWITTER_SECRET'] đã khai báo ở trên.

Add routes cho callback tương ứng với Callback URL đã khai báo ở trên:

get '/auth/:provider/callback', to: 'sessions#create'
delete '/logout', to: 'sessions#destroy'

Tạo controller và method sẽ xử lý request

class SessionsController < ApplicationController
  def create

  end
end

Thế là đã kết nối xong (honho).

Sau khi gửi request, thông tin được Twitter API trả về được lưu trong biến request.env['omniauth.auth'] có dạng như sau:

{
  :provider => "twitter",
  :uid => "123456",
  :info => {
    :nickname => "johnqpublic",
    :name => "John Q Public",
    :location => "Anytown, USA",
    :image => "http://si0.twimg.com/sticky/default_profile_images/default_profile_2_normal.png",
    :description => "a very normal guy.",
    :urls => {
      :Website => nil,
      :Twitter => "https://twitter.com/johnqpublic"
    }
  },
  :credentials => {
    :token => "a1b2c3d4...", # The OAuth 2.0 access token
    :secret => "abcdef1234"
  },
  :extra => {
    :access_token => "", # An OAuth::AccessToken object
    :raw_info => {
      :name => "John Q Public",
      :listed_count => 0,
      :profile_sidebar_border_color => "181A1E",
      :url => nil,
      :lang => "en",
      :statuses_count => 129,
      :profile_image_url => "http://si0.twimg.com/sticky/default_profile_images/default_profile_2_normal.png",
      :profile_background_image_url_https => "https://twimg0-a.akamaihd.net/profile_background_images/229171796/pattern_036.gif",
      :location => "Anytown, USA",
      :time_zone => "Chicago",
      :follow_request_sent => false,
      :id => 123456,
      :profile_background_tile => true,
      :profile_sidebar_fill_color => "666666",
      :followers_count => 1,
      :default_profile_image => false,
      :screen_name => "",
      :following => false,
      :utc_offset => -3600,
      :verified => false,
      :favourites_count => 0,
      :profile_background_color => "1A1B1F",
      :is_translator => false,
      :friends_count => 1,
      :notifications => false,
      :geo_enabled => true,
      :profile_background_image_url => "http://twimg0-a.akamaihd.net/profile_background_images/229171796/pattern_036.gif",
      :protected => false,
      :description => "a very normal guy.",
      :profile_link_color => "2FC2EF",
      :created_at => "Thu Jul 4 00:00:00 +0000 2013",
      :id_str => "123456",
      :profile_image_url_https => "https://si0.twimg.com/sticky/default_profile_images/default_profile_2_normal.png",
      :default_profile => false,
      :profile_use_background_image => false,
      :entities => {
        :description => {
          :urls => []
        }
      },
      :profile_text_color => "666666",
      :contributors_enabled => false
    }
  }
}

Điều ta cần làm bây giờ là dựa vào data trả về để khởi tạo Sessions & Save User.

Trong một đống data trả về thế kia, ta chỉ lựa chọn một số thông tin cho Demo này.

  • provider: Twitter
  • uid: Id của User
  • name: Tên của User
  • location
  • image: Avatar URL
  • url: Profile url

Generate migration

rails g model User provider:string uid:string name:string location:string image_url:string url:string

Bên phía model User ta tạo một method để lưu trữ lại thông tin User vào DB khi đăng nhập lần đầu tiên

  def self.from_omniauth auth_hash
    user = find_or_create_by(uid: auth_hash['uid'], provider: auth_hash['provider'])
    user.name = auth_hash['info']['name']
    user.location = auth_hash['info']['location']
    user.image_url = auth_hash['info']['image']
    user.url = auth_hash['info']['urls']['Twitter']
    user.save!
    user
  end

Từ agrument auth_hash là thông tin User được trả về sau khi login qua Twitter, ta kiểm tra trong DB User này đã tồn tại chưa, nếu chưa thì tạo mới user.

Xử lý tạo Sessions

class SessionsController < ApplicationController
  def create
    begin
      @user = User.from_omniauth(request.env['omniauth.auth'])
      session[:user_id] = @user.id
      flash[:success] = "Welcome, #{@user.name}!"
    rescue
      flash[:warning] = "Got errors when authenticate"
    end
    redirect_to root_path
  end
end

Tạo biến current_user để xác thực User đã login hay chưa

# application_controller.rb
private
def current_user
  @current_user ||= User.find_by(id: session[:user_id])
end

helper_method :current_user

Xử lý hiển thị trên View khi User đã login

<% if current_user %>
  <ul class="nav navbar-nav pull-right">
    <li><%= image_tag current_user.image_url, alt: current_user.name %></li>
    <li><%= link_to 'Log Out', logout_path, method: :delete %></li>
  </ul>
<% else %>
  <ul class="nav navbar-nav">
    <li><%= link_to 'Twitter', '/auth/twitter' %></li>
  </ul>
<% end %>

Destroy sessions khi logout

def destroy
  if current_user
    session.delete(:user_id)
    flash[:success] = 'See you!'
  end
  redirect_to root_path
end

Kết quả màn hình trang chủ

index_1.png

Sau khi click vào nút login, trang sẽ được redirect tới Twitter để chứng thực quyền đăng nhập twitter_redirect_1.png

Nhập Username và password của tài khoản trên Twitter vào, hệ thống sẽ lưu thông tin User, tạo Session và redirect về trang chủ

index_2.png

Giờ thông tin đã lấy đấy đủ, ta sẽ show ra ở trang index

<% if current_user %>
  <div class="panel panel-default">
    <div class="panel-body">
      <ul>
        <li><strong>Name:</strong> <%= current_user.name %></li>
        <li><strong>Provider:</strong> <%= current_user.provider %></li>
        <li><strong>uID:</strong> <%= current_user.uid %></li>
        <li><strong>Location:</strong> <%= current_user.location %></li>
        <li><strong>Avatar URL:</strong> <%= current_user.image_url %></li>
        <li><strong>URL:</strong> <%= current_user.url %></li>
      </ul>
    </div>
  </div>
<% end %>

Kết quả

index_3.png

Source

Github: https://github.com/NguyenTanDuc/twitter-oauth2

Nguồn tham khảo


All Rights Reserved