JSONなしでGoogleAPIやFirebaseに認証する方法 ～CloudFunctions サーバーレスでの認証～

# Google系のサービスの認証は大体JSON！
**2023/9/22追記、9/28修正あり**

**※Pythonでの紹介となります**


プログラムからGoogleSheetの操作をするSheetsAPIやNoSQLでデータが保持できるFirebaseなどGoogle系のサービスに接続する場合、秘密鍵が入った[JSONファイル](https://products.sint.co.jp/topsic/blog/json)をダウンロードして、その情報から接続を行います。

https://reffect.co.jp/html/google-api-account/


これは慣れると便利なのですがCloudFunctionsからGoogleSheetを読み取るものを作ろうとしたとき気づきました…

## ~~ファイルを置ける場所がない…~~

~~CloudFunctionsなどサーバレスのサービスはどんな小さなファイルもサーバに置いておくことができません。またサーバがある場合でもこのようなファイルを置いておくのは、セキュリティ上なんか不安…なんて方もいるかと思います。~~

**失礼しました。新規でファイルを作成すればmain.pyと同じディレクトリのファイルとして扱うことができます。ただファイルを置きたくない方のために以下の方法を残しておきます。**
<br>

## JSONファイルを使わずに認証をする方法

そこで…
### 環境変数から取得して、その場でJSONファイル作ればいいじゃん！

ってのを思いつき、プログラマ心をくすぐられたのですが、そんなことする必要はありませんでした。

# SheetsAPI：from_json_keyfile_dictを利用

結果から言うとJSONの中身の一部をdict型の変数に入れて、それをセットすればいいみたいです。

```python:Google推奨（修正前）
scope = ['https://spreadsheets.google.com/feeds','https://www.googleapis.com/auth/drive']
credentials = ServiceAccountCredentials.from_json_keyfile_name('auth/charged.json', scope)
gc = gspread.authorize(credentials)
```

```python:今回の修正後
scope = ['https://spreadsheets.google.com/feeds','https://www.googleapis.com/auth/drive']
credentials = ServiceAccountCredentials.from_json_keyfile_dict(charged_dict, scope)
gc = gspread.authorize(credentials)
```

わかりづらいですが**from_json_keyfile_name**が**from_json_keyfile_dict**になっています。
で**charged_dict**というdict型の変数に（名前はなんでもよい）キーたちをセットし引数にしてやればいいとのことです。

charged_dictへのセットですが、そのダウンロードしたJSONってのはこんな感じなので

```json:charged.json ※修正あり
{
  "type": "service_account",
  "project_id": "charged-polymer-376***",
  "private_key_id": "f822b1********************",
  "private_key": "-----BEGIN PRIVATE KEY----- MIIEv******************,
  "client_email": "google-sheet-263@charged-polymer-376204.iam.gserviceaccount.com",
  "client_id": "115324932356144364298",
  "auth_uri": "https://accounts.google.com/o/oauth2/auth",
  "token_uri": "https://oauth2.googleapis.com/token",
  "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
  "client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/google-shee",
  "universe_domain": "googleapis.com"
}

```

このうち一部だけを環境変数にセットします。環境変数名はなんでもいいです。
（キー保存の常套手段として環境変数に入れておきましたが、中身が使えればなんでもいいです）

```python:環境変数へのセット
os.environ["GS_TYPE"] = "service_account"
os.environ["GS_PRIVATE_KEY"] = "-----BEGIN PRIVATE KEY-----\nMIIEm3YZhOzcW\nIV9qjoE4YuzXrs0Zdzq5Vn0=\n-----END PRIVATE KEY-----\n"
os.environ["GS_CLIENT_EMAIL"] = "google-sheet-263@charged-polymer-3******.iam.gserviceaccount.com"
os.environ["GS_PRIVATE_KEY_ID"] = "f822b1207797f********************"
os.environ["GS_CLIENT_ID"] = "11532493235614*******************"
```
**※CloudFunctionsの場合は作成ウィザードの"ランタイム環境変数"でキーと値をセットして下さい**


JSONの全てのキーが必要ではなく、SheetsAPI(gspreadでの利用)では
**type、private_key、client_email、private_key_id、client_id**
の5つのキーだけです。
ただこれはSCOPEにもよると思いますので、試しながらやってみてください。
（キーが足りないとエラーになります。）
後はdict型の変数(charged_dict)にセット。

```python:
charged_dict = {
    "type":os.environ["GS_TYPE"],
    "private_key":os.environ["GS_PRIVATE_KEY"],
    "client_email":os.environ["GS_CLIENT_EMAIL"],
    "private_key_id":os.environ["GS_PRIVATE_KEY_ID"],
    "client_id":os.environ["GS_CLIENT_ID"]
}
```

これで**from_json_keyfile_dict(charged_dict, scope)** により接続が行えるようになります。

「まぁそりゃそうだろ」
って言う方もいるかと思いますが、私は目から鱗でした。

# Firebase：dictをぶっこんで大丈夫
Firebase(Firestore)の認証は

```python:
cred = credentials.Certificate('auth/test-01.json')
app = firebase_admin.initialize_app(cred)
db = firestore.client()
```

こんな感じでCertificateの引数にJSONのパスを入れるのですが、これはdictを入れても勝手に判断してくれるようです。![2023-09-07 222202.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3549938/471e0992-4f33-47c5-473f-3a2bb8df82ac.png)
なのでSheetsAPIと同じように環境変数へJSONの中身をセットしておき、認証時dict型変数に入れてCertificateの引数にします。ただSheetsAPIとキーが異なるので注意してください。

```python:環境変数へのセット
os.environ["FB_TYPE"] = "service_account"
os.environ["FB_PROJECT_ID"] = "test-01-fc****"
os.environ["FB_PRIVATE_KEY"] = "-----BEGIN PRIVATE KEY-----\nMIIEvQIBADANB***************E KEY-----\n"
os.environ["FB_CLIENT_EMAIL"] = "firebase-adminsdk-******@test-01-.iam.gserviceaccount.com"
os.environ["FB_TOKEN_URI"] = "https://oauth2.googleapis.com/token"
```

```python:dictへのセット
charged_dict = {
    "type":os.environ["FB_TYPE"],
    "project_id":os.environ["FB_PROJECT_ID"],
    "private_key":os.environ["FB_PRIVATE_KEY"],
    "client_email":os.environ["FB_CLIENT_EMAIL"],
    "token_uri":os.environ["FB_TOKEN_URI"]
}
```

```python:認証処理（修正後）
cred = credentials.Certificate(charged_dict)
app = firebase_admin.initialize_app(cred)
db = firestore.client()
```

キーは**type、project_id、private_key、client_email、token_uri**となります。
（こちらもエラーになったら、確認してみてください。）

## おわりに

この記事だけ読んだ方は「環境変数に入れたり出したり何がしたいの？」
って思われると思いますが、**認証はJSONがいる** というわけではないことがわかって頂ければ幸いです。



## 2023/9/22追記
すいません。以上の方法ですが、CloudFunctionsなど環境によってキー内の改行コード **\n** が **\\\n** に変換されてしまう場合があり、その場合は以下の対処をお願いします。

**cert["キー名"] = cert["キー名"].replace("\\\n","\n")**

特に**private_key**が改行を含んでいるので、一度dictに入れた後にこの方法で変換してください。
※キーをdictにセットする際にPythonが勝手に **\\\n** に変えてしまうようです。


## 参考にさせて頂いた記事
https://qiita.com/tasogarei/items/87e3bc482bff539921ce