programing

Firebase 인증된 사용자만 허용하도록 Firebase Cloud Function HTTP 끝점을 보호하는 방법은 무엇입니까?

newsource 2023. 7. 4. 21:56

Firebase 인증된 사용자만 허용하도록 Firebase Cloud Function HTTP 끝점을 보호하는 방법은 무엇입니까?

새로운 Firebase 클라우드 기능을 사용하여 HTTP 끝점의 일부를 Firebase로 이동하기로 결정했습니다.모든 것이 잘 작동합니다.하지만 저는 다음과 같은 문제가 있습니다.HTTP 트리거(클라우드 함수)에 의해 구축된 두 개의 엔드포인트가 있습니다.

  1. 사용자를 생성하고 Firebase Admin SDK에서 생성한 사용자 지정 토큰을 반환하는 API 끝점입니다.
  2. 특정 사용자 세부 정보를 가져오는 API 끝점입니다.

첫 번째 엔드포인트는 괜찮지만 두 번째 엔드포인트는 인증된 사용자만을 위해 보호하고 싶습니다. 즉, 이전에 생성한 토큰을 가진 사용자를 의미합니다.

이 문제를 어떻게 해결해야 합니다.

다음을 사용하여 클라우드 기능에서 헤더 매개 변수를 가져올 수 있습니다.

request.get('x-myheader')

하지만 실시간 데이터베이스를 보호하는 것처럼 엔드포인트를 보호하는 방법이 있습니까?

당신이 하려는 일에 대한 공식 코드 샘플이 있습니다.여기에서는 인증 중에 클라이언트가 받은 토큰이 포함된 인증 헤더를 요구하도록 HTTPS 기능을 설정하는 방법에 대해 설명합니다.이 함수는 firebase-admin 라이브러리를 사용하여 토큰을 확인합니다.

또한 앱에서 Firebase 클라이언트 라이브러리를 사용할 수 있는 경우 "호출 가능한 기능"을 사용하여 이 상용판의 많은 부분을 더 쉽게 만들 수 있습니다.

@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확인할 수 있습니다.응답에서 해당 사용자에 대한 디코딩된 정보를 얻을 수 있습니다.그렇지 않으면 토큰이 유효하지 않으면 오류가 발생합니다.

@Doug에서도 언급했듯이, 당신은 당신의 클라이언트와 당신의 서버에서 일부 상용어구 코드를 제외하기 위해 호출 가능기능들을 사용할 수 있습니다.

호출 가능 함수의 예:

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

  /** This scope is reachable for authenticated users only */

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

다음과 같이 클라이언트에서 직접 호출할 수 있습니다.

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

위의 방법은 함수 내부의 논리를 사용하여 사용자를 인증하므로, 검사를 수행하려면 함수를 호출해야 합니다.

그것은 완전히 좋은 방법이지만, 포괄성을 위해 대안이 방법은 다음과 같습니다.

등록된 사용자(사용자가 권한을 결정함) 이외에는 호출할 수 없도록 기능을 "개인"으로 설정할 수 있습니다.이 경우 인증되지 않은 요청은 함수의 컨텍스트 밖에서 거부되며 함수는 전혀 호출되지 않습니다.

다음은 (a) 공용/개인 기능으로 구성한 다음 (b) 최종 사용자를 인증하는 기능에 대한 참조입니다.

위의 문서는 Google Cloud Platform용이며, 모든 Firebase 프로젝트도 GCP 프로젝트이기 때문에 실제로 작동합니다.이 방법과 관련하여 주의할 점은 쓰기의 경우 Google 계정 기반 인증에서만 작동한다는 것입니다.

Firebase에서는 코드와 작업을 단순화하기 위해 아키텍처 설계의 문제일 뿐입니다.

  1. 액세스할 수 있는 공용 사이트/콘텐츠의 경우 HTTPS 트리거를 사용합니다.동일한 사이트 또는 특정 사이트만 제한하려면 사용CORS보안의 이런 측면을 통제하기 위해.이게 말이 되는 이유는Express서버 측 렌더링 콘텐츠로 인해 SEO에 유용합니다.
  2. 사용자 인증이 필요한 앱의 경우 HTTPS 호출 가능 Firebase 기능을 사용한 다음context매개 변수를 사용하여 모든 번거로움을 저장합니다.--AngularAngularJS --AngularJS --AngularJS로 단일 페이지 도 일리가 .JS는 SEO에 좋지 않지만, 비밀번호 보호 앱이기 때문에 SEO도 많이 필요하지 않습니다.Angular ▁angular,, Angular이 내장되어 에 JS template가 필요 없습니다.Express그러면 Firebase Callable Functions가 충분할 것입니다.

위와 같은 사항을 염두에 두고, 더 이상의 번거로움이 없고 삶을 편하게 해줍니다.

이 값을 함수가 boolean으로 반환하는 것으로 간주할 수 있습니다.사용자가 확인했는지 여부를 확인하면 API를 계속하거나 중지합니다.또한 변수 디코딩에서 클레임 또는 사용자 결과를 반환할 수 있습니다.

const authenticateIdToken = async (
    req: functions.https.Request,
    res: functions.Response<any>
) => {
    try {
        const authorization = req.get('Authorization');
        if (!authorization) {
            res.status(400).send('Not Authorized User');
            return false;
        }
        const tokenId = authorization.split('Bearer ')[1];

        return await auth().verifyIdToken(tokenId)
            .then((decoded) => {
                return true;
            })
            .catch((err) => {
                res.status(401).send('Not Authorized User')
                return false;
            });
    } catch (e) {
        res.status(400).send('Not Authorized User')
        return false;
    }
}

여기에 저에게 정말 도움이 되는 많은 훌륭한 정보가 있지만, Angular를 사용하는 사람이라면 누구나 처음으로 이것을 시도할 수 있는 간단한 작업 예제를 분해하는 것이 좋을 것 같습니다.Google Firebase 설명서는 https://firebase.google.com/docs/auth/admin/verify-id-tokens#web 에서 확인할 수 있습니다.

//#### YOUR TS COMPONENT FILE #####
import { Component, OnInit} from '@angular/core';
import * as firebase from 'firebase/app';
import { YourService } from '../services/yourservice.service';

@Component({
  selector: 'app-example',
  templateUrl: './app-example.html',
  styleUrls: ['./app-example.scss']
})

export class AuthTokenExample implements OnInit {

//property
idToken: string;

//Add your service
constructor(private service: YourService) {}

ngOnInit() {

    //get the user token from firebase auth
    firebase.auth().currentUser.getIdToken(true).then((idTokenData) => {
        //assign the token to the property
        this.idToken = idTokenData;
        //call your http service upon ASYNC return of the token
        this.service.myHttpPost(data, this.idToken).subscribe(returningdata => {
            console.log(returningdata)
        });

    }).catch((error) => {
        // Handle error
        console.log(error);
    });

  }

}

//#### YOUR SERVICE #####
//import of http service
import { Injectable } from '@angular/core';
import { HttpClient, HttpHeaders } from '@angular/common/http';
import { Observable } from 'rxjs';

@Injectable({
  providedIn: 'root'
})

export class MyServiceClass {

    constructor(private http: HttpClient) { }

  //your myHttpPost method your calling from your ts file
  myHttpPost(data: object, token: string): Observable<any> {

    //defining your header - token is added to Authorization Bearer key with space between Bearer, so it can be split in your Google Cloud Function
    let httpOptions = {
        headers: new HttpHeaders({
            'Content-Type': 'application/json',
         'Authorization': 'Bearer ' + token
        })
    }

    //define your Google Cloud Function end point your get from creating your GCF
    const endPoint = ' https://us-central1-your-app.cloudfunctions.net/doSomethingCool';

    return this.http.post<string>(endPoint, data, httpOptions);

  }

}


//#### YOUR GOOGLE CLOUD FUNCTION 'GCF' #####
//your imports
const functions = require('firebase-functions');
const admin = require('firebase-admin');
const cors = require('cors')({origin: true});


exports.doSomethingCool = functions.https.onRequest((req, res) => {

//cross origin middleware
    cors(req, res, () => {

        //get the token from the service header by splitting the Bearer in the Authorization header 
        const tokenId = req.get('Authorization').split('Bearer ')[1];

        //verify the authenticity of token of the user
        admin.auth().verifyIdToken(tokenId)
            .then((decodedToken) => {
                //get the user uid if you need it.
               const uid = decodedToken.uid;

                //do your cool stuff that requires authentication of the user here.

            //end of authorization
            })
            .catch((error) => {
                console.log(error);
            });

    //end of cors
    })

//end of function
})

익스프레스를 사용하는 좋은 공식적인 예가 있습니다 - 미래에 유용할 수도 있습니다: https://github.com/firebase/functions-samples/blob/master/authorized-https-endpoint/functions/index.js (아래에 확실히 붙여짐)

명심하세요exports.app다음에서 사용자의 기능을 사용할 수 있습니다./app슬러그(이 경우에는 하나의 기능만 있으며 아래에서 사용할 수 있습니다.<you-firebase-app>/app/hello그것을 제거하기 위해서는 실제로 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
}

저는 Golang GCP 기능에서 적절한 Firebase 인증을 받기 위해 고군분투하고 있습니다.실제로 그런 예는 없습니다. 그래서 저는 이 작은 도서관을 짓기로 결정했습니다. https://github.com/Jblew/go-firebase-auth-in-gcp-functions

이제 firebase-auth(gcp-authenticated-functions와 구별되며 ID 인식 프록시에서 직접 지원되지 않음)를 사용하여 사용자를 쉽게 인증할 수 있습니다.

다음은 유틸리티의 사용 예입니다.

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-unauthenticatedflag(firebase 인증이 함수 실행 내부에서 발생하기 때문에).

이것이 나에게 도움이 되었으니 당신에게 도움이 되길 바랍니다. 성능상의 이유로 클라우드 기능에 Golang을 사용하기로 결정했습니다. - Jęrzej

언급URL : https://stackoverflow.com/questions/42751074/how-to-protect-firebase-cloud-function-http-endpoint-to-allow-only-firebase-auth