【Firebase】自作API(API Gateway + Lambda)でFirebaseのAuthenticationを利用する方法

# 概要
**Firebase Authentication**に登録されているどのアカウントか判断し、なにか処理をおこなうAPIを**API Gateway(AWS)**と**lambda(AWS)**を利用して作成します。

※_せっかくなのでlambda(AWS)では新機能のレイヤーの使い方も説明しちゃおうと思います。_

# 対象者
Firebase Authenticationの機能を知っている人
Firebase Authenticationを利用したAPIを作成したい人

# Firebase Admin SDKについて
自作APIでアカウントの判断をするためには、`firebase`ではなく`firebase-admin`が必要になります。
`firebases-admin`では、アカウントが発行するトークンをもとに、どのアカウントか判断できる機能があります。この機能を利用することで自作APIを作成していきます。

# Firebaseプロジェクトの作成

Firebase consoleを開き、新規プロジェクトを作成します。
開発のAuthenticationを開き、Googleを有効にします。

<img width="655" alt="キャプチャ.JPG" src="https://qiita-image-store.s3.amazonaws.com/0/189146/c2093f55-62cd-9584-badc-e709eee07f9b.jpeg">

プロジェクトの設定のサービスアカウント画面を開きます。
Project Overviewの右の馬車アイコンから、プロジェクトの設定を選択し、サービスアカウントを開きましょう。

<img width="761" alt="キャプチャ.JPG" src="https://qiita-image-store.s3.amazonaws.com/0/189146/681da740-9633-e0c0-116f-dfc0b0e6b834.jpeg">

Firebase Admin SDKの新しい秘密鍵の生成から**jsonファイル**をダウンロードしましょう。**databaseURL**の値は後で使うのでメモしておきます。

<img width="577" alt="キャプチャ.JPG" src="https://qiita-image-store.s3.amazonaws.com/0/189146/9d098855-93c0-4340-e1d9-76da85fb9aeb.jpeg">

firebase consoleですることは終わりです。

# APIの作成

### イメージ

**firebase-sdk**は**Token**の発行、
**firebase-admin-sdk**は**Token**をチェックします。

<img width="443" alt="キャプチャ.JPG" src="https://qiita-image-store.s3.amazonaws.com/0/189146/3d9f1cf4-901e-8015-035a-c84f5145f29d.jpeg">

### API環境の準備(AWS)

ローカル環境にAPIを作るのもいいのですが、最終的にはAWSを利用したり、Azureを利用してAPIを公開していくものだと思っているので、いきなりAWS上に環境作ります。

#### Layerの作成
今回は`firebase-admin`を使用します。
`firebase-admin`をLambdaで利用できるようにするため、**Layer**を作成してみましょう。**Layer**はソースコードの共有が可能になる機能です。Lambda関数内で**Layer**を読み込むことで`firebase-admin`が即座に利用可能になります。

PC上に**nodejs**という名前でフォルダを作成し、フォルダ内で`firebase-admin`をインストールします。

※Layerは読み込むファイルのパスが決まっているため、フォルダ構成は**nodejs/node_modules/**にしなければなりません。ほかの構成でも読み込めますが詳しくは[こちら](https://docs.aws.amazon.com/lambda/latest/dg/configuration-layers.html)をご覧ください。

| library        | version | 
|:---------------|:--------|
| firebase-admin | v6.3.0  |

```
npm install firebase-admin
```

AWSマネジメントコンソール画面でLambdaのLayersを選択し、
新規**Layer**をランタイム Node.js 8.10で、先ほど作成したnodejsフォルダをzip化してS3経由でアップロードし、作成します。

※S3経由でのアプロード方法がわからない方はGoogleで調べてください。

#### Lambda関数の作成

AWSマネジメントコンソール画面でLambdaの関数を選択し、
新規関数をランタイム Node.js 8.10で作成します。

Layersを選択し、レイヤーの追加から先ほど作成した**Layer**を追加します。

<img width="836" alt="キャプチャ.png" src="https://qiita-image-store.s3.amazonaws.com/0/189146/e0e56abd-9a5a-a735-1c0d-17161a2b0b37.png">

次に、Lambda関数本体を書いていきます。

```js:index.js
const admin = require('firebase-admin');
const firebaseDatabaseEndpoint = process.env.FIREBASE_DATABASE_ENDPOINT;

const serviceAccount = require('./admin.json');

admin.initializeApp({
    credential: admin.credential.cert(serviceAccount),
    databaseURL: firebaseDatabaseEndpoint,
})

exports.handler = async (event, context, callback) => {
    // firebase uid 検証
    const uid = await admin.auth().verifyIdToken(event.token)
        .then((decodedToken) => {
            return decodedToken.uid;
        })
        .catch((error) => {
            console.log(error);
            return null;
        })
   
    if(uid) {
        // 検証に成功している場合実行する処理
        console.log('hello firebase');
        
        const response = {
            statusCode: 200,
            body: { 
                message: 'verify token success.',
                uid: uid
            },
        };
        
        return response;
    }
    else {
        const response = {
            statusCode: 500,
            body: {
                message: 'verify token failure.'
            },
        };
        
        return response;
    }
};
```

firebaseのプロジェクトの設定をします。

`firebaseDatabaseEndpoint`には、さきほどメモしておいた**databaseURL**を環境変数で設定します。`serviceAccount`には、さきほどダウンロードした秘密鍵を**admin.json**としてアップロードし読み込みます。

```js
const admin = require('firebase-admin');
const firebaseDatabaseEndpoint = process.env.FIREBASE_DATABASE_ENDPOINT;

const serviceAccount = require('./admin.json');

admin.initializeApp({
    credential: admin.credential.cert(serviceAccount),
    databaseURL: firebaseDatabaseEndpoint,
})

```

<img width="810" alt="キャプチャ.JPG" src="https://qiita-image-store.s3.amazonaws.com/0/189146/fcf8a082-9216-6ef9-37ea-33f97db6a4d6.jpeg">

`firebase-admin`の`auth`の`verifyIdToken`関数で**Token**の検証を行います。検証に成功した場合は、アカウント毎に設定されている**uid**を獲得できます。

```js
// firebase uid 検証
const uid = await admin.auth().verifyIdToken(event.token)
    .then((decodedToken) => {
        return decodedToken.uid;
    })
    .catch((error) => {
        console.log(error);
        return null;
    })

```

次に、**uid**の有無で処理を分けて、最後にレスポンスを返すようにします。

```js
if(uid) {
    // 検証に成功している場合実行する処理
    console.log('hello firebase');
        
    const response = {
        statusCode: 200,
        body: { 
            message: 'verify token success.',
            uid: uid
        },
    };
        
    return response;
}
else {
    const response = {
        statusCode: 500,
        body: {
            message: 'verify token failure.'
        },
    };
        
    return response;
}
```

これでLambdaは完成です。

#### API Gatewayの作成

AWSマネジメントコンソール画面でAPI Gatewayの新規APIを作成します。作成したAPIのリソースでアクション→メソッドの作成よりGETを追加します。

統合タイプはLambda 関数で、Lambda関数の欄は先ほど作成した関数を入力します。

<img width="580" alt="キャプチャ.JPG" src="https://qiita-image-store.s3.amazonaws.com/0/189146/d53e2915-36c8-31db-b084-b48a64b51d5d.jpeg">

はじめに、Firebaseの**Token**を送信するカスタムヘッダーを設定します。

メソッドリクエストを選択し、HTTPリクエストヘッダーに**X-FIREBASE-TOKEN**を追加、リクエストの検証を「**クエリ文字列パラメータおよびヘッダーの検証**」に変更します。

<img width="646" alt="キャプチャ.JPG" src="https://qiita-image-store.s3.amazonaws.com/0/189146/178d25c4-b50f-4558-fb7a-3affec7aec1e.jpeg">

次に、ヘッダー**のX-FIREBASE-TOKEN**をLambda関数で受け取れるように設定します。

統合リクエストを選択し、マッピングテンプレートのリクエスト本文のパススルーを「**テンプレートが定義されていない場合**」に変更します。
**application/json**でマッピングテンプレートを追加します。
Lambda関数内で`event.token`で**X-FIREBASE-TOKEN**の値を取得できるようになります。

```json:application/json
{
    "token": "$input.params('X-FIREBASE-TOKEN')"
}
```

<img width="630" alt="キャプチャ.JPG" src="https://qiita-image-store.s3.amazonaws.com/0/189146/80843fa0-ce3d-03db-44c7-5192034c0ede.jpeg">

リソースでアクション→APIのデプロイより、ステージを新しく作成しAPIを完成させましょう。

<img width="495" alt="キャプチャ.JPG" src="https://qiita-image-store.s3.amazonaws.com/0/189146/f51f2d07-1fed-167b-f2ea-cb5d4dc9dfb9.jpeg">

APIの作成は完了です。

# APIの利用

クライエントサイドは情報が溢れているため端折らせてもらいます。
※_React Firebase SNS認証等で調べればたくさん出てきます。_

`firebase`と`axios`をインストールします。

```
npm install firebase
npm install axios
```

| library 　　| version | 
|:---------|:--------|
| firebase | v5.7.0  |
| axios    | v0.18.0 |

### configファイルの準備

Firebase consoleを開き、プロジェクトを開きます。
開発のAuthenticationを開き、ウェブ設定を押して、**config**を確認します。

<img width="1113" alt="スクリーンショット 2018-12-15 1.31.13.png" src="https://qiita-image-store.s3.amazonaws.com/0/189146/9c3f3dbf-b033-08db-e8bf-fa97111535f5.png">

configファイルを作成します。

```js:config.js
export const config = {
    apiKey: "xxxx",
    authDomain: "xxxx",
    databaseURL: "xxxx",
    projectId: "xxxx",
    storageBucket: "xxxx",
    messagingSenderId: "xxxx"
  };
```


### ログインの例

GoogleとFacebookのログインの仕方を書いています。

```js
import * as firebase from 'firebase'
import { config } from './config' // config読み込み
firebase.initializeApp(config)

const googleProvider = new firebase.auth.GoogleAuthProvider()
const facebookProvider = new firebase.auth.FacebookAuthProvider()

const auth = firebase.auth()

// Google Login
const authGoogle = () => {
    auth.signInWithRedirect(googleProvider)
}

// Facebook Login
const authFacebook = () => {
    auth.signInWithRedirect(facebookProvider)
}

```

### Token取得の例

ログイン時のアカウントの**Token**の取得方法を書いています。

```js
import * as firebase from 'firebase'
import { config } from './config' // config読み込み
firebase.initializeApp(config)

const auth = firebase.auth()

auth.currentUser.getIdToken(true).then((idToken) => {
    const token = idToken
    console.log(token)
})
.catch(err => {
    console.log(err)
})

```

### リクエスト送信の例

X-FIREBASE-TOKENに先ほど取得した、**Token**を設置し、GETリクエストを送信します。

```js
import axios from 'axios'

const lambdaEndpoint = 'xxxx'
const token = 'xxxx'

axios.get(lambdaEndpoint, {} ,{
    headers: {
        'X-FIREBASE-TOKEN': token
    }
})
.then(res => {
    console.log(res.data)
})
.catch(err => {
    console.log(err)
})

```

これらのコードを利用して、Reactアプリでもサクッと作って試してみましょう。

# 最後に

firebaseのSNS認証機能はかなり強力で、爆速でアプリ開発できます。
ただアプリ開発おいて、DBは自前でもちたい等、firebaseのサービスだけで完結できるとは限らないです。
そのため、今回はfirebaseのサービス以外と連携させるためのAPIの作成方法を紹介させていただきました。