顔認証のクラウドサービスMercury Cloudでさらに遊んでみる – 2要素認証に1対1比較を用いる –

2021.11.10

Mercury Cloudの顔比較APIを2要素認証として使ってみる記事です。

顔比較APIは2つの画像(バイナリ)をパラメータとして送信して比較してくれます。

1対1比較のケースだと、比較元の画像データは別のデータベースなどに持っておいて、アプリ側で画像をアップロードしたり、 カメラで自分の顔を写してそのデータをバイナリにして比較する といったことが考えられます。

社内アプリケーションで使えそうな気がしますね。 社員証に使っている顔の画像、従業員のマスターデータに紐づいている顔の画像などが元画像として使えるのではないでしょうか。

本記事では一部をモック化して、実際に2要素認証で顔比較APIを扱ってみようと思います。

認証にはAuth0を使います。

Auth0は認証フローの途中で別ページにリダイレクトすることが可能なので、その機能を使って顔比較をしてみます。

前提

大まかな流れは上記の通りです。

ログインが必要なアプリ -> Auth0 Actions -> 顔比較用のアプリ -> Mercury Cloudの顔比較API といった順にアクセスが流れるように作ってみます。

Auth0を使用したWebアプリケーションは準備しておきます。

※ Auth0でログインするといった説明は割愛

顔比較のアプリにダイレクトするAuth0 Actions

Auth0での認証途中でリダイレクトするようなActionsを作ります。

例)

onExecutePostLoginに別のアプリにリダイレクトするコードを書きます。

payloadにユーザーを特定できる情報を送っていますが、これは顔比較をするアプリ内でユーザーの元画像を取得するためのキーとして使用する想定にしています。

exports.onExecutePostLogin = async (event, api) => {
  const token = api.redirect.encodeToken({
    secret: event.secrets.REDIRECT_SECRET, //シークレット文字列
    expiresInSeconds: 600, 
    payload: {
      // Custom claims to be added to the token
      email: event.user.email,
      //externalUserId: event.user.app_metadata.feature_id, // 外部IDなどをapp_metadataに入れておけば使えそう
    },
  });

  // リダイレクト
  // session_tokenという名前で作成したトークンをアプリに渡している
  api.redirect.sendUserTo("<<顔比較アプリのURL>>", {
    query: { session_token: token }
  });
};

顔比較のアプリ

このアプリでは、Auth0から渡ってきた情報を使ってユーザーの元画像を取得したり、 カメラアプリにアクセスして顔画像データを取得したり、端末から顔画像をアップロードしてデータを取得します。

イメージ)

その後、顔比較APIへのリクエストを行い、顔比較結果を作ってAuth0にアクセスを返します。

顔比較APIへのリクエスト例)

Mercury Cloudを申し込んだときにもらえる情報を使って認証ヘッダーを作成し、比較APIにアクセスしませう。

// 比較画像データ
const data = JSON.stringify({
    "one": {
      "data": org_face_image // base64エンコードした元画像
    },
    "another": {
      "data": compare_face_image // base64エンコードした顔比較画像
    }
  });

// 
const config = {
    method: 'post',
    url: 'https://<<API_URL>>/openapi/face/v1/<<APP_ID>>/compare',
    headers: { 
      'x-date': x_date_time_string, // RFC-7231形式でのUTC日付と時刻
      'Authorization': authorization, // API認証ヘッダー
      'Content-Type': 'application/json'
    },
    data : data
};

比較APIにアクセスして、Auth0に返すサンプル)

// Auth0に返す情報を作成する
const is_compare = false
const returnToken = jwt.sign(
  {
              sub: "<<Auth0ユーザーのID>>",
              exp: 1646444617, //expiresInSecondsパラメータで指定された有効期限(秒単位
              iss: "<<Auth0ユーザーのID>>",
              state: "<<Auth0から発行されたstateパラメーター>>",
              is_compare: is_compare, // 比較結果
              score: 0, // 比較スコア  
  },
  "<<シークレット文字列>>",
  { algorithm: "HS256" }
);

axios(config)
  .then(response => {
      // ここにAPIの実行結果を使った処理を書く
            〜〜〜〜〜〜〜
            〜〜〜〜〜〜〜
            〜〜〜〜〜〜〜
  }).catch(error => {
    console.log("error")
              // ここにAPIの実行結果を使った処理を書く
            〜〜〜〜〜〜〜
            〜〜〜〜〜〜〜
            〜〜〜〜〜〜〜
  }).finally(function() { 
     // Auth0のcontinueエンドポイントにリダイレクト
      // face_compareというパラメータを使って情報を渡している
     reply.redirect(auth0_continue + "?state=" + request.body.state + "&face_compare=" + returnToken)
     return
   });

Auth0に戻ってきた時のActions

Actions利用時、 外部サイトからAuth0に戻ってきたときは,ActionsのonContinuePostLoginが実行されます

サンプル)

顔比較の結果と、スコアを受け取り、最終的にスコア値をid-tokenに格納した例です。

exports.onContinuePostLogin = async (event, api) => {
  const payload = api.redirect.validateToken({
    secret: event.secrets.REDIRECT_SECRET, //シークレット文字列
    tokenParameterName: 'face_compare', // 返却情報を受け取るためのパラメータ名
  });

  if(payload.is_compare == false) {
    api.access.deny("顔認証に失敗しました");
  }

  // use the data encoded in the token, such as: 
  const namespace = 'https://sample-app';
  api.idToken.setCustomClaim(`${namespace}/score`, payload.score);
};

一連の流れを確認

ログインから顔認証をし、id-tokenにスコア値を格納したところまでを録画しました

最後に

顔比較APIを使ってログインの2要素認証に使ってみました。

今回のように元画像(社員証の顔写真など)にアクセスできるような社内向けシステムであれば比較的簡単に組み込めるのではないでしょうか?

次回は1対1ではなく、1対Nで顔検索を行い、2要素認証として使えるかどうか試す予定でいます。