Analog Studio

カレンダー共有アプリ「TimeTree」のAPIを使おう!~アクセストークン取得編~

概要

1500万人のユーザが利用するカレンダー共有アプリの「TimeTree」が外部サービスとの連携の為に API をリリースしました。(2019年5月30日)
そこで、そんなできたての API を使ってみましたので使い方を PHP や Javascript によるサンプルを使って解説します。

TimeTree API は、2023年12月22日をもって終了しました。本記事の内容はすでに利用できません。
https://timetreeapp.com/intl/ja/newsroom/2023-12-14/connect-app-api-202312

今回は API へのアクセスに必須なアクセストークン取得についてまとめます。
カレンダー情報の取得や予定の追加・更新・削除は別途まとめます。

トークン取得だけ読みたい方は「3. アクセストークンを取得しよう!」まで飛ばして下さい。

TimeTreeとは?

TimeTree とは 株式会社 TimeTree が開発、管理を行うカレンダー共有アプリです。
今では1500万人以上のユーザが利用するカレンダーアプリの定番として定着しつつあります。

TimeTree API

TimeTree ではカレンダーを自分以外と共有することを前提に開発され、予定を簡単に共有できます。
家族で用事を登録しあったり、仕事の仲間で進捗を共有したりできます。使うと便利さに驚きます。

誰とでも共有し合えます

また、予定画面では簡易的なチャットとして使うこともできるので、いちいち LINE などの他のアプリを起動することなくやり取りができます。

チャット機能も備わっています

こういった利用者の利便性を考えて作られているので多くの方に利用されているのでしょう。
Web 版もありますが、スマートフォン用のアプリの方が圧倒的に利便性が高いので App Store や Google Play からインストールしましょう。

Google Play で手に入れよう

TimeTree API で何ができるのか?

続いて TimeTree から提供される API を使うと何ができるのでしょうか?確認してみましょう。
公式なリリース情報は以下のリンクよりご確認下さい。

・TimeTree 公式ブログ - TimeTree API now available

カレンダー情報の取得

基本となるカレンダーに関する情報を取得できます。
この API では、API 利用者のアカウントでアクセス可能なカレンダーの ID やテーマカラー、参加しているユーザ一覧、ラベル情報一覧が取得できます。
※ "include" キーでそれぞれ "labels" や "members" の指定が必要です。

カレンダーの ID を指定すれば指定したカレンダーの情報のみを取得できます。(取得できる情報は一覧と同じ)
また、"ラベル情報のみ" や "参加ユーザのみ" の情報取得も可能です。

管理するカレンダーの数が多くない場合は一括で全ての情報を取得してしまうと便利でしょう。

カレンダーへの予定登録・編集・削除

メインとなる機能ですね。カレンダーに予定を登録したり、登録した予定を編集・削除できます。
この機能にはカレンダー ID が必要なのでカレンダーの一覧を取得して必要な ID を確認しておきます。

ここに自身のシステムと連携して予定 (イベント) を自動登録できるようにすれば非常に便利です。

予定へのコメント投稿

登録した予定にコメントを投稿することができます。
コメントにはカレンダー ID と予定の ID が必要になります。

カレンダーに登録されている予定のIDは取得できない

一点、注意です。
現在リリースされている API ではカレンダーに登録されている予定の ID は取得することができません。
予定の ID は予定の登録時のレスポンスに含まれるだけなのでここで自身のシステムに保存しておかなければ削除等ができません。
(API ではなくアプリ経由であれば削除や編集ができます)

この機能は今後追加実装されるらしいので、開発を待ちましょう。
予定の取得ができれば手動で登録した予定を自身のシステムに読み出すことができるようになるのでより便利になりますね。

アクセストークンを取得しよう!

では、アクセストークンを取得していきましょう。認証方法は良くある OAuth2 と馴染みのある方も多いと思います。
Javascript でもできますが、PKCE (権限の横取り対策) に対応しているということなので PHP で実装していきます。

大前提として TimeTree のアカウント取得とアプリの作成は済ませておいて下さい。

・TimeTree | OAuth Apps - アプリ管理画面 ・TimeTree | Dev - API 認証
・Qiita - PKCE: 認可コード横取り攻撃対策のために OAuth サーバーとクライアントが実装すべきこと

認証手順

PKCE 対策を利用した認証手順は以下の通りです。

  1. アプリの "クライアント ID" と "クライアントシークレット" の確認
  2. CSRF 対策の文字列と PKCE 対策の文字列も用意
  3. 認可サーバにパラメータを付けてアクセス
  4. ログインした状態で承認する (ブラウザ上でボタンをクリック)
  5. トークン発行用サーバで承認コードと PKCE 用の文字列を使ってトークンを発行

色々書いてますが、動作としてはサーバへのリクエストが2回だけやればOKです。

"クライアントID"と"クライアントシークレット"の確認

TimeTree のアプリ管理画面から使うアプリを選択します。
現時点ではアプリごとに権限の制限などができないので一つだけ作成しておけば良いでしょう。

アプリ一覧

作成したアプリの詳細を表示すると一番上に "クライアント ID" と "クライアントシークレット" が表示されます。
これらをメモしておきましょう。

アプリ詳細情報

※リセットという赤いボタンをクリックすると "クライアントシークレット" が違うものに変更されます。が、取得済みのアクセストークンは有効のままです。

CSRF対策の文字列とPKCE対策の文字列も用意

続いて不正にアクセストークンが取得されてしまわないように CSRF と PKCE 対策用の文字列を準備します。
※ CSRF : Cross-Site Request Forgeries の略で正規の要求と意図しない要求を判断するランダムな一時的な文字列を使って攻撃を防ぐことが一般的です。 ※ PKCE : Proof Key for Code Exchange の略でスマホアプリでの認証では対策を推奨しています。

トークンの取得処理はサーバサイドで完結させてブラウザとのやり取りは SSL で暗号化されますので、基本的に PKCE 対策は不要でしょう。
しかし、せっかく実装されているので PKCE 対策キーも使います。細かいことは調べてみて下さい。
※スマホアプリからトークンを取得する際にアプリと認可サーバへアクセスするブラウザ間が暗号化されないのでアプリ側でトークン取得に必要な鍵を持っておく為に PKCE を使います。

CSRF 用の文字列はセッション ID などから一意に生成した文字列が良く使われます。
この際、CSRF の認証が済んだらセッション ID を変更すると良いでしょう。

セッション ID から一意の文字列を生成するには SHA-256 などのハッシュ関数が便利です。
ハッシュ化した文字列からはセッション ID が推定されにくく、同じセッション ID からは同じハッシュ値が得られるからです。

<?php /* セッションIDからハッシュ値を生成する */ // セッション開始 session_start(); // ハッシュ化 $csrf_token = hash('sha256', session_id()); ?>

PKCE 用の文字列はちょっと面倒です。
まずは、半角英数字と [-._~] から43~128桁の文字列を生成します。これが "code_verifier" と呼ばれるものになります。
これをセッション変数などの第3者がアクセスできない場所に保管しておきます。
(スマホアプリならアプリが横取りされないようにする為の鍵になる)

次に、"ある規則"に従ってこの文字列を変換します。
"ある規則" には現在は2つあり "plane" と "S256" から選びます。が、"plane" は変換なしのことなので "S256" 一択です。

"S256" の変換は SHA-256 でハッシュ化した文字列を Base64 URL エンコードしたものになります。
この時、SHA-256 でハッシュ化した文字列はバイナリとします。
また、Base64 エンコードは URL で特殊な意味を持つ "+" や "/" を使うのでこれを URL に対応させるために "-" と "_" で置換して末尾も文字埋めの "=" を削除したものが Base64 URL エンコードです。
こうして得られた文字列が "code_challenge" というものになります。

<?php /* PKCE用の文字列を作成する */ // セッション開始 session_start(); // code_verifierをセッション変数に入れる $_SESSION['timetree'] = array( 'pkce_code_verifier' => create_pkce_code_verifier(), ); // code_challengeにS256で変換する // hash()関数の第3引数にtrueを設定するとバイナリに変換される $code_challenge = base64url_encode( hash( 'sha256', $_SESSION['timetree']['pkce_code_verifier'], true ) ); /* ユーザ定義関数 */ // PKCE用のランダムな文字列を生成 // 使用される文字列は [A-Z][a-z][0-9]-._~ で43~128桁 function create_pkce_code_verifier(){ $base_str = array_merge(range('A', 'Z'), range('a', 'z'), range('0', '9'), array('-', '_', '~', '.')); $length = rand(43, 128); $pkce_str = ''; for($i = 0, $n = count($base_str) - 1; $i < $length; $i++){ $pkce_str .= $base_str[rand(0, $n)]; } return $pkce_str; } // URL用のBASE64変換 // 参考:https://www.php.net/manual/ja/function.base64-encode.php#103849 function base64url_encode($data){ return rtrim(strtr(base64_encode($data), '+/', '-_'), '='); } ?>
認可サーバにパラメータを付けてアクセス

では、下準備が出来たので認可サーバにアクセスしていきます。
PHP では header() 関数で "Location: " にアクセス用の URL を入れればアクセスできます。
もちろん、ブラウザのアドレスバーに作成した URL を入れてもOKです。

具体的には以下のようにパラメータを設定します。普通の GET ですね。

https://timetreeapp.com/oauth/authorize? client_id={クライアントID}& redirect_uri={アプリ作成時に設定した認証コールバックURL}& response_type=code& state={CSRFトークン}& code_challenge={PKCE用で作成したcode_challenge} code_challenge_method=S256

PHP で作成するなら以下のようにすると楽です。

<?php /* PHPから認可サーバにリダイレクトさせる */ // セッション開始 session_start(); // CSRF用のトークン $csrf_token = hash('sha256', session_id()); // code_verifierをセッション変数に入れる $_SESSION['timetree'] = array('pkce_code_verifier' => create_pkce_code_verifier()); // code_challengeにS256で変換する // hash()関数の第3引数にtrueを設定するとバイナリに変換される $code_challenge = base64url_encode(hash('sha256', $_SESSION['timetree']['pkce_code_verifier'], true)); // TimeTree用のアプリ情報 $timetree = array( 'client_id' => 'クライアントID', 'client_secret' => 'クライアントシークレット', 'redirect_uri' => '認証コールバックURL', ); // 送信パラメータを用意する $param = array( 'client_id' => $timetree['client_id'], 'redirect_uri' => $timetree['redirect_uri'], 'response_type' => 'code', 'state' => $csrf_token, 'code_challenge' => $code_challenge, 'code_challenge_method' => 'S256', ); // リクエストURLに整形する $authorize_url = 'https://timetreeapp.com/oauth/authorize?'. format_get_param($param); // リダイレクトする header('Location: '. $authorize_url); exit; /* ユーザ定義関数 */ // PKCE用のランダムな文字列を生成 // 使用される文字列は [A-Z][a-z][0-9]-._~ で43~128桁 function create_pkce_code_verifier(){ $base_str = array_merge(range('A', 'Z'), range('a', 'z'), range('0', '9'), array('-', '_', '~', '.')); $length = rand(43, 128); $pkce_str = ''; for($i = 0, $n = count($base_str) - 1; $i < $length; $i++){ $pkce_str .= $base_str[rand(0, $n)]; } return $pkce_str; } // URL用のBASE64変換 // 参考:https://www.php.net/manual/ja/function.base64-encode.php#103849 function base64url_encode($data){ return rtrim(strtr(base64_encode($data), '+/', '-_'), '='); } // GETのリクエストパラメータ整形 // 1次元連想配列のみサポート // ※http_build_query()だとURLエンコードされてcode_challengeが変わってしまうので関数定義する function format_get_param($data = array()){ $arr = array(); foreach($data as $key => $value){ $arr[] = "{$key}={$value}"; } return implode('&', $arr); } ?>
ログインした状態で承認する

上記パラメータを付けてアクセスすると認証するかの画面が表示されるので、問題なければ "許可" をクリックします。

問題なければアクセストークン取得を許可します

許可するとアプリ作成時に設定した認証コールバック URL (リクエストのリダイレクト URI) に認証コードを追加してリダイレクトされます。
あとはこのコードを使ってトークンを取得します。

トークン発行用サーバで承認コードとPKCE用の文字列を使ってトークンを発行

認可サーバからトークン取得用のコードが発行されるのでこれを使ってアクセストークンを取得します。
この時に先ほど準備しておいた PKCE 用の文字列 "code_verifier" を使います。

cURL を使うと楽ですが、インストールされていない・できないサーバでも汎用的に動くように file_get_contents() 関数を使います。

<?php // セッション開始 session_start(); // TimeTree用のアプリ情報 $timetree = array( 'client_id' => 'クライアントID', 'client_secret' => 'クライアントシークレット', 'redirect_uri' => '認証コールバックURL', ); // GETでパラメータが送られてくるので取得する $code = filter_input(INPUT_GET, 'code'); $csrf = filter_input(INPUT_GET, 'state'); // CSRF対策の文字列が一致するか確認する if($csrf === $_SESSION['timetree']['csrf_token']){ // セッションIDを変更する session_regenerate_id(); // APIのリクエストURL $url = 'https://timetreeapp.com/oauth/token'; // 送信メソッド $method = 'POST'; // ヘッダ $header = array( 'Accept: application/vnd.timetree.v1+json', 'Content-Type: application/json', ); // 送信データ $param = array( 'client_id' => $timetree['client_id'], 'client_secret' => $timetree['client_secret'], 'redirect_uri' => $timetree['redirect_uri'], 'code' => $code, 'grant_type' => 'authorization_code', // セッションに保存しておいたPKCE用の文字列を設定する 'code_verifier' => $_SESSION['timetree']['pkce_code_verifier'], ); // file_get_contens関数でPOST送信する準備 $options = array( 'http' => array( 'method' => $method, 'header' => implode("\r\n", $header), 'content' => json_encode($param), 'ignore_errors' => true, // エラー発生時でも結果を受け取れるようにするフラグ 'protocol_version' => '1.1', ) ); // 結果を取得する $result = json_decode(file_get_contents($url, false, stream_context_create($options)), true); // 成功すれば$result['access_token']にアクセストークンが格納される } ?>

まとめ

以上、TimeTree の API でアクセストークンを取得する方法でした。
アクセストークンが取得できればカレンダーに予定を登録していけます。

トークンの取得自体は OAuth に則っているので分かりやすいと思います。
また、はじめて OAuth に触る方も API がシンプルで練習にも最適だと思います。(PKCE にも対応していますし)

次回は今回取得したアクセストークンを使ってカレンダーの情報を取得するをまとめたいと思います。
API の使い方の解説はカレンダー情報取得と予定の追加・編集の3記事になる予定です。

自分でやったけど良く分からない…という方は有償でお手伝いもできますのでぜひご相談下さい。