Author Archives: kamiyagunji

GoogleOAuth2.0を使って、超簡単にユーザー情報を取得した話

kamiyagunji|2019年12月04日
PHP

ランサーズ Advent Calendar 2019 4日目の記事となります。

みなさんこんにちは。ランサーズでエンジニアとしてインターンをしている神谷(@Gunji Kamiya)です。

今回の記事では、タイトルにもある通り、GoogleOAuthを使った実装の話を書いていきたいと思います!

みなさんは「GoogleOAuth」という言葉を聞いたことあるでしょうか?

もしかしたら、耳にしたことがあるかもしれませんが、一応解説をしておくと。。

GoogleOAuthとは

GoogleOAuthとは、Googleのプラットフォームが提供しているOAuth認証のことです。

これにより、Googleが提供しているプラットフォーム上でユーザーの合意が簡単に取れ、セキュアにユーザーの権限などを受け渡しすることができます。

今回管理画面では、よりログインのスムーズに行うために、このGoogleOAuthを使って、ログイン機能を実装することになりました。

ランサーズの歴史を簡単に

ランサーズのメインプロダクトは、2008年12月にリリースされました。

それと同じ時期に管理画面も作られたようなので、もう10年以上の歴史があることになりますね。。

ユーザーが使っているメインプロダクトは、定期的にUIの変更があったりするのですが、管理画面は社内の人間しか使わないということもあり、当時のままです。

まさにThe 管理画面!!というイメージを想像してもらって大丈夫です(笑)

とはいえ、10年以上ランサーズの管理画面を支えてきたのは事実ですし、これからもずっと使われていく大切な一つのプロダクトです。

これから責任を持って、使いやすく、みんなから好かれるような管理画面にしてあげるからね(´∀`*)

GoogleOAuthには世界中に豊富なライブラリが存在

さて、Googleログイン機能実装に取り組んでいきたいところだったのですが、ここで問題が。。

「やり方がわからない!!」という問題です。

まあこれは、初心者や経験者関係なくあるものですよね。

誰にでも初めてはあるものですし、今のご時世、インターネットに検索キーワードを入れれば簡単に答えにたどり着くことができるはずです。

検索をしてみると、さすがGoogleですね。

たくさんのドキュメントが用意されていました!

ただほとんどすべてが英語ベース!!

よく考えてみれば当たり前なことなのですが、これも良い経験なので頑張ってやってみましょう!

ライブラリをインストール

まずは、GoogleOAuthの実装に必要なライブラリをインストールしていきます。

ランサーズでは、CakePHPというフレームワークを使っているので、Composer経由でインストールします。

Google-Api-PHP-Clientのインストール

$ composer require google/apiclient:"^2.*"

GoogleSDKのインストール

$ composer require google/cloud:"^0.*"

これらのライブラリをインストールすることで、Googleサービス内の情報にアクセスするための機能を入れることができます。

しかし、これだけでは動きません!!

これとは別に、ユーザーを識別するための認証情報が必要になります。

Google Developers Consoleにログインし、対象のプロジェクトから「認証情報を作成」を押してください。

プルダウンが表示されるので、「OAuthクライアントID」を選択し、名前や制限事項などを設定してください。

その後、認証情報をJSON形式でダウンロードできるので、それもしておきましょう!!

認証情報を設置

アプリケーション側から認証情報を利用するために、特定のディレクトリにJSONファイルを設置します。

僕はこんな感じでディレクトリにJSONファイルを設置しました。

 README.md
 bin
 composer.json
 composer.lock
 composer.phar
 config
  |-- adminlte.php
  |-- app.php
  |-- bootstrap.php
  |-- routes.php
  |-- google_client_secret.json ←←← Googleの認証情報を設置
 logs
 phpunit.xml.dist
 plugins
 src
 tests
 tmp
 vendor
 webroot

JSONファイルはわかりやすいようにリネームして使用すると良いと思います。

ちなみに認証情報は次のようなデータが入っています。

{
	"web": {
		"client_id": "********************************************.apps.googleusercontent.com",
		"project_id": "**************",
		"auth_uri": "https://accounts.google.com/o/oauth2/auth",
		"token_uri": "https://oauth2.googleapis.com/token",
		"auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
		"client_secret": "************************"
	}
}

ログインアクション

ここからは実際にソースコードに記載する実装を書いていきます。

僕の場合は特定のメソッドにログインアクションを実装していきました。

それがこちらです↓


        // オブジェクトを生成
        $client = new Google_Client();

        // 認証情報が記載されているJSONファイルを読み込む(必須)
        $client->setAuthConfig(__DIR__ . '/../../config/google_client_secret.json');

        // リダイレクト先に `authGoogle`メソッドに指定(必須)
        $client->setRedirectUri('https://' . $_SERVER['HTTP_HOST'] . '/users/authGoogle');

        // アクセスのスコープを定義(必須)
        $client->addScope("https://www.googleapis.com/auth/userinfo.email");

        // ユーザーのオフライン時にアクセストークンを更新できるように指定(推奨)
        $client->setAccessType('offline');

        // アクセストークンの増分を許可(オプション)
        $client->setIncludeGrantedScopes(true);

ここでは、Google認証を行うために必要な設定をしています。

それぞれの細かい詳細はこちらを参考にしてください。
Google OAuth2.0

その後、Googleへログイン処理を行うための処理を実装していきます。

GoogleOAuthでは、リダイレクト時に正常なアクセスかどうかを判別するために、トークンを設定できます。

以下では、そういったセキュアな設定も行なっています。


        // 偽造防止のためにトークンを指定
        $state = substr(base_convert(hash('sha256', uniqid()), 16, 36), 0, 40);
        $session->write('google.stateToken', $state);
        $client->setState($state);

        // GoogleのOAuth2.0サーバーへリクエストを行うためのURLを生成する
        $auth_url = $client->createAuthUrl();
        header('Location: ' . filter_var($auth_url, FILTER_SANITIZE_URL));

Googleオブジェクトの凄いところなのですが、リクエストのためのURLなどの生成が非常に簡単にできるんですよね。

URLを格納した変数を確認してみると、きちんと設定した情報をもとに長いURLが生成されていました。

https://accounts.google.com/o/oauth2/auth?response_type=code&access_type=offline&client_id=********************************************.apps.googleusercontent.com&redirect_uri=https*******************.jp%2Fusers%2FauthGoogle&state=21awgk049068gcwkwcsokgk4okkgsowogwgckk8c&scope=https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fuserinfo.email&approval_prompt=auto&include_granted_scopes=true

これらの手順を踏むと、画面は勝手にGoogleログインのページへ遷移することになります。

コールバック先での処理

ここからはGoogleの認証画面を経て、コールバックしてきた先の処理を実装していきます。

先ほどにも書きましたが、ログインなどの処理は正常なリクエストかどうかをきちんと確認すべきなので、そういった点を考慮していきます。


        // 正常なリクエストではなかった場合の処理
        if ($_GET['state'] !== $session->read('google.stateToken')) {
            // Googleログインに失敗した場合は `/`にリダイレクトし、処理を終了させる
            $redirect_uri = 'https://' . $_SERVER['HTTP_HOST'] . '/';
            header('Location: ' . filter_var($redirect_uri, FILTER_SANITIZE_URL));
            exit;
        }
        // 認証トークンを変数に格納
        $code = $_GET['code'];
        // 認証トークンを元にアクセストークンを生成
        $client->authenticate($code);
        // 後ほどユーザー情報を取得するためにセッションに保存
        $session->write('google.access', $client->getAccessToken());
        $redirect_uri = 'https://' . $_SERVER['HTTP_HOST'] . '/users/googleLogin';
        header('Location: ' . filter_var($redirect_uri, FILTER_SANITIZE_URL));
        $client->setAccessToken($session->read('google.access.access_token'));

現在の状況は、アクセストークンを控えている状態です。

コメントにも記載されていますが、後ほどユーザー情報を取得するために、セッションにアクセストークンを保存します。

超簡単にユーザー情報を取得

さて、ここまで来ればあとはユーザー情報を取得するだけです。

タイトルにもある通り、超簡単に取得できるので、ご安心ください(笑)


        // アクセストークンをもとにGoogleのユーザーデータを取得
        $googleUser = json_decode(
            file_get_contents('https://www.googleapis.com/oauth2/v1/userinfo?' . 'access_token=' . $session->read('google.access.access_token'))
        );

これだけです!

この1行だけで、次のようなユーザー情報を取得することができるのです!

(
    [id] => 000000000000000000000
    [email] => test.lancers@lancers.co.jp
    [verified_email] => 1
    [name] => テスト ランサーズ
    [given_name] => ランサーズ
    [family_name] => テスト
    [picture] => https://sdl-stickershop.line.naver.jp/stickershop/v1/product/1348144/iphone/main@2x.png
    [locale] => ja
    [hd] => lancers.co.jp
)

あとはこれらの情報をもとにログイン処理をすれば、簡単にGoogleログイン機能を作ることができます!

まとめ

今回は、Googleのライブラリを使ってユーザー情報を超簡単に取得して見ました!

それによって、スムーズに管理画面へログインをすることができるようになりました。

英語ベースのドキュメントを読み解きながら実装していくのは大変でしたが、ものすごく良い経験になりました。

今後、そういった実装があったとしても、臆することなく、突き進んでいきたいと思います!

Advent Calendar 4日目でした!!(忘れてた笑)

Lancers(ランサーズ) Advent Calendar 2019

参考ソース

googleapis/google-api-php-client
Google | OAuth2

ランサーズ開発合宿2019 GASとSmartHR APIで業務効率化

kamiyagunji|2019年10月10日
JavaScript

みなさんこんにちは。
ランサーズでインターンをしている神谷(GunjiKamiya)です。

自身初投稿となる今回の記事では、自分が参加したLancers開発合宿2019でのことについて書いていこうと思います。

Lancersの開発部では、1年に一度大都会東京を離れ、自然豊かな場所で開発をする機会を設けています。

過去に4回ほど開催されており、どの開発合宿も日頃の疲れを取るとともに、メンバーをリフレッシュさせてくれる素晴らしい合宿になっているようです!

過去の開発合宿の記事はこちら

2015年

ランサーズ初のハッカソン開発合宿

2016年

横須賀ハッカソン合宿

2017年

ランサーズ開発合宿2017@湯河原温泉おんやど恵

2018年

ランサーズ開発合宿2018@熱海 新規事業開発チーム
ハイパフォーマンスな開発合宿をする3つの秘訣
ランサーズ開発合宿2018 バージョンアップチーム
ランサーズ開発合宿2018@熱海 仕事案件のクローラー開発チーム

そして今年も無事開催されることとなり、場所は静岡県の城ヶ崎海岸というところになりました。

↑このような素晴らしい場所で開発しました!(凄すぎて集中できない笑)

 

バックグラウンドを簡単に

2019年の3月からランサーズでエンジニアとしてインターンをしている、神谷暉士(かみやぐんじ)と申します。

2000年生まれの19歳です。

高校卒業後、都内のベンチャー企業をいくつかインターンをしながら生活をしていました。

1000人規模の大きな会社や数人しかいないスタートアップなどで1年と少しの期間を働いて、4社目にランサーズにジョインしたということになりますね。

ランサーズのエンジニアブログは前々から、社内の雰囲気を知るために見ていたのですが、今そのブログを書いているのだと思うと、少し不思議な感じがしますね(笑)

頑張って書きます!

 

開発合宿開始!

今回のランサーズ開発合宿の目標は、未来へ繋がるような開発合宿にすることです!

自分は初参加ということもあり、開発合宿という短期間でどのようなアウトプットを出せるのかを考えてみました。

自分が今回のテーマ決めのために考慮したのは、以下の2つです。

  • 開発合宿だけではなく、今後の開発に使用できる技術
  • 社内の小さな問題を解決

今回の合宿は未来につながる合宿にするということで、様々なものに応用できるような技術を使ってみたいと思いました。また、開発合宿は2日間(実際の作業時間はもっと少ない泣)という短期間できちんとしたアウトプットを出すために、大きいテーマではなく、あえて小さな問題を解決することにしました。そんな感じで、開発合宿初参加の自分の開発テーマは次のように決まりました!

「SmartHRのAPIを使って労務の社員管理を向上する」

ランサーズではSmartHRというクラウド人事労務ソフトを使っています。

SmartHR上に社員情報などを保存し管理しているのですが、実はそれとは別でスプレッドシートにもベタ書きしたデータを持っているようなのです。

その状態では、SmartHR上に変更があるたびに、スプレッドシートも変更しなければならないという2重管理状態になってしまいます。

毎日あるような作業ではないものの、作業のたびSmartHRに直接アクセスして手動で更新しなければならないため、労務の方が大変です。

色々考えていくうちに、SmartHRで変更したタイミングで自動的にスプレッドシートが更新されたら、すごく便利なのではないかと思い始めていきました。

そして、テーマ決めの参考材料である、「開発合宿だけでなく、今後の開発に使用できる技術」「社内の小さな問題を解決」の2つもクリアしていることがわかり、早速取り組んでみることにしました。

 

開発環境

SmartHRからはAPIを使ってデータを取得することになります。

しかし、直接本番データをいじるのはちょっと気が引けますよね。

自分自身、外部のサービスへAPIを使った開発には慣れていないということもあり、どうすればいいのやら。。

そんな時のために、SmartHRではsandboxというとても便利な環境が用意されているのです!

ありがとう!SmartHRさん!有効活用させていただきます!

以下がSmartHR APIのサンドボックス環境実行サンプルになります。

■サンドボックス
[ログインURL]
https://app.daruma.space/

 

[Basic認証]
ID: *****
Password: *****

 

[API実行サンプル]
curl -H 'Authorization: Bearer *******************' https://******.daruma.spce/api/v1/crews/

ちなみにこちらのサンドボックス環境は、SmartHRのAPIリファレンスページから申請できるので、よろしければ下記のURLから申請してみてください!

SmartHR API Sandbox ご利用お申込みフォーム

実行サンプルを叩いてみると、JSON形式でデータが返ってくるようになっています。

実行サンプルから返ってくるデータは全て架空の人物の情報なので、テストデータとして使うことができます。
また、ページネートを変えてAPIを叩けば、違う人物のデータが返ってきます。(以下で実証)

[API実行サンプル]
for i in $(seq 1 10) ;do curl -H 'Authorization: Bearer *******************' https://******.daruma.spce/api/v1/crews?page=$i ;done

上ではページネートを10まで変えてAPIを叩いてるのですが、実際に帰ってきたデータは30件ほどでした。

 

SmartHR APIを使ってデータを取得!

通常SmartHRのAPIからは、1度に10件のデータしか取得することができないのですが、先ほど紹介したやり方で全てのデータを取得することができるようになったと思います。

ここからは実際に、APIを叩いて配列に格納するところまでやってみましょう。

まずは、取得したデータを格納する配列を作っておきます。

// SmartHRのAPIから取得したデータ保存する配列
var user_data = [];

SmartHRにAPIを叩くために必要な情報を書いていきます。

// APIのアクセス先を定義
var url = 'https://******.daruma.spce/api/v1/crews?page=' + i;
var options =
  {
    'method' : 'get',
    'contentType': 'application/json',
    'headers': {'Authorization' : 'Bearer *******************'},
  };

これらの情報を使ってAPIを叩きます!
返ってきたデータはJSON形式なので、パースしてから配列に格納していきましょう。

for (var i=1; i<10; i++) {
  var response = UrlFetchApp.fetch(url, options);
  json = JSON.parse(response);

  // 1ページあたり10のユーザー情報が取得できるので、それらを配列に追加していく
  for (var e=0, l=json.length; e<l; e++) {
    user_data[user_data.length] = json[e];
  }
}
return user_data;

これでSmartHRから取得した全てのデータが格納された配列( user_data)を作ることができました。

 

GoogleAppsScriptを使っていざ出力!

今回は労務で使用するスプレッドシートへ自動で出力できるようにするために、GoogleAppsScript(通称:GAS)を使っていきます。

GASとは、Googleが開発したスクリプトプラットフォームで、G Suiteプラットフォームに対して色々自由に実装ができるのです!

これを使えば、SmartHRに対してAPIを叩き、スプレッドシートに出力するところまで、一括で実装できるようになります。

シートを生成

まずはスプレッドシートを作成していきます。

スプレッドシートの生成は、 insertSheet(シート名)で行うことができます。

var sheetName = '社員一覧';
var spreadSheet = SpreadsheetApp.getActive();
spreadSheet.insertSheet(sheetName);

 

社員一覧のひな形を生成

社員データを一覧に出力するために、まずはひな形を生成する必要があります。

直接書いていっても良いのですが、せっかくなので実装していきます。

// 出力するシートを定義
var sheet = spreadSheet.getSheetByName('社員一覧');

// ひな形で生成する項目を定義
var display_items = ['社員\n番号', '氏名', '雇用形態', '役職', '在籍状況'];

// 項目を出力するセルを定義
var scoop = ['A1', 'B1', 'C1', 'D1', 'E1'];

for (var i=0, l=display_items.length; i<l; i++) {
  //出力先を定義
  var push_point = sheet.getRange(scoop[i]);

  // 項目を出力
  push_point.setValue(display_items[i]);
  // 背景色を変更
  push_point.setBackground('#01366a');
  // 文字色を変更
  push_point.setFontColor('#ffffff');
  // 文字をセルの中央に配置
  push_point.setHorizontalAlignment('center');
  push_point.setVerticalAlignment('middle');
}

↑このように綺麗にひな形を生成することができました!

ユーザーデータをシートに出力

それでは、ここから実際に取得したユーザー情報を生成したひな形に出力していきましょう。

出力は配列に格納されている全てのユーザー情報を、出力するだけになります。
少し処理が重いように感じるかもしれませんが、それは実装の問題ではなく、GAS自体の問題になるので、少し遅くても気にしなくても大丈夫だと思います。

for (var a=0, l=user_data.length; a<l; a++) {
  // 出力する行
  var pushNumber = i + 2;
    
  // 社員番号出力
  sheet.getRange("A" + pushNumber).setValue(user_data[a]['emp_code']);
    
  // 氏名出力
  var name = user_data[a]['last_name'] + user_data[a]['first_name'];
  sheet.getRange("B" + pushNumber).setValue(name);
    
  // 雇用形態出力
  sheet.getRange("C" + pushNumber).setValue(user_data[a]['employment_type']['name']);
    
  // 役職出力
  sheet.getRange("E" + pushNumber).setValue(user_data[a]['position']);
    
  // 在籍状況出力
  sheet.getRange("F" + pushNumber).setValue(user_data[a]['emp_status']);
}

このように出力されました!
在籍状況のように、入れられている値が日本語ではない場合もあるので、それぞれ変換させて出力させてもいいですね。

↑こんな感じで在籍状況を日本語に変換!

テストデータは全部で30人ほど登録されていたので、30人の情報が全て下にズラーっと出力されました。

今回は5つの項目しか出力しませんでしたが、SmartHR APIには、これ以外にも多くの項目が用意されていて、SmartHR上に登録されている情報は、おおよそ取得できるようです。

詳しくは公式リファレンスを参考にしてみてくださいね。

SmartHR API Specifications

GoogleAppsScriptの公式リファレンスもぜひ参考にしてみてください。

Spreadsheet Service | Apps Script | Google Developers

 

各チームの発表タイム!

ついに開発合宿も最終日となりました。

各チーム、それぞれ開発したものや検証したものを発表する時間になりました。

少し見えづらいですが、きちんと目指していたものを形にすることができました。

その他のチームでも。。。

技術の検証をしたり、、

 

ツールの検証をしてみたり、、

あれ?どこかで見たことのあるシルエットですね(笑)

どのチームも、ランサーズの未来につながるような発表で本当に勉強になりました!

 

最後に。。

ランサーズでは、今回で5回目の開発合宿を無事終えることができました。

普段とは違った環境、違った空気感での開発は本当に最高でした。

(最高すぎて集中できなかったことがあったのは内緒ですが笑)

時にはこうした場所で、心と体を休めながら、自分やチームのメンバーに向き合うことも大切なのでしょうね。

普段からあまり詰め込みすぎないようにしたいところですが、それはそれでしょうがないです。

最後に、毎年恒例の写真を載せておきます。