FileMaker CloudのためのClaris ID Token生成アプリを開発・公開しました

公開日: 更新日:

最近、FileMaker Cloud環境での開発案件が重なり、Data APIを利用する機会が増えてきました。そこで、開発効率を上げるために Claris ID Tokenを簡単に生成できるWebアプリを作成・公開しました。

FileMaker Server環境の場合は、以下のようなcurlコマンドで簡単にセッショントークンが作成できます。

TOKEN=$(echo -n "ACCOUNT:PASSWORD" | base64)

curl --request "POST" \
  -H "Authorization: Basic ${TOKEN}" \
  -H "Content-Type: application/json" \
  -d "{}" \
  --include \
  "https://xxx.frudens.jp/fmi/data/v2/databases/DATABASE_NAME/sessions" > "$(date "+%Y%m%d_%H%M%S").txt"
FileMaker Server 環境でのセッショントークン作成コマンド例

しかし、FileMaker Cloud環境の場合、Amazon CognitoのAPIを呼び出したり、AWS SDKを実装する必要があるため、FileMaker Server環境に比べると実装難易度が高くなります。

ヘルプを確認

具体的には、以下のヘルプにAmazon CognitoのAPIを利用するための簡易的なJavaScriptのサンプルが記載されています。

外部認証での Claris ID の使用

FileMaker Cloud で FileMaker Data API、FileMaker Admin API または OData API を使用するには、外部 IdP アカウントではなく、Claris ID アカウントを使用して認証する必要があります。

FileMaker Cloud では外部認証に Amazon Cognito を使用します。

Amazon Cognito ではユーザプールを使用してユーザを管理します。サインイン、アイデンティティプロバイダを使用した認証、ユーザディレクトリとユーザプロファイルの管理、およびセキュリティ監視などのサービスを提供します。(Amazon Cognito ユーザプールを参照してください。)

外部認証での Claris ID の使用 | Claris Customer Console ヘルプ

Claris ID Tokenを取得するためのサンプル

var authenticationData = {
    Username : 'ユーザ名',
    Password : 'パスワード',
};
var authenticationDetails = new AmazonCognitoIdentity.AuthenticationDetails(authenticationData);
var poolData = {
    UserPoolId : 'us-west-2_NqkuZcXQY',
    ClientId :'4l9rvl4mv5es1eep1qe97cautn'
};
var userPool = new AmazonCognitoIdentity.CognitoUserPool(poolData);
var userData = {
    Username : 'ユーザ名',
    Pool : userPool
};
var cognitoUser = new AmazonCognitoIdentity.CognitoUser(userData);
cognitoUser.authenticateUser(authenticationDetails, {
    onSuccess: function (result) {
        var Cognito_access_token = result.getAccessToken().getJwtToken();
        var Claris_ID_token = result.idToken.jwtToken;
        var Claris_ID_refresh_token = result.refreshToken.token;
    },
    onFailure: function(err) {
        alert(err);
    },
    mfaRequired: function(codeDeliveryDetails) {
        var verificationCode = prompt('Please input verification code' ,'');
        cognitoUser.sendMFACode(verificationCode, this);
    }
});
Claris ヘルプに記載されている Amazon Cognito SDK を使用したサンプルコード

Accessing Your User Pools using the Amazon Cognito Identity SDK for JavaScript | Front-End Web & Mobile

一般的なプログラミングを経験している開発者であれば、容易に理解できると思いますが、FileMakerしか使ったことのない開発者の場合は、少し難しいと感じるかもしれません。

また、AWSのJavaScriptのSDKを使うということは、簡易的なHTMLを作成する必要があります。Node.jsやPythonなどのAWS SDKを使用する場合は、それらのランタイム環境やサーバー構築が必要となります。

そこで、Web技術(HTML/JS)やAWSの環境構築に不慣れな開発者の方でも、簡単にClaris ID Tokenを取得できるように、Claris ID Token生成アプリを開発・公開しました。

Claris ID Token生成アプリについて

以下のURLにアクセスし、Claris IDとパスワードを入力するだけで、Claris ID Tokenを取得できます。

ページ下部の動画で、実際の操作手順や動作の様子をご確認いただけます。

Generating a Claris ID Token with FileMaker Cloud | frudens Inc.

本アプリは、処理のすべてがクライアントサイド(ブラウザ上)で完結しています。Amazon CognitoへのAPIリクエストを除き、入力された認証情報を外部サーバーへ送信・保存する処理は一切含まれていません。ソースコードは公開しておりますので、エンジニアの方はご自身で安全性を確認していただくことも可能です。

認証フロー図

sequenceDiagram autonumber actor User as ユーザー participant Browser as ブラウザ
(Claris ID Token生成アプリ) participant Cognito as Amazon Cognito
(us-west-2) Note over Browser, Cognito: ⚠️ サーバーを介さず直接通信 User ->> Browser: メールアドレス / PW 入力 Browser ->> Cognito: InitiateAuth (SRP_A)
認証開始リクエスト Cognito -->> Browser: Challenge (PASSWORD_VERIFIER)
パスワード検証要求 Browser ->> Cognito: RespondToAuthChallenge
署名付きレスポンス送信 alt MFA認証が必要な場合 Cognito -->> Browser: Challenge (SMS_MFA)
認証コード要求 Browser ->> User: コード入力プロンプト User ->> Browser: SMSのコードを入力 Browser ->> Cognito: RespondToAuthChallenge
認証コード送信 end Cognito -->> Browser: AuthenticationResult
(ID Token, Access Token, Refresh Token) Browser ->> User: トークンとcurlコマンドを表示
認証プロセスのシーケンス図:処理はブラウザとAmazon Cognito間で完結する

Claris ID Token生成アプリのHTML

ソースコードを確認したい場合は、以下をクリックして展開してください。

ソースコードを表示(index.html)
<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <meta name="author" content="Teruhiro Komaki, frudens">
  <meta name="description" content="A simple app for generating Claris ID Token with FileMaker Cloud.">
  <meta name="keywords" content="Claris FileMaker, FileMaker Cloud, FileMaker Cloud ID Token">
  <meta name="theme-color" content="#ffffff">
  <meta name="twitter:card" content="summary">
  <meta name="twitter:site" content="@frudensjp">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta property="og:description" content="A simple app for generating Claris ID Token with FileMaker Cloud.">
  <meta property="og:image" content="https://claris-idtoken.frudens.jp/og-image.png">
  <meta property="og:site_name" content="claris-idtoken.frudens.jp">
  <meta property="og:title" content="Generating a Claris ID Token with FileMaker Cloud | frudens Inc.">
  <meta property="og:type" content="website">
  <meta property="og:url" content="claris-idtoken.frudens.jp">
  <link rel="apple-touch-icon" href="/apple-touch-icon-114x114.png" sizes="114x114">
  <link rel="apple-touch-icon" href="/apple-touch-icon-120x120.png" sizes="120x120">
  <link rel="apple-touch-icon" href="/apple-touch-icon-144x144.png" sizes="144x144">
  <link rel="apple-touch-icon" href="/apple-touch-icon-152x152.png" sizes="152x152">
  <link rel="apple-touch-icon" href="/apple-touch-icon-180x180.png" sizes="180x180">
  <link rel="apple-touch-icon" href="/apple-touch-icon-57x57.png" sizes="57x57">
  <link rel="apple-touch-icon" href="/apple-touch-icon-60x60.png" sizes="60x60">
  <link rel="apple-touch-icon" href="/apple-touch-icon-72x72.png" sizes="72x72">
  <link rel="apple-touch-icon" href="/apple-touch-icon-76x76.png" sizes="76x76">
  <link rel="apple-touch-icon" href="/apple-touch-icon-76x76.png" sizes="76x76">
  <link rel="canonical" href="https://claris-idtoken.frudens.jp/">
  <link rel="icon" href="/favicon-16x16.png" sizes="16x16" type="image/png">
  <link rel="icon" href="/favicon-32x32.png" sizes="32x32" type="image/png">
  <link rel="icon" href="/favicon-96x96.png" sizes="96x96" type="image/png">
  <link rel="icon" href="/favicon.ico" sizes="48x48">
  <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-9ndCyUaIbzAi2FUVXJi0CjmCapSmO7SnpJef0486qhLnuZ2cdeRhO02iuK6FUUVM" crossorigin="anonymous">
  <script src="https://cdn.jsdelivr.net/npm/amazon-cognito-identity-js@5.1.0/dist/amazon-cognito-identity.min.js"></script>
  <title>Generating a Claris ID Token with FileMaker Cloud | frudens Inc.</title>
</head>
<body class="bg-body-tertiary">
<div class="container">
  <div class="py-5 text-center">
    <h1>Generating a Claris ID Token with FileMaker Cloud</h1>
    <p class="lead text-black-50">A simple app for generating Claris ID Token with FileMaker Cloud.<br>
      As this is a client-side process only, we do not collect any information you enter. Please check the source code.</p>
  </div>

  <div class="card mb-3">
    <div class="card-header">
      <strong>Get Claris ID Token</strong>
    </div>
    <div class="card-body">
      <div class="mb-3">
        <label for="email" class="form-label">Email address</label>
        <input type="email" class="form-control" id="email" aria-describedby="emailHelp">
        <div id="emailHelp" class="form-text">Enter your email address when signing in to your Claris ID.</div>
      </div>
      <div class="mb-3">
        <label for="password" class="form-label">Password</label>
        <input type="password" class="form-control" id="password">
      </div>
      <div>
        <button type="button" class="btn btn-outline-primary" onclick="getClarisIdToken()">Get Claris ID Token</button>
      </div>
    </div>
  </div>

  <div class="card mb-3">
    <div class="card-header">
      <strong>Refresh Claris ID Token / Global Sign out</strong>
    </div>
    <div class="card-body">
      <div class="mb-3">
        <label for="clarisIdTokenResponse" class="form-label">Claris ID Token Response</label>
        <textarea id="clarisIdTokenResponse" class="form-control" aria-label="clarisIdTokenResponse" rows="5"></textarea>
      </div>
      <div class="mb-3">
        <label for="refreshToken" class="form-label">Refresh Token</label>
        <textarea id="refreshToken" class="form-control" aria-label="refreshToken"></textarea>
        <div id="refreshTokenHelp" class="form-text"><a href="https://help.claris.com/en/customer-console-help/content/create-fmid-token.html">The Claris ID refresh token allows scripts to continue running after the Claris ID token expires, and is valid for one year.</a></div>
      </div>
      <div class="mb-3">
        <label for="accessToken" class="form-label">Access Token</label>
        <textarea id="accessToken" class="form-control" aria-label="accessToken"></textarea>
        <div id="accessTokenHelp" class="form-text"><a href="https://docs.aws.amazon.com/ja_jp/cognito-user-identity-pools/latest/APIReference/API_GlobalSignOut.html">The access token is used in the GlobalSignOut method.</a></div>
      </div>
      <div class="mb-3">
        <label for="clarisIdToken" class="form-label">Claris ID Token</label>
        <textarea id="clarisIdToken" class="form-control" aria-label="clarisIdToken"></textarea>
        <div id="clarisIdTokenHelp" class="form-text"><a href="https://help.claris.com/en/customer-console-help/content/create-fmid-token.html">The Claris ID token is used for authentication when you use the FileMaker Data API, FileMaker Admin API, or OData API, and is valid for one hour.</a></div>
      </div>
      <div>
        <button type="button" class="btn btn-outline-primary" onclick="refreshClarisIdToken()">Refresh Claris ID Token</button>
        <button type="button" class="btn btn-outline-secondary" onclick="globalSignOut()">Global Sign out</button>
      </div>
    </div>
  </div>

  <div class="card mb-3">
    <div class="card-header">
      <strong>curl Command for FileMaker</strong>
    </div>
    <div class="card-body">
      <div class="mb-3">
        <label for="curlRefreshClarisIdToken" class="form-label">Refresh Claris ID Token</label>
        <textarea id="curlRefreshClarisIdToken" class="form-control" aria-label="curlRefreshClarisIdToken" rows="5" readonly></textarea>
      </div>
      <div class="mb-3">
        <button id="curlRefreshClarisIdTokenButton" type="button" class="btn btn-outline-secondary" onclick="copy(this)">Copy</button>
      </div>
      <hr class="my-3">
      <div class="mb-3">
        <label for="curlGlobalSignOut" class="form-label">Global Sign out</label>
        <textarea id="curlGlobalSignOut" class="form-control" aria-label="curlGlobalSignOut" rows="5" readonly></textarea>
      </div>
      <div>
        <button id="curlGlobalSignOutButton" type="button" class="btn btn-outline-secondary" onclick="copy(this)">Copy</button>
      </div>
    </div>
  </div>

  <div class="card mb-3">
    <div class="card-header">
      <strong>API Reference</strong>
    </div>
    <div class="card-body">
      <p><strong>Amazon Cognito User Pools - API Reference</strong></p>
      <ul>
        <li>
          <a href="https://docs.aws.amazon.com/ja_jp/cognito-user-identity-pools/latest/APIReference/API_InitiateAuth.html">InitiateAuth - Amazon Cognito User Pools</a>
        </li>
        <li>
          <a href="https://docs.aws.amazon.com/ja_jp/cognito-user-identity-pools/latest/APIReference/API_GlobalSignOut.html">GlobalSignOut - Amazon Cognito User Pools</a>
        </li>
      </ul>
    </div>
  </div>

  <div class="card">
    <div class="card-header">
      <strong>Video</strong>
    </div>
    <div class="card-body">
      <div style="padding:56.19% 0 0 0;position:relative;">
        <iframe src="https://player.vimeo.com/video/924884953?badge=0&amp;autopause=0&amp;player_id=0&amp;app_id=58479" frameborder="0" allow="autoplay; fullscreen; picture-in-picture; clipboard-write" style="position:absolute;top:0;left:0;width:100%;height:100%;" title="Generating a Claris ID Token with FileMaker Cloud | frudens Inc."></iframe>
      </div>
      <script src="https://player.vimeo.com/api/player.js"></script>
    </div>
  </div>

  <footer class="my-5 text-center">
    <p>
      <strong>Generating a Claris ID Token with FileMaker Cloud</strong> by <a href="https://frudens.jp">Teruhiro Komaki (frudens Inc.)</a>.
    </p>
  </footer>

</div>
<script>
  // auth
  const EmailElement = document.getElementById('email');
  const PasswordElement = document.getElementById('password');

  // token
  const ClarisIdTokenResponseElement = document.getElementById('clarisIdTokenResponse');
  const RefreshTokenElement = document.getElementById('refreshToken');
  const AccessTokenElement = document.getElementById('accessToken');
  const ClarisIdTokenElement = document.getElementById('clarisIdToken');

  // curl
  const CurlRefreshClarisIdTokenElement = document.getElementById('curlRefreshClarisIdToken');
  const CurlGlobalSignOutElement = document.getElementById('curlGlobalSignOut');
  const CurlRefreshClarisIdTokenButtonElement = document.getElementById('curlRefreshClarisIdTokenButton');
  const CurlGlobalSignOutButtonElement = document.getElementById('curlGlobalSignOutButton');

  const POOL_DATA = {
    UserPoolId: 'us-west-2_NqkuZcXQY',
    ClientId: '4l9rvl4mv5es1eep1qe97cautn',
  };
  const USER_POOL = new AmazonCognitoIdentity.CognitoUserPool(POOL_DATA);

  function CustomResponse(ok, data, error, message) {
    this.ok = ok;
    this.data = data;
    this.error = error;
    this.message = message;
  }

  const curlRefreshClarisIdTokenTemplate = (refreshToken) => {
    return `curl -X 'POST' 'https://cognito-idp.us-west-2.amazonaws.com/' \\
  -H 'authority: cognito-idp.us-west-2.amazonaws.com' \\
  -H 'content-type: application/x-amz-json-1.1' \\
  -H 'x-amz-target: AWSCognitoIdentityProviderService.InitiateAuth' \\
  --data-raw '{"ClientId":"${POOL_DATA.ClientId}","AuthFlow":"REFRESH_TOKEN_AUTH","AuthParameters":{"REFRESH_TOKEN":"${refreshToken}","DEVICE_KEY":null}}'`;
  };

  const curlGlobalSignOutTemplate = (accessToken) => {
    return `curl -X 'POST' 'https://cognito-idp.us-west-2.amazonaws.com/' \\
  -H 'authority: cognito-idp.us-west-2.amazonaws.com' \\
  -H 'content-type: application/x-amz-json-1.1' \\
  -H 'x-amz-target: AWSCognitoIdentityProviderService.GlobalSignOut' \\
  --data-raw '{"AccessToken":"${accessToken}"}'`;
  };

  function clearElement() {
    // clear token
    ClarisIdTokenResponseElement.value = '';
    RefreshTokenElement.value = '';
    AccessTokenElement.value = '';
    ClarisIdTokenElement.value = '';

    // clear curl command
    CurlRefreshClarisIdTokenElement.value = '';
    CurlGlobalSignOutElement.value = '';
  }

  function copy(element) {
    if (element === CurlRefreshClarisIdTokenButtonElement) {
      navigator.clipboard.writeText(CurlRefreshClarisIdTokenElement.value);
    } else if (element === CurlGlobalSignOutButtonElement) {
      navigator.clipboard.writeText(CurlGlobalSignOutElement.value);
    }
  }

  async function getClarisIdToken() {
    console.log('getClarisIdToken');
    try {
      const authenticationData = {
        Username: EmailElement.value,
        Password: PasswordElement.value,
      };
      const authenticationDetails = new AmazonCognitoIdentity.AuthenticationDetails(authenticationData);

      const userData = {
        Username: EmailElement.value,
        Pool: USER_POOL,
      };
      const cognitoUser = new AmazonCognitoIdentity.CognitoUser(userData);
      await cognitoUser.authenticateUser(authenticationDetails, {
        onSuccess: function(result) {
          console.log('onSuccess');
          console.log('result');
          console.log(result);

          // update token
          ClarisIdTokenResponseElement.value = JSON.stringify(result);
          const refreshToken = result.refreshToken.token;
          const accessToken = result.accessToken.jwtToken;
          RefreshTokenElement.value = refreshToken;
          AccessTokenElement.value = accessToken;
          ClarisIdTokenElement.value = result.idToken.jwtToken;

          // update curl command
          CurlRefreshClarisIdTokenElement.value = curlRefreshClarisIdTokenTemplate(refreshToken);
          CurlGlobalSignOutElement.value = curlGlobalSignOutTemplate(accessToken);

          return new CustomResponse(true, result, null, null);
        },

        onFailure: function(e) {
          console.error('onFailure');
          console.error(e);

          clearElement();
          // update response
          ClarisIdTokenResponseElement.value = JSON.stringify(e);

          return new CustomResponse(false, null, e, e.message);
        },

        mfaRequired: function(codeDeliveryDetails) {
          console.log('mfaRequired');
          const verificationCode = window.prompt('Please input verification code', '');
          cognitoUser.sendMFACode(verificationCode, this);
        },
      });
    } catch (e) {
      console.error(e);

      clearElement();
      // update response
      ClarisIdTokenResponseElement.value = JSON.stringify(e);

      return new CustomResponse(false, null, e, 'An unexpected error has occurred');
    }
  }

  async function refreshClarisIdToken() {
    console.log('refreshClarisIdToken');
    try {
      const refreshToken = RefreshTokenElement.value;
      const body = {
        'ClientId': POOL_DATA.ClientId,
        'AuthFlow': 'REFRESH_TOKEN_AUTH',
        'AuthParameters': {
          'REFRESH_TOKEN': refreshToken,
          'DEVICE_KEY': null,
        },
      };

      const url = new URL(`https://cognito-idp.us-west-2.amazonaws.com/`);
      const res = await fetch(url, {
        method: 'POST',
        headers: {
          authority: 'cognito-idp.us-west-2.amazonaws.com',
          'content-type': 'application/x-amz-json-1.1',
          'x-amz-target': 'AWSCognitoIdentityProviderService.InitiateAuth',
        },
        body: JSON.stringify(body),
      });
      const refreshResponse = await res.json();
      console.log('refreshResponse');
      console.log(refreshResponse);

      if (res.status !== 200) {
        // clear token
        ClarisIdTokenResponseElement.value = JSON.stringify(refreshResponse);
        AccessTokenElement.value = '';
        ClarisIdTokenElement.value = '';

        // clear curl command
        CurlGlobalSignOutElement.value = '';
      } else {
        // update token
        ClarisIdTokenResponseElement.value = JSON.stringify(refreshResponse);
        const accessToken = refreshResponse.AuthenticationResult.AccessToken;
        AccessTokenElement.value = accessToken;
        ClarisIdTokenElement.value = refreshResponse.AuthenticationResult.IdToken;

        // update curl command
        CurlGlobalSignOutElement.value = curlGlobalSignOutTemplate(accessToken);
      }

      return new CustomResponse(true, refreshResponse, null, null);
    } catch (e) {
      console.error(e);

      // clear token
      ClarisIdTokenResponseElement.value = JSON.stringify(e);
      AccessTokenElement.value = '';
      ClarisIdTokenElement.value = '';

      // clear curl command
      CurlGlobalSignOutElement.value = '';

      return new CustomResponse(false, null, e, 'An unexpected error has occurred');
    }
  }

  async function globalSignOut() {
    console.log('globalSignOut');
    try {
      const accessToken = AccessTokenElement.value;
      const body = {
        'AccessToken': accessToken,
      };

      const url = new URL(`https://cognito-idp.us-west-2.amazonaws.com/`);
      const res = await fetch(url, {
        method: 'POST',
        headers: {
          authority: 'cognito-idp.us-west-2.amazonaws.com',
          'content-type': 'application/x-amz-json-1.1',
          'x-amz-target': 'AWSCognitoIdentityProviderService.GlobalSignOut',
        },
        body: JSON.stringify(body),
      });
      const signOutResponse = await res.json();
      console.log('signOutResponse');
      console.log(signOutResponse);

      return new CustomResponse(true, signOutResponse, null, null);
    } catch (e) {
      console.error(e);
      return new CustomResponse(false, null, e, 'An unexpected error has occurred');
    }
  }

</script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js" integrity="sha384-geWF76RCwLtnZ8qwWowPQNguL3RmwHVBC9FhGdlKrxdiJJigb/j/68SIy3Te4Bkz" crossorigin="anonymous"></script>
</body>
</html>
Claris ID Token生成アプリの全ソースコード(index.html)

Claris ID Tokenに関するよくある誤解

Claris ID Tokenについて調べると、以下のような課題があるといった記事を見かけることがありますが、実際にはこれらの課題は解決可能です。
古いコードを元に作業をされたり、Amazon CognitoのAPIを見ていないことが原因です。

  • MFA認証(SMSで通知されるコード)に対応していないと誤解されている。
  • トークン更新時にもSDKが必須となり、FileMakerからは扱いづらいと思われている。

Claris ID Token生成アプリ内にも、Claris ID Tokenを更新するための、curlオプションを表示するようにしました。

つまり、Claris ID Refresh Tokenの初回取得時は、手動で取得作業が必要になりますが、取得した Claris ID Refresh Tokenが有効な1年間の間は、FileMakerの「URL から挿入」スクリプトステップのcURLオプションを利用して、Claris ID Tokenを更新し続けることができます。

現在、Claris ID Tokenの仕組みや関連用語(アクセストークン、リフレッシュトークン等)を詳しく解説する記事も執筆中です。公開まで今しばらくお待ちください。

小巻旭洋のプロフィール写真

小巻 旭洋

2014年からフリーランスとして活動し、2016年に株式会社フルーデンスを設立する。FileMaker開発歴は約10年。多数の企業システム構築を手がけ、特にデータベース設計と、JavaScript連携、Web連携、パフォーマンス最適化を専門としています。2025年から、Web業務システム・アプリ開発をメインに開発をしています。