kubell Creator's Note

ビジネスチャット「Chatwork」のエンジニアのブログです。

ビジネスチャット「Chatwork」のエンジニアのブログです。

読者になる

チャットワークのOAuth2のクライアントをPHPで簡単に実装するためのライブラリを紹介

安達です、PHPでチャットワークのサーバーサイドの開発をしています。 ChatWork Advent Calendar 2017の15日目のエントリーは、チャットワークのOAuth2のクライアント開発をPHPで実装する方法を紹介したいと思います。

今年の10月にチャットワークはWebhookとOAuth2をリリースし、サービス間の連携がしやすくなりました。 developer.chatwork.com

とはいえ、OAuth2の認可コードフローを用いたクライアントの開発は、

  1. クライアントの登録
  2. コンセント画面へアクセスさせるリンクの作成
  3. 発行された認可コードからアクセストークンを取得
  4. アクセストークンを使ってAPIにアクセスする
  5. アクセストークンの有効期限が切れたらリフレッシュ

といったお決まりのパターンを実装していく必要があり、アクセストークンの取得やリフレッシュ周りの実装を1から開発するのは手間がかかります。

そこでPHPでのクライアント開発の手間を少しでも楽にするためのライブラリ oauth2-chatwork を用意しました。 このライブラリはthephpleague/oauth2-clientをベースにチャットワークのOAuth2の設定を組み込んだものになっています。

今回はこの oauth2-chatworkを使ってチャットワークのOAuth2クライアントを開発する流れを紹介します。なおこの記事で使用したコードはchatwork-oauth2-client-exampleにて公開していますので、クライアント開発時の参考にしていただければと思います。

使い方

1. クライアントの登録

まずはチャットワークのAPI管理画面にあるクライアント登録画面からOAuth2のクライアントを登録します。

f:id:cw-adachi:20171212163734p:plain

クライアントのIDやシークレットは登録完了時に画面に表示されます。これらはクライアント情報の編集画面でも確認できます。

2. ライブラリのインストール

次にcomposerを使ってPackagistからoauth2-chatwork をダウンロードしてください。現在の最新版はバージョンv0.1.1となっています。

$ composer require chatwork/oauth2-chatwork

ライブラリのインストールが終わったら、ユーザーがクライアントに認可を与える画面(この記事ではコンセント画面と呼ぶこととします)のURLを生成するところから実装を始めていきましょう。

3. コンセント画面のURLを生成する

コンセント画面のURLを生成するためには、ChatWorkProviderのコンストラクタに

  • クライアントID
  • クライアントシークレット
  • リダイレクトURI

を渡してインスタンスを生成し、getAuthorizationUrlメソッドを呼び出すことで取得できます。

<?php

$provider = new ChatWorkProvider(
    getenv('OAUTH2_CLIENT_ID'),
    getenv('OAUTH2_CLIENT_SECRET'),
    getenv('OAUTH2_REDIRECT_URI')
);

$url = $provider->getAuthorizationUrl([
    'scope' => ['users.all:read', 'rooms.all:read_write']
]);

動作確認のために生成されたURLにアクセスしてコンセント画面を表示してみてください。次のような画面が表示されていればOKです。

f:id:cw-adachi:20171215103624p:plain

なおスコープの定義とエンドポイントのマッピングはこちらのドキュメントを参照ください。

4. 認可コードからアクセストークンを発行する

ユーザーがコンセント画面中の「許可」ボタンを押すと、ChatWorkProviderのコンストラクタで指定したredirect_uriにリダイレクトされます。 リダイレクト時にcodeというキーで認可コード(有効期間は1分)が付与されるので、これを元にアクセストークンを発行していきます。アクセストークンの発行はChatWorkProvidergetAccessTokenを次のように呼び出します。

<?php

$accessToken = $provider->getAccessToken((string) new AuthorizationCode(), [
    'code' => $_GET['code']
]);

5. アクセストークンを利用してチャットワークのプロフィール情報 (/me)を取得する

4.で取得したアクセストークンを用いてChatWork APIを利用する方法を紹介します。

/meでプロフィール情報を取得する方法

ChatWorkProviderにはgetResourceOwnerというメソッドが用意されていて、このメソッドにアクセストークンを渡すだけでプロフィール情報(/me)を取得できます。

<?php

$resourceOwner = $provider->getResourceOwner($accessToken);

Chatwork APIドキュメント

その他のエンドポイント

その他のAPIにアクセスするメソッドは実装していないので、今のところGuzzleHttpのようなHTTPクライアントを使用していただく必要があります。 参考までに自分自身の未読数やメンション数を取得するエンドポイントを叩くサンプルコードを紹介します。

<?php

$client = new Client();
$response = $client->request('GET', 'https://api.chatwork.com/v2/my/status', [
    'headers' => [
        'Authorization' => sprintf('Bearer %s', $accessToken)
    ]
]);

アクセストークンは、HTTPのAuthorizationリクエストヘッダにBearerスキームでセットしてください。

6.リフレッシュトークンを使ってアクセストークンを再取得する

アクセストークンの有効期間は30分となっていて、30分を経過すると無効になってしまいますが、リフレッシュトークンを用いてアクセストークンを再発行することができます。リフレッシュトークンは認可コードからアクセストークンを発行した際のレスポンスボディに含まれています。

AccessTokenクラスのhasExpiredというメソッドでアクセストークンの期限切れを判定できますので、アクセストークンを再発行する処理は下のように書けます。

<?php

if ($accessToken->hasExpired()) {
    $refreshedAccessToken = $provider->getAccessToken((string) new RefreshToken(), [
        'refresh_token' => $accessToken->getRefreshToken()
    ]);
}

開発Tips & 注意事項

ここまで oauth2-chatwork を使ってクライアントを開発する流れを説明してきましたが、ここからHTTPSの開発環境を簡単に用意する方法やパブリックに公開するようなクライアントを開発するときの注意事項をお話します。

HTTPSの開発環境をローカルにサクッと用意する方法

現状チャットワークのOAuth2はredirect_uriとしてhttps://で始まるuriを指定する必要があります。 ngrokhttps-portalを使う方法もありますが、今回この記事を書く際にはPHPの実行環境をlaradockで用意したので、laradockの環境に含まれているCaddyを使ってみることにしました。

laradock直下のcaddy/Caddyfileをエディタで開いて、ホスト名を編集し、#tls self_signedのコメントアウトを外します。この後docker-compose up daddyするだけでSSLの自己証明書が生成されます。

diff --git a/caddy/Caddyfile b/caddy/Caddyfile
--- a/caddy/Caddyfile
+++ b/caddy/Caddyfile
@@ -1,5 +1,5 @@
 # Docs: https://caddyserver.com/docs/caddyfile
-0.0.0.0:80 {
+https://oauth2-chatwork.local {
     root /var/www/public
     fastcgi / php-fpm:9000 php {
         index index.php
@@ -17,7 +17,7 @@
     errors /var/log/caddy/error.log
     # Uncomment to enable TLS (HTTPS)
     # Change the first list to listen on port 443 when enabling TLS
-    #tls self_signed
+    tls self_signed

     # To use Lets encrpt tls with a DNS provider uncomment these
     # lines and change the provider as required

細かな手順はこちらを参照ください。

クライアント開発時の注意点

今回実装したクライアントは認可コード発行時のCSRF対策ができていません。stateの値自体はChatWorkProviderを使ってコンセント画面のURLを発行したときに生成されているのですが、認可を許可もしくは拒否した後にredirect_uriにリダイレクトされた後の検証がされていません。実際にCSRF対策をするときは次のような処理を実装することとなります。

  1. コンセント画面に渡すstateをセッションに保存する
  2. コンセント画面で「許可」もしくは「拒否」を押した後にredirect_uriにstateがクエリパラメータにセットされた状態でリダイレクトされる
  3. 1.でセッションに保存したstateと2.のクエリパラメータにセットされたstateを比較し、一致した場合にのみアクセストークンの取得処理へ進むように実装する

ちなみにChatWorkProviderはコンセント画面のURLを生成する度にランダムなstateを生成します。この値を取得するためには、getAuthorizationUrlを呼び出した後に、getStateを呼び出す必要があります。

<?php

$url = $provider->getAuthorizationUrl([
    'scope' => ['users.all:read', 'rooms.all:read_write']
]);

$state = $provider->getState();

業務で運用をされたり、インターネット上でパブリックに公開されるようなクライアントを開発される際は、必ずstateを用いたCSRF対策をしていただきたいと思います。

stateに関する詳しい仕様はRFC6749を参照ください。 RFC6749 - 10.12. Cross-Site Request Forgery

まとめ

OAuth2のクライアントをPHPで0から開発するのは大変ですが、今回開発したライブラリを用いることでアクセストークン取得/リフレッシュ周りの実装の手間が少しは省けるのではないでしょうか。 より多くの方にOAuth2のクライアントを開発していただきたいと思っていますので、ライブラリの使い勝手だけでなく、チャットワークのOAuth2機能に関するフィードバックもお待ちしています。