Firebase Cloud Function HTTPエンドポイントを保護してFirebase認証済みユーザーのみを許可するにはどうすればよいですか?


141

新しいfirebaseクラウド関数を使用して、HTTPエンドポイントの一部をfirebaseに移動することにしました。すべてがうまくいきます...しかし、次の問題があります。HTTPトリガー(Cloud Functions)によってビルドされた2つのエンドポイントがあります

  1. ユーザーを作成し、Firebase Admin SDKによって生成されたカスタムトークンを返すAPIエンドポイント。
  2. 特定のユーザーの詳細を取得するためのAPIエンドポイント。

最初のエンドポイントは問題ありませんが、2番目のエンドポイントについては、認証されたユーザーに対してのみ保護したいと思います。以前に生成したトークンを持っている人を意味します。

これを解決するにはどうすればよいですか?

私はクラウド関数でヘッダーパラメーターを取得できることを知っています

request.get('x-myheader')

しかし、リアルタイムデータベースを保護するのと同じようにエンドポイントを保護する方法はありますか?


最初のAPIでFirebase Admin SDKによって生成されたカスタムトークンを取得した方法
Amine Harbaoui 2018

2
@AmineHarbaoui同じ質問がありました。このページを参照してください。firebase.google.com/docs/auth/admin/verify-id-tokens
MichM

回答:


137

あなたがやろうとしていることのための公式のコードサンプルがあります。これは、クライアントが認証中に受信したトークンを含むAuthorizationヘッダーを要求するようにHTTPS関数を設定する方法を示しています。関数は、firebase-adminライブラリを使用してトークンを確認します。

また、アプリがFirebaseクライアントライブラリを使用できる場合は、「呼び出し可能関数」を使用して、このボイラープレートの多くを簡単にすることができます。


2
このコードサンプルはまだ有効ですか?これは、今日、これに対処する方法ですか?
Gal Bracha 2017年

1
@GalBracha現在も有効です(2017年10月31日)。
ダグスティーブンソン

@DougStevensonこれらの「console.log」呼び出しは、パフォーマンスに「顕著な」影響を及ぼしますか?
Sanka Darshana 2017

1
呼び出し可能関数を使用すると、ボイラープレートがどのように簡単になりますか?私が理解していることから、これらは単なる「非REST」サーバー機能ですが、ここでそれらがどのように関係しているかは本当にわかりません。ありがとう。
1252748

2
@ 1252748リンクされたドキュメントを読むと、それが明らかになります。authトークンの受け渡しと検証を自動的に処理するので、どちらの側でもそのコードを記述する必要はありません。
ダグスティーブンソン

121

@Dougで述べたようにfirebase-admin、トークンの検証に使用できます。簡単な例を設定しました:

exports.auth = functions.https.onRequest((req, res) => {
  cors(req, res, () => {
    const tokenId = req.get('Authorization').split('Bearer ')[1];

    return admin.auth().verifyIdToken(tokenId)
      .then((decoded) => res.status(200).send(decoded))
      .catch((err) => res.status(401).send(err));
  });
});

上記の例では、CORSも有効にしていますが、これはオプションです。まず、Authorizationヘッダーを取得してを見つけますtoken

次に、を使用firebase-adminしてそのトークンを確認できます。そのユーザーのデコードされた情報をレスポンスで取得します。それ以外の場合、トークンが有効でないと、エラーがスローされます。


13
シンプルで公式の例のようにexpressに依存しないため、賛成です。
DarkNeuron 2017年

5
コアについて詳しく説明できますか?
ピート

@pete:corsは、クロスオリジンリソースシェアリングを解決しているだけです。あなたはそれについてもっと知るためにグーグルすることができます。
ラングホアン

@pete Corsを使用すると、さまざまなURLからそのfirebase-backendエンドポイントにアクセスできます。
Walter Monecke 2017

6
あなたが使用することができます@RezaRahmati getIdToken()クライアント側でメソッドを(例えばfirebase.auth().currentUser.getIdToken().then(token => console.log(token))ドキュメントfirebase
ウィル

18

@Dougでも言及されているように、クライアントおよびサーバーからボイラープレートコード除外するために呼び出し可能関数を使用できます。

呼び出し可能な関数の例:

export const getData = functions.https.onCall((data, context) => {
  // verify Firebase Auth ID token
  if (!context.auth) {
    return { message: 'Authentication Required!', code: 401 };
  }

  // do your things..
  const uid = context.auth.uid;
  const query = data.query;

  return { message: 'Some Data', code: 400 };
});

次のようにクライアントから直接呼び出すことができます。

firebase.functions().httpsCallable('getData')({query}).then(result => console.log(result));

2

上記のメソッドは関数のロジックを使用してユーザーを認証するため、チェックを実行するには関数を呼び出す必要があります。

それは完全に良い方法ですが、包括性のために代替手段があります:

関数を「プライベート」に設定して、登録されたユーザー以外は呼び出せないようにすることができます(権限を決定します)。この場合、認証されていない要求は関数のコンテキスト外では拒否され、関数はまったく呼び出されません

ここでは、(a)public / privateとして関数を構成し、(b)関数に対してエンドユーザーを認証する方法について説明します

上記のドキュメントはGoogle Cloud Platform向けであり、実際にはすべてのFirebaseプロジェクトがGCPプロジェクトであるため、これは機能します。この方法に関連する警告は、執筆時点では、Googleアカウントベースの認証でのみ機能することです。


1

Expressを使用した公式の良い例があります-将来的に便利になるかもしれません:https : //github.com/firebase/functions-samples/blob/master/authorized-https-endpoint/functions/index.js(以下に貼り付けます)確かに)

exports.appは、関数を/appslugで使用できるように<you-firebase-app>/app/helloすることに注意してください(この場合、関数は1つだけで、で使用できます。それを取り除くには、実際にはExpressの部分を少し書き直す必要があります(検証用のミドルウェアの部分は同じです-非常に機能します)良いし、コメントのおかげでかなり理解できる)。

/**
 * Copyright 2016 Google Inc. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
'use strict';

const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp();
const express = require('express');
const cookieParser = require('cookie-parser')();
const cors = require('cors')({origin: true});
const app = express();

// Express middleware that validates Firebase ID Tokens passed in the Authorization HTTP header.
// The Firebase ID token needs to be passed as a Bearer token in the Authorization HTTP header like this:
// `Authorization: Bearer <Firebase ID Token>`.
// when decoded successfully, the ID Token content will be added as `req.user`.
const validateFirebaseIdToken = async (req, res, next) => {
  console.log('Check if request is authorized with Firebase ID token');

  if ((!req.headers.authorization || !req.headers.authorization.startsWith('Bearer ')) &&
      !(req.cookies && req.cookies.__session)) {
    console.error('No Firebase ID token was passed as a Bearer token in the Authorization header.',
        'Make sure you authorize your request by providing the following HTTP header:',
        'Authorization: Bearer <Firebase ID Token>',
        'or by passing a "__session" cookie.');
    res.status(403).send('Unauthorized');
    return;
  }

  let idToken;
  if (req.headers.authorization && req.headers.authorization.startsWith('Bearer ')) {
    console.log('Found "Authorization" header');
    // Read the ID Token from the Authorization header.
    idToken = req.headers.authorization.split('Bearer ')[1];
  } else if(req.cookies) {
    console.log('Found "__session" cookie');
    // Read the ID Token from cookie.
    idToken = req.cookies.__session;
  } else {
    // No cookie
    res.status(403).send('Unauthorized');
    return;
  }

  try {
    const decodedIdToken = await admin.auth().verifyIdToken(idToken);
    console.log('ID Token correctly decoded', decodedIdToken);
    req.user = decodedIdToken;
    next();
    return;
  } catch (error) {
    console.error('Error while verifying Firebase ID token:', error);
    res.status(403).send('Unauthorized');
    return;
  }
};

app.use(cors);
app.use(cookieParser);
app.use(validateFirebaseIdToken);
app.get('/hello', (req, res) => {
  res.send(`Hello ${req.user.name}`);
});

// This HTTPS endpoint can only be accessed by your Firebase Users.
// Requests need to be authorized by providing an `Authorization` HTTP header
// with value `Bearer <Firebase ID Token>`.
exports.app = functions.https.onRequest(app);

取り除くための私の書き直し/app

const hello = functions.https.onRequest((request, response) => {
  res.send(`Hello ${req.user.name}`);
})

module.exports = {
  hello
}

0

golang GCP関数で適切なFirebase認証を取得するのに苦労しています。実際にはその例はないので、この小さなライブラリを構築することにしました。https//github.com/Jblew/go-firebase-auth-in-gcp-functions

これで、firebase-auth(gcp-authenticated-functionsとは異なり、identity-aware-proxyで直接サポートされていない)を使用してユーザーを簡単に認証できるようになりました。

ユーティリティの使用例を次に示します。

import (
  firebaseGcpAuth "github.com/Jblew/go-firebase-auth-in-gcp-functions"
  auth "firebase.google.com/go/auth"
)

func SomeGCPHttpCloudFunction(w http.ResponseWriter, req *http.Request) error {
   // You need to provide 1. Context, 2. request, 3. firebase auth client
  var client *auth.Client
    firebaseUser, err := firebaseGcpAuth.AuthenticateFirebaseUser(context.Background(), req, authClient)
    if err != nil {
    return err // Error if not authenticated or bearer token invalid
  }

  // Returned value: *auth.UserRecord
}

--allow-unauthenticatedフラグを付けて関数をデプロイすることを覚えておいてください(関数実行内でFirebase認証が発生するため)。

これが私を助けるようにこれがあなたを助けることを願っています。パフォーマンス上の理由から、クラウド機能にgolangを使用することにしました —Jędrzej


0

Firebaseでは、コードと作業を簡略化するために、アーキテクチャ設計の問題にすぎません。

  1. 公開されているアクセス可能なサイト/コンテンツの場合は、でHTTPSトリガーをExpress使用します。samesiteのみまたは特定のサイトのみを制限するCORS、を使用してセキュリティのこの側面を制御します。これExpressは、サーバー側のレンダリングコンテンツによりSEOに役立つので意味があります。
  2. ユーザー認証が必要なアプリの場合はHTTPS呼び出し可能なFirebase関数を使用してcontextから、パラメータを使用してすべての面倒を保存します。これは、AngularJSで構築されたシングルページアプリなどの理由でも理にかなっています。AngularJSはSEOには適していませんが、パスワードで保護されたアプリであるため、SEOの多くは必要ありません。テンプレートに関しては、AngularJSにはテンプレートが組み込まれているため、を使用したサーバー側のテンプレートは必要ありませんExpress。そうすれば、Firebase Callable Functionsで十分です。

上記を念頭に置いて、これ以上の手間と生活を楽にしません。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.