Ruby on Rails Line Login API only mode


전반적인 설명과 Google Login방법은 Ruby on Rails Google Login API only mode에서 이미 했으니 그것을 참고하길 바란다.

이 글에서는 관련 코드만을 다루겠다.

1. LINE developers에 App 등록

https://developers.line.biz/en/Provider를 생성하고 ChannelLINE Login으로 추가한다. Callback URLhttp://localhost:3000/auth/line/callback을 추가한다. Basic settings 탭으로 가서 Channel IDChannel secret를 확인한다. 아래에서 필요한 정보이다.

2. Gem Install

Gemfile에 아래 줄을 추가한다.

gem 'omniauth-line', git: "https://github.com/DevStarSJ/omniauth-line"

omniauth-line은 현재 관리되지 않는 gem으로 여겨진다. 그래서 omniauth-oauth2 최신버전을 사용하는 omniauth-google-oauth2와는 같이 사용이 안된다. 그래서 필자가 수정한 DevStarSJ/omniauth-line를 사용할 예정이다. 원래 gem에서 어디가 수정되었는지는 이 글 마지막에 설명하겠다.

Bundle을 실행하여 Install을 한다.

bundle install

2. Model 생성

필자의 경우 User Model과 SocialAuth Model을 분리하였다. 왜냐면 1명의 User에게 여러 Social Login을 가능하게 하기 위해서이다.

2.1 User Model 생성

rails g model User 를 실행하여 Migration 파일을 수정한다.

class CreateUsers < ActiveRecord::Migration[6.0]
  def change
    create_table :users do |t|
      t.string :email, null: false
      t.string :password_digest

      t.timestamps
    end
  end
end

passwordnullable로 설정하였다. 왜냐면 password가 없는 경우 Social Login만을 허용하기 위해서다. 하지만 Model에서 Null Check를 하므로 validation을 하지 않는 것으로 선언해야 한다.

  • user.rb
    class User < ApplicationRecord
    include ActiveModel::SecurePassword
    has_secure_password validations: false
    end
    

rails db:migrate 를 실행하여 Model을 생성한다.

2.2 SocialAuth Model 생성

rails g model SocialAuth를 실행하여 Migration 파일을 생성 후

class CreateSocialAuths < ActiveRecord::Migration[6.0]
  def change
    create_table :social_auths do |t|
      t.string :provider, null: false
      t.string :uid, null: false
      t.string :first_name
      t.string :last_name
      t.string :email
      t.string :photo
      t.references :user, null: false, foreign_key: false

      t.timestamps
    end
  end
end

위 내용과 같이 수정한다.

  • social_auth.rb
    class SocialAuth < ApplicationRecord
    belongs_to :user
    end
    

Model 파일을 수정한 뒤 rails db:migrate 를 실행하여 Model을 생성한다.

3. Initialize 선언

config/initializers/omniauth.rb 파일을 생성하여 아래와 같이 입력한다.

Rails.application.config.middleware.use OmniAuth::Builder do
  provider :line, ENV['LINE_CHANNEL_ID'], ENV['LINE_CHANNEL_SECRET']
end

기본 redirect_pathhttp://localhost:3000/line/oauth이다. 다른 Social Login과의 URL 통일성을 위해서 수정하였다.

.env 파일에 위에서 확인한 Channel IDChannel secret를 각각 LINE_CHANNEL_IDLINE_CHANNEL_SECRET로 등록한다. 참고로 .env파일을 적용하려면 dotenv-rails를 설치해야 한다.

4. Route 선언

routes.rb파일에 아래 내용을 추가한다.

  get 'auth/line/callback', to: 'auth#line'

5. Controller, Service 선언

auth_controller.rb 파일을 생성하여 아래와 같이 입력한다.

class AuthController < ApplicationController
  def line
    user = SocialAuthService.line(omniauth_params)
    render json: user
  end

  private
  def omniauth_params
    request.env['omniauth.auth'].except('extra').to_h
  end
end

참고로 인증받은 구글에서의 profile정보는 request.env['omniauth.auth']에서 확인이 가능하다. 예제에서는 User Model을 그대로 response해주는 것으로 해 놓았다. 실제 사용할때는 생성되거나 찾은 사용자 정보에서 JWT Token등을 생성해서 전달해 준다던지, request.env['omniauth.auth']안에 있는 token을 그대로 사용하는 방법이 있겠다.

social_auth_service.rb 파일을 생성하여 아래와 같이 입력한다.

class SocialAuthService
  def self.naver(params)
    apply(params, 'line')
  end

  def self.apply(params, provider)
    return nil unless params["provider"].present? && params["uid"].present? && params["info"].present?
    return nil unless params["provider"].include?(provider)

    social_auth = initialize(params, provider)
    social_auth.user
  end

  def self.initialize(params, provider)
    SocialAuth.where(provider: provider, uid: params["uid"]).first_or_create do |auth|
      auth.provider = provider
      auth.uid = params["uid"]
      auth.email = params["info"]["email"]
      auth.first_name = params["info"]["first_name"] ||  params["info"]["name"]
      auth.last_name = params["info"]["last_name"]
      auth.photo = params["info"]["image"]
  
      link_user(auth)
    end
  end

  def self.link_user(auth)
    return if auth.user_id.present?

    user = User.find_by(email: auth.email)
    user = User.create(email: auth.email) if user.blank?
    auth.user = user
  end
end

.first_or_create는 앞에 조건에 만족하는 첫번째 instance를 반환하거나 없다면 block에 선언한대로 새로 생성을 한다. 여기서 찾거나 새로 생성한 SocialAuth에서 전달받은 email정보를 가지고 User를 검색한 후, 있으면 그 User와 연결을 하고 없다면 User를 새로 생성한다. 이 부분 역시 .first_or_create를 사용해도 된다.

6. 실행 및 확인

이제 서버를 실행해서 확인해보자.

rails s

로 서버를 실행한 다음 http://localhost:3000/auth/line 를 Browser에서 실행하면 서버를 실행한 로그에 각 Model이 생성되는 SQL문을 볼 수 있으며, 화면에도 새로 생성된 사용자 정보를 볼 수 있다. 참고로 request.env['omniauth.auth'] 정보를 확인해보고 싶다면 AuthController.line의 내용을 render json: omniauth_params로 수정을 하던지, 아니면 해당 method 안에서 binding.pry를 걸어서 확인해보면 된다.

아래의 모양으로 되어 있다.

{
  "provider"=>"line",
  "uid"=>"U1234567890",
  "info"=>{
    "name"=>"Seokjoon.Yun",
    "image"=>"https://profile.line-scdn.net/0m01acce277251aad8f5dbf2fed2b6b54579f766273d24",
    "description"=>nil,
    "email"=>"seokjoon.yun@gmail.com"
  },
  "credentials"=>
  {
    "token"=>"xcxcxcxcxcxcxcxcxcx",
   "refresh_token"=>"xcxcxcxcxcxcxcxcxcxcxcxc",
   "expires_at"=>1609293083,
   "expires"=>true
  }
}

7. 수정한 omniauth-line 정보

7.1 omniauth-oauth2 버전 Upgrade

원본 gem인 omniauth-lineomniauth-oauth2 1.3에서 정상동작하게 되어 있다. 하지만 다른 Social Login들이 대부분 1.6이상을 사용하도록 되어 있는데, 그 둘이 호환이 되지 않아서 같이 사용 할 수가 없다. 그래서 그 부분을 수정하였다.

omniauth-line.gemspec 파일에서 1.7.0 이상으로 설정하였다.

s.add_dependency 'omniauth-oauth2', '~> 1.7.0'

이렇게만 수정하면 redirect_uri_mismatch 오류가 계속 발생하는데, 그걸 해결하기 위해서 lib/omniauth/strategies/line.rb에 다음 method를 추가한다.

def callback_url
  if @authorization_code_from_signed_request_in_cookie
    ''
  else
    options[:callback_url] || (full_host + script_name + callback_path)
  end
end

그러면 이제 정상동작한다.

7.2 email 정보 추가

https://developers.line.biz/en/reference/line-login 여기에서 확인해보면 https://api.line.me/v2/profile 에서는 email 정보를 주지 않는다. 분명 LINE인증을 하기 위해서 email을 입력하는 곳이 있는데도 불구하고 주지 않는 것이 좀 이상하다. 이 문서를 아래까지 쭉 읽다보면 https://developers.line.biz/en/reference/line-login/#revoke-access-tokenhttps://developers.line.biz/en/reference/line-login/#verify-id-token 에서 email 정보를 주는 것을 확인 할 수 있다. 단, email정보를 받을려면 token을 생성할 때 scopeemail을 미리 추가해야 한다고 적혀있다. 사실 scope에 email을 적으면 생성해주는 token에 이미 email 정보가 포함되어 있으며, 그냥 token을 https://jwt.io 에서 decode해보면 email 정보를 볼 수 있다. 하지만 LINE에서 제공해주는 POST https://api.line.me/oauth2/v2.1/verify 를 활용하여 구현해 보았다.

먼저 default scope를 수정하였다.lib/omniauth/strategies/line.rb 8번째줄에 email을 추가하였다.

option :scope, 'profile openid email'

그런 다음 raw_infoid_token정보를 instance variable로 저장하는 코드를 추가하였다.

def raw_info
  @id_token = access_token.params["id_token"]
  @raw_info ||= JSON.load(access_token.get('v2/profile').body)
rescue ::Errno::ETIMEDOUT
  raise ::Timeout::Error
end

email 정보를 가져오는 method를 추가하였다.

def email
  uri = URI.parse("https://api.line.me/oauth2/v2.1/verify")
  request = Net::HTTP::Post.new(uri)
  request.set_form_data(
    "client_id" => client.id,
    "id_token" => @id_token
  )

  req_options = {
    use_ssl: uri.scheme == "https",
  }

  response = Net::HTTP.start(uri.hostname, uri.port, req_options) do |http|
    http.request(request)
  end

  JSON.parse(response.body)["email"]
end

마지막으로 info에 email정보를 추가하였다.

info do
  {
    name:        raw_info['displayName'],
    image:       raw_info['pictureUrl'],
    description: raw_info['statusMessage'],
    email: email
  }
end

이렇게 수정해주면 이제 email이 request.env['omniauth.auth']에 포함되어진다.

마치며…

위에서 소개한 방법을 적용한 예제코드는 아래 Link에서 확인이 가능하다.

https://github.com/DevStarSJ/backend_boilerplates/tree/master/rails
https://github.com/DevStarSJ/omniauth-line

이로서 LINE Login에 대한 안내는 마치겠다.

다른 Social Login의 방법은 아래에서 확인이 가능하다.


이 글이 도움이 되셨다면 공감 및 광고 클릭을 부탁드립니다 :)