M2MのアクセストークンをLambdaでキャッシュする #Auth0 #Lambda

2020.05.07

APIからAPIなど、サービス間の認証をしたい場合はAuth0のM2M認証が便利です。
また、有効期限内のアクセストークンをキャッシュすれば、処理効率も上がります。
今回はAuth0のM2Mのアクセストークンをサーバー側(Lambda)でキャッシュする方法を検討してみます。

Auth0のM2MでAPI間の認証を実現する方法

Auth0のM2M認証でサービス間の認証を担う方法はこちらの記事で紹介されています。

ここでも同じ設定方法で進めていきます。
M2Mアプリケーションの作成までできている前提で進めるので、Auth0の設定は上記を参考にしてください。

DBよりLambdaのインメモリに保存した方がいい理由

DynamoDBに保存する場合、TTLに有効期限を設定して自動削除を実現できますが、アクセストークンの取得の代わりにDynamoDBへのリクエストが発生してしまうため処理コストは変わらず、キャッシュするメリットが薄くなってしまいます。
ということでLambdaのインメモリに保存しましょう。

Lambdaのインメモリキャッシュ

こちらの記事にもあるように、Lambdaの仕様としてグローバルスコープで定義された値はインスタンスが生きている限り再利用されます。

構築

シークレット情報の保存

M2Mのアプリケーション作成時に発行される Client ID Client Secret はSecret Managerに保存しましょう。

Secrets Managerのコンソールを開いて、 Store a new secret をクリックします。

  1. typeは Other type of secrets を選択して作成したM2MアプリケーションのクライアントIDとシークレットをそれぞれ入力します。

  • CLIENT_ID
  • CLIENT_SECRET
  1. シークレット名を入力します。

ここでは Sample/devio とします。

他の設定項目はよしなに変えてください。
設定は下記のようにしてシークレットを作成します。

実装コード

実際にアクセストークンを取得してキャッシュする処理をNodeで実装していきます。
アクセストークンの取得リクエストにはnode-fetchを使っています。

index.js

const AWS = require('aws-sdk');
const SecretsManager = new AWS.SecretsManager();
const fetch = require('node-fetch');

const accessToken = undefined;

const newAccessToken = async () => {
    console.log('Get a new access token.');
    // シークレットの取得
    const secretValue = await SecretsManager.getSecretValue({
        SecretId: 'Sample/devio'
    }).promise();
    const secret = JSON.parse(secretValue.SecretString);

    const url = 'https://{AUTH0_TENANT_NAME}.auth0.com/oauth/token';
    const headers = { 'Content-Type': 'application/json' };
    const audience = 'https://api.example.com/v1';
    const payload = {
        client_id: secret.CLIENT_ID,
        client_secret: secret.CLIENT_SECRET,
        audience,
        grant_type: 'client_credentials'
    };
    const body = JSON.stringify(payload);
    const timestamp = Math.floor(new Date().getTime() / 1000);
    const response = await fetch(url, { method: 'POST', body, headers});
    const result = await response.json();
    this.accessToken = {
        access_token: result.access_token,
        expires_in: timestamp + result.expires_in,
        token_type: result.token_type
    };
    return result.access_token;
};

const getAccessToken = async () => {
    if (!this.accessToken) return await newAccessToken(); // キャッシュ確認
    const timestamp = Math.floor(new Date().getTime() / 1000);
    if (this.accessToken.expires_in < timestamp) return await newAccessToken(); //有効期限確認
    console.log('Use cache access token.');
    return this.accessToken.access_token;
};

exports.handler = async (event) => {
    const result = await getAccessToken();
    return result;
};
  • getAccessToken でキャッシュ確認&有効期限のチェックを行う
  • キャッシュ確認or有効期限のチェックで弾かれれば、newAccessToken でアクセストークンをAuth0に取得しにいく

キャッシュの確認

関数 getAccessToken() の中でキャッシュの確認をしています。

if (!this.accessToken) return await newAccessToken();
if (this.accessToken.expires_in < timestamp) return await newAccessToken();

キャッシュがない、もしくは有効期限が切れている場合は newAccessToken() で取得します

トークン取得

リクエストのURLは以下になります。自身のAuth0テナントを入れてください。

const url = 'https://{AUTH0_TENANT_NAME}.auth0.com/oauth/token';

audienceは実際にアクセスするAPIのエンドポイントです。
M2Mアプリケーションに設定したAPIsの Identifier と一致している必要があります。

const audience = 'https://api.example.com/v1';

アクセストークンの形式

M2M認証は実際にはOAuthの認証になるのでJWT形式のアクセストークンが返ってきます。
レスポンスに含まれるexpires_inは経過時間なので、期限のタイムスタンプに変更しておきます。

this.accessToken = {
        access_token: result.access_token,
        expires_in: timestamp + result.expires_in,
        token_type: result.token_type
    };

有効期限はデフォルトだと86400(sec)です。
Auth0のAPIsの設定項目で Token Expiration (Seconds) をいじれば有効期限を変更できます。

最低限の処理のみの実装となっていますが、キャッシュを効かせることができました。