LambdaオーソライザーでLINEミニアプリ用APIのアクセス制御をする #LINE_API

今回はLINEミニアプリ(LIFFアプリ)用のAPIをAPI GatewayのLambdaオーソライザーを使ってアクセス制御を実現します。
2020.06.27

はじめに

こんにちは、中村です。

LIFFアプリおよびサーバーでユーザー情報を使用するにてLINE公式アナウンスされておりますが、LINEミニアプリ(LIFFアプリ)においてはフロントで取得したIDトークンやアクセストークンを元にAPIを操作することが想定されます。今回はAPI GatewayのLambdaオーソライザーを使ってAPIへのアクセス制御を実現します。

LINEミニアプリ用にLambdaオーソライザーでアクセス制御する

今回は下記のようにAPI GatewayとLambdaを使ってサーバレスAPIにします。

API検証にあたりIDトークンを取得するLINEミニアプリ(LIFFアプリ)が必要です。LIFFに登録するためには、HTTPSのエンドポイントを用意してください。またIDトークンを利用するためScopeでopenidを選択します。LIFF SDKの読み込み・下記のコードの実行することでLIFFアプリ内でIDトークンを取得することができます。

取得するためのコード

liff.init({
    liffId: 'xxxxxxxxxx-xxxxxxx'
}).then(() => {
    if (!liff.isLoggedIn()) {
        liff.login(); // External browser support
    }
    const idToken = liff.getIDToken();
    // console.log() or display id token.

}).catch((err) => {
    alert(err.code, err.message);
})

Lambda

構成図を見ていただくと2つのLambda関数がありますが、今回は認証の部分の話をしたいのでLambdaAuthのコードについて説明します。LambdaBackendは、LambdaのHelloWorld関数を利用します。Lambdaのメイン処理でAuthorizationヘッダーのIDトークンを取得しIDトークンを検証するAPIを実行します。その結果に応じてポリシーAllowDenyを分岐します。

Lambda環境変数 - CLIENT_IDは、LINEログインのチャネルIDを入力してください。

const fetch = require('node-fetch');

exports.handler = async (event) => {
    console.log('Event: ' + JSON.stringify(event, null, 2));
    /**
     * Get id token from `Bearer xxxxxxxxxxxxxxxxxx`
     */
    const [, idToken] = event.headers['Authorization'].split(' ');

    /**
     * Verify id token using social API v2.1 and create policy
     */
    return await fetch('https://api.line.me/oauth2/v2.1/verify', {
        method: "POST",
        headers: {
            "Content-Type": "application/x-www-form-urlencoded"
        },
        body: `id_token=${idToken}&client_id=${process.env.CLIENT_ID}`
    })
    .then((res) => res.json())
    .then((json) => {
        console.log('Social API Response: ' + JSON.stringify(json, null, 2));
        if (json.error) {
            return generatePolicy('user', 'Deny', event.methodArn);
        } else {
            return generatePolicy('user', 'Allow', event.methodArn, json.sub);
        }
    });

};

const generatePolicy = (principalId, effect, resource, userId) => {
    let authReponse = {};
    authReponse.principalId = principalId;

    if (effect && resource) {
        let policyDocument = {};
        policyDocument.Version = '2012-10-17';
        policyDocument.Statement = [];
        let statementOne = {};
        statementOne.Action = 'execute-api:Invoke';
        statementOne.Effect = effect;
        statementOne.Resource = resource;
        policyDocument.Statement.push(statementOne);
        authReponse.policyDocument = policyDocument;
    }

    if (userId) authReponse.context = {
        userId: userId
    };
    
    console.log('Policy: ' + JSON.stringify(authReponse, null, 2));
    return authReponse;
};

Lambda2つを作成したら、API Gatewayの設定に移ります。

API Gateway

API Gatewayにアクセスして、APIの作成をクリックします。REST APIを構築しましょう。下記の設定を入力しAPIの作成をクリックします。

項目名
プロトコルを選択する REST
新しいAPIの作成 新しいAPI
API名 任意

API作成が成功したらサイドバーのオーソライザーにアクセスし、新しいオーソライザーの作成をクリックします。下記の設定でオーソライザーを作成してください。

項目名
名前 任意
タイプ Lambda
Lambda関数 LambdaAuth用の関数
Lambdaイベントペイロード リクエスト
IDソース ヘッダー・Authorization
認可のキャッシュ 無効

作成が完了すると、登録内容としては以下のようになると思います。テストをクリックして実際に正常に動作するか確認しましょう。

AuthorizationヘッダーがBearer ${id_token}でリクエストされる想定ですので、下記のように指定してください。

検証成功・失敗は下記の画像を参考にしてください。成功の場合はLambdaBackendにリクエストがされます。

検証失敗されると、LambdaBackendへはアクセスされず403が返されます。

ここまで確認ができたらあとはLambdaBackendをメソッドに登録します。サイドバーからリソースへアクセスしてください。今回はルートにPOSTでリクエストするメソッドを作成します。アクションプルダウンからメソッドの作成をクリックします。リソースパスの下にメソッドのプルダウンが表示されますので、POSTを選択しチェックアイコンをクリックします。

作成が完了すると、統合ポイント(バックエンド)の登録に入ります。下記の設定を入力して保存をクリックします。保存しようとするとAPI GatewayからLambdaを実行する権限を付与する旨のメッセージが表示されるのでOKで権限を付与します。

項目名
統合タイプ Lambda関数
Lambdaプロキシ統合の使用 チェックする
Lambdaリージョン LambdaBackendのリージョン
Lambda関数 LambdaBackendを選択

ここまで完了すると、下記の画面になりますのでオーソライザーの設定をします。メソッドリクエストをクリックしてください。

認可の編集アイコンをタップして先ほど作成したオーソライザーを選択し保存します。

さて最後にAPIをデプロイして完成です。アクションプルダウンからAPIのデプロイをクリックします。デプロイするステージ名を入力してデプロイをクリックします。デプロイが完了すると、ステージへ遷移しAPIのエンドポイントが確認できます。こちらをメモしておいてください。

テスト

デプロイが完了したので、Postmanを使ってAPIにアクセスしてみます。最初に用意したLIFFアプリからIDトークンを取得して実行してみます。

IDトークンが検証できた場合はバックエンドまでリクエストされ200が返され、失敗時は403が返されました。IDトークンによりAPIアクセス制御ができることが確認できました。

まとめ

今回は、LINEミニアプリ(LIFFアプリ)で利用するAPIの認証をAPI GatewayのLambdaオーソライザーを使って実現しました。現時点でHTTP APIのJWTオーソライザーではまだES256アルゴリズムが未対応のため利用できませんでしたが、対応されればこのソースを書くこともなくなるでしょう。LINEミニアプリ(LIFFアプリ)はサーバレスで!