Data API のエンドポイント「authentication」を使って、サインインと投稿を行う

Movable Type の Data APIには、認証用の2つのエンドポイントが存在します。

* authorization
 * Movable Type の管理画面を呼び出し、サインインを行う。
* authentication
 * Data APIへユーザー名とパスワードを渡し、サインインを行う

(詳細は[「詳説 Data API Vol.7: Movable Type Data API のセキュリティの話」](https://www.movabletype.jp/blog/data-api-v3-07.html)をご覧ください。)

ウェブサイトやウェブアプリなどでは、MTのサインイン画面がそのまま使えるので、authorizationのほうが利用しやすいことでしょう。

一方で、スマホアプリやWebサービスなどから認証を行いたい場合は、authenticationを利用するケースが多いと思います。

今回は、authenticationを利用したサインイン、そして記事投稿のサンプルを考えてみます。

## authenticationを利用したサインインを行うサンプル

まず最初に、authenticationを使って、単純にサインインを行うサンプルを考えます。
このサンプルでは、以下の機能を実装するものとします。

* サインイン画面を表示
* ユーザー名、パスワードを入力後、「サインイン」ボタンを押すと、Movable Type のData APIを利用した認証を行う
* ユーザー情報が正しい場合、サインイン画面を消去して「サインインしています」というメッセージに切り替える

上記を実装してみたサンプルはこちらです。

authentication_signin_sample.html


```html

<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="utf-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>autheticationを使ったサインインサンプル</title>
  <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.1.1/jquery.min.js"></script>
  <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css">
</head>

<body>

<div class="container">
  <div class="form-group col-sm-8" id="content">
    <h2>サインイン</h2>
    <div class="form-group">
      <input class="form-control" id="username" placeholder="ユーザー名" type="text">
    </div>
    <div class="form-group">
      <input class="form-control" id="password" placeholder="パスワード" type="password">
    </div>
    <div class="form-group">
      <button id="signin">サインイン</button>
    </div>
  </div>
</div>


<script> 

$(function() {
  const clientId = 'authentication_signin_sample';
  const currentSession = 'mt_session_' + clientId;
  const apiUrl = 'http://your-mt/mt-data-api.cgi';

  if (sessionStorage.getItem(currentSession)) {
    haveSignedIn();
  }

  $('#signin').on('click', function() {
    signIn();
  });

  function haveSignedIn() {
    const displayData = `
        <div>
          <p>サインインしています。</p>
        </div>
        `
    $("#content").empty();
    $("#content").append(displayData);
  }

  function signIn() {
    const username = $('#username').val();
    const password = $('#password').val();
    $.ajax({
      url: apiUrl + '/v3/authentication',
      type: 'POST',
      dataType: 'json',
      data: {
        'username': username,
        'password': password,
        'clientId': clientId,
      }
    }).done(function(data) {
      sessionStorage.setItem(currentSession, data.sessionId);
      alert("サインインしました。");
      location.reload();
    }).fail(function(data) {
      alert("サインイン時にエラーが発生しました。再度サインインをお試しください。");
    });
  }

});

</script>
</body>
</html>

```

サンプルでは、Movable Type のData APIのURLを変数 「apiUrl」に設定して、使いまわしています。
ここでは2つの関数を実行しています。

### サインインを行う

サインインの処理は、関数「signIn」で行っています。

サインイン画面から入力したユーザ名とパスワードを入力後、「サインイン」ボタンをクリックしたタイミングで、Movable Type のData APIへ問い合わせます。ユーザー情報が正しい場合、サインインを実行して、レスポンスデータを返します。レスポンスデータには以下の情報が含まれています。

* accessToken
 * リクエストを呼び出すときにリクエストヘッダーに付与するアクセストークン
* sessionId
 * 認証済みセッションにおいて、新しいアクセストークンを取得するための ID
* expiresIn
 * アクセストークンの TTL（Time To Live: 生存期間）
* remember
 * セッションを保持し続けるかを示す

今回のサンプルでは、レスポンスデータから「sessionId」を取得して、sessionStorageに保存しています。

### サインイン済みの処理

サインイン済みの処理は関数「haveSignedIn」で行っています。

sessionnStorageにセッションIdが保存されているかどうかをチェックします。もし保存されている場合、サインインが完了していると判断して、サインイン画面を消去。代わりに「サインインしています」というメッセージを表示します。表示の切り替えはjQueryを使っています。


## authenticationを利用して、サインインとサインアウトを行うサンプル

前項のサンプルを少しアレンジしてみましょう。サインイン以外に、サインアウトも行うサンプルを考えてみます。

このサンプルでは、以下の機能を実装してみます。

* サインイン画面を表示
* ユーザー名、パスワードを入力後、「サインイン」ボタンを押すと、Movable Type のData APIを利用した認証を行う
* ユーザー情報が正しい場合、サインイン画面を消去して「サインインしています」というメッセージに切り替える
* 「サインアウト」ボタンを押すと、サインアウト処理を行い、初期画面に戻る

上記を実装したサンプルはこちらです。

authentication_signin_sample_2.html

```html
<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="utf-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>autheticationを使ったサインインサンプル2</title>
  <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.1.1/jquery.min.js"></script>
  <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css">
</head>

<body>

<div class="container">
  <div class="form-group col-sm-8" id="content">
    <h2>サインイン</h2>
    <div class="form-group">
      <input class="form-control" id="username" placeholder="ユーザー名" type="text">
    </div>
    <div class="form-group">
      <input class="form-control" id="password" placeholder="パスワード" type="password">
    </div>
    <div class="form-group">
      <button id="signin">サインイン</button>
    </div>
  </div>
</div>


<script> 

$(function() {
  const clientId = 'authentication_signin_sample_2';
  const currentSession = 'mt_session_' + clientId;
  const apiUrl = 'http://your-mt/mt-data-api.cgi';

  if (sessionStorage.getItem(currentSession)) {
    haveSignedIn();
  }

  $('#signin').on('click', function() {
    signIn();
  });

  $('#signout').on('click', function() {
    revokeSession();
  });

  function haveSignedIn() {
    const displayData = `
        <div>
          <p>サインインしています。</p>
        </div>
        <div>
          <button id="signout" name="signout" type="button">サインアウト</button>
        </div>
        `
    $("#content").empty();
    $("#content").append(displayData);
  }

  function signIn() {
    const username = $('#username').val();
    const password = $('#password').val();
    $.ajax({
      url: apiUrl + '/v3/authentication',
      type: 'POST',
      dataType: 'json',
      data: {
        'username': username,
        'password': password,
        'clientId': clientId,
      }
    }).done(function(data) {
      sessionStorage.setItem(currentSession, data.sessionId);
      alert("サインインしました。");
      location.reload();
    }).fail(function(data) {
      alert("サインイン時にエラーが発生しました。再度サインインをお試しください。");
    });
  }

  function revokeSession() {

    $.ajax({
        url: apiUrl + "/v3/authentication",
        type: "DELETE",
        headers: {
          'Content-Type': 'application/x-www-form-urlencoded',
          'X-MT-Authorization': 'MTAuth sessionId=' + sessionStorage.getItem(currentSession),
        }
    }).done(function() {
      sessionStorage.removeItem(currentSession);
      alert("サインアウトしました。");
      location.reload();
    }).fail(function(data) {
      alert("サインアウト中にエラーが発生しました。");
    });
  }  

});

</script>
</body>
</html>

```

中身は最初のサンプルとほぼ同じですが、以下の2点が追加となっています。

* サインイン後の画面に「サインアウト」ボタンを表示している
* サインアウトを実行する関数「revokeSession」を追加している

追加した関数「revokeSession」では、以下のような機能を実行しています。

* 「サインアウト」ボタンをクリックすると、Data APIへ通信を行い、現在利用中のセッションを破棄する
* セッションの破棄が完了したら、続いてsessionStorageに保存しているセッションIdトークンも消去する
* セッションIdトークンの削除後、初期のサインイン画面に戻る

セッションの破棄では、Data APIのエンドポイント「authentication」に対して、「DELETE」というメソッドで通信を行っています。

https://www.movabletype.jp/developers/data-api/v3-reference.html#authentication-authentication-delete

APIリファレンスに「This is like sign-out.」と記述されているとおり、DELETEリクエストが実行されると、利用中のセッション情報を破棄します。保存しているセッションIDも使えなくなるため、sessionStorageから消去処理を行います。

なお、REST APIで「DELETE」メソッドを利用する場合、サーバー側でDELETEメソッドが実行できるように設定を行う必要があります。

Movable Type では、環境変数[「DataAPICORSAllowMethods」](https://www.movabletype.jp/documentation/appendices/config-directives/dataapicorsallowmethods.html)で、PUT、GET以外に許可するメソッドを指定することができます。DELETEメソッドを使う場合は、環境変数の設定を適切に行なっておく必要があるのでご注意ください。

参考：
- https://www.w3.org/TR/cors/#access-control-allow-methods-response-header
- https://www.w3.org/wiki/CORS

## authenticationを利用したサインイン後、Movable Type に記事を投稿するサンプル

最後に、Movable Type へ投稿するサンプルを考えてみましょう。

最後のサンプルでは、以下の機能を実装します。

* サインイン画面を表示
* ユーザー名、パスワードを入力後、「サインイン」ボタンを押すと、Movable Type のData APIを利用した認証を行う
* ユーザー情報が正しい場合、サインイン画面を消去して記事投稿用のフォームを表示する。
 * 今回は「タイトル」と「本文」を入力できるようにする。
* データ入力後「投稿する」ボタンを押すと、Movable Type にデータを投稿する。
* 「サインアウト」ボタンを押すと、サインアウト処理を行い、初期画面に戻る


authentication_post_sample.html

```html
<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="utf-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>autheticationを使った投稿サンプル</title>
  <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.1.1/jquery.min.js"></script>
  <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css">
</head>

<body>

<div class="container">
  <div class="form-group col-sm-8" id="content">
    <h2>サインイン</h2>
    <div class="form-group">
      <input class="form-control" id="username" placeholder="ユーザー名" type="text">
    </div>
    <div class="form-group">
      <input class="form-control" id="password" placeholder="パスワード" type="password">
    </div>
    <div class="form-group">
      <button id="signin">サインイン</button>
    </div>
  </div>
</div>

<script> 

$(function() {
  const clientId = 'authentication_post_sample';
  const currentSession = 'mt_session_' + clientId;
  const currentToken = 'mt_token_' + clientId;
  const nowTime = Math.floor(new Date().getTime() / 1000);

  const apiUrl = 'http://your-mt/mt-data-api.cgi';
  const siteId = '投稿したいブログのID番号';


  if (sessionStorage.getItem(currentSession)) {
    haveSignedIn();
  }

  $('#signin').on('click', function() {
    signIn();
  });

  $('#submit').on('click', function() {
    var promise = Promise.resolve(checkToken());
    promise.then(function() {
      PostEntry();
    });
  });

  $('#signout').on('click', function() {
    revokeSession();
  });

  function haveSignedIn() {
    const displayData = `
        <h3>投稿する</h3>
        <form class="col-sm-8">
        <div class="form-group">
          <input id="title" name="title" placeholder="タイトル" type="text"  class="form-control">
        </div>
        <div class="form-group">
          <textarea id="body" maxlength="140" placeholder="本文" rows="7"  class="form-control"></textarea>
        </div>
        <div class="form-group">
          <button id="submit" name="submit" type="button">投稿する</button>
        </div>
        <div class="form-group">
          <button id="signout" name="signout" type="button">サインアウト</button>
        </div>
        </form>
        `
    $("#content").empty();
    $("#content").append(displayData);
  }

  function signIn() {
    const username = $('#username').val();
    const password = $('#password').val();
    $.ajax({
      url: apiUrl + '/v3/authentication',
      type: 'POST',
      dataType: 'json',
      data: {
        'username': username,
        'password': password,
        'clientId': clientId,
      }
    }).done(function(data) {
      sessionStorage.setItem(currentSession, data.sessionId);
      let TokenData = {
        'accessToken': data.accessToken,
        'expiresIn': data.expiresIn,
        'gotTokenTime': nowTime,
      }
      sessionStorage.setItem(currentToken, JSON.stringify(TokenData));
      alert("サインインしました。");
      location.reload();
    }).fail(function(data) {
      alert("サインイン時にエラーが発生しました。再度サインインをお試しください。");
    });
  }

  function PostEntry() {
    const entry = {};
    entry.title = $('#title').val();
    entry.body = $('#body').val();
    $.ajax({
      url: apiUrl + '/v3/sites/' + siteId + '/entries',
      type: 'POST',
      dataType: 'json',
      headers: {
        'X-MT-Authorization': 'MTAuth accessToken=' + JSON.parse(sessionStorage.getItem(currentToken)).accessToken,
      },
      data: {
        'entry': JSON.stringify(entry)
      },
    }).done(function() {
      alert("データを投稿しました。");
    }).fail(function(data) {
      alert("投稿時にエラーが発生しました。");
    });
  }

  function revokeSession() {
    $.ajax({
      url: apiUrl + '/v3/token',
      type: "DELETE",
      headers: {
        'Content-Type': 'application/x-www-form-urlencoded',
        'X-MT-Authorization': 'MTAuth accessToken=' + JSON.parse(sessionStorage.getItem(currentToken)).accessToken,
      }
    }).done(function() {
      $.ajax({
        url: apiUrl + "/v3/authentication",
        type: "DELETE",
        headers: {
          'Content-Type': 'application/x-www-form-urlencoded',
          'X-MT-Authorization': 'MTAuth sessionId=' + sessionStorage.getItem(currentSession),
        }
      })
    }).done(function() {
      sessionStorage.removeItem(currentSession);
      sessionStorage.removeItem(currentToken);
      alert("サインアウトしました。");
      location.reload();
    }).fail(function(data) {
      alert("サインアウト中にエラーが発生しました。もう一度サインインし直してください。");
      sessionStorage.removeItem(currentSession);
      sessionStorage.removeItem(currentToken);
      location.reload();
    });
  }

  function checkToken() {
    const expiresIn = JSON.parse(sessionStorage.getItem(currentToken)).expiresIn;
    const gotTokenTime = JSON.parse(sessionStorage.getItem(currentToken)).gotTokenTime;
    if (nowTime >= expiresIn + gotTokenTime) {
      refreshToken();
    }
  }

  function refreshToken() {
    $.ajax({
      url: apiUrl + '/v3/token',
      type: "DELETE",
      headers: {
        'Content-Type': 'application/x-www-form-urlencoded',
        'X-MT-Authorization': 'MTAuth accessToken=' + JSON.parse(sessionStorage.getItem(currentToken)).accessToken,
      }
    }).done(function() {
      $.ajax({
        url: apiUrl + '/v3/token',
        type: "POST",
        dataType: "json",
        headers: {
          'Content-Type': 'application/x-www-form-urlencoded',
          'X-MT-Authorization': 'MTAuth sessionId=' + sessionStorage.getItem(currentSession),
        }
      }).done(function(data) {
        let TokenData = {
          'accessToken': data.accessToken,
          'expiresIn': data.expiresIn,
          'gotTokenTime': nowTime,
        }
        sessionStorage.setItem(currentToken, JSON.stringify(TokenData));
      }).fail(function(data) {
        alert("アクセストークンのリフレッシュ中にエラーが発生しました。サインし直してください。");
        sessionStorage.removeItem(currentSession);
        sessionStorage.removeItem(currentToken);
        location.reload();
        
      });
    });
  }
});


</script>
</body>
</html>

```

今回のサンプルで行っていることは以下となります。


- サインイン画面を表示
- ユーザー名、パスワードを入力後、「サインイン」ボタンを押すと関数「signIn」を実行。Movable Type のData APIを利用した認証を行う
- ユーザー情報が正しい場合、Movable Type からのレスポンスデータを受取る
- レスポンスデータを2つに分けてsessionStorageに保存する。保存する2つのデータは以下
    - mt_session_クライアントID名
        - セッションIdを単体で保存。アクセストークンを入れ替えるときに使用する。
    - mt_token_クライアントID名
        - アクセストークンとその有効期限、アクセストークンの取得時間、の3点をJSON形式で保存。
- 「投稿する」ボタンを押すと、最初に関数「checkToken」を実行。sessionStorageに保存されているアクセストークンが有効期限内かどうかをチェックする。
 - 有効期限内の場合、関数「postEntry」を実行する。sessionStorageからアクセストークンを取り出し、Movable Type に記事データを投稿する
 - 有効期限が切れている場合、先に関数「refreshToken」を実行。
    - Movable Typeから期限切れのアクセストークンを削除後、アプリ側で保存しているデータも削除。
    - セッションIdを利用して、新たにアクセストークンを取得しなおす。
- アクセストークンの再取得が終わったら、関数「postEntry」を実行して記事を投稿する
-「サインアウト」ボタンを押すと、関数「revokeSession」を実行する
  - アクセストークンとセッションIDをMovable Type から消去
  - sessionStorageに保存されている関連データを全て消去後、初期画面に戻る

今回のサンプルでは、最初の2つのサンプルに加えて「checkToken」「refreshToken」「postEntry」関数を追加しています。また、revokeSessionも少し修正しています。


### アクセストークンの有効期限をチェックする (checkToken)

アクセストークンの有効期限を、関数「checkToken」でチェックしています。checkTokenでは、アクセストークンの取得時間と有効期限を取り出し、現在時刻と比較します。もしアクセストークンの有効期限が切れている場合、関数「refreshToken」を実行します。


### アクセストークンを取得し直す (refreshToken)

アクセストークンの有効期限が切れていた場合、関数「refreshToken」で再取得します。

refreshTokenでは、アプリ側に保存しているセッションIdトークンを利用して、アクセストークンを再取得しています。[Data API の「token」というエンドポイント](https://www.movabletype.jp/developers/data-api/v3-reference.html#authentication-token-post
)を使い、新しいアクセストークンを発行します。

### Movable Type に記事を投稿する (postEntry)

記事の投稿は、関数「postEntry」で行います。

postEntryでは、投稿画面に入力されたタイトル、本文データを、jQueryを使って取得後、Data API経由でMovable Type に投稿しています。投稿時には、[エンドポイント「entries」](https://www.movabletype.jp/developers/data-api/v3-reference.html#entries-entries-post)を使用します。



投稿時には、アプリに保存しているアクセストークンをリクエストヘッダーに記述します。X-MT-Authorization リクエストヘッダーに MTAuth accessToken=&lt;ACCESS_TOKEN&gt;という形式で指定します。

詳しくは、「クイックスタートガイド」内の「アクセストークンを付加したリクエストを行なう」をご覧ください。
https://www.movabletype.jp/developers/data-api/quick-start/


## 免責

今回のサンプルは、authenticationの使い方を説明するために作成したサンプルとなり、十分なテストを行っておりません。実際の案件に利用する際は、作成したコードのテストを十分に行った上で実装ください。

※何かお気づきの点などありましたら、どうぞお知らせください。