使用新的firebase云功能,我决定将一些HTTP端点移动到firebase。
一切都很好……但我有以下问题。我有两个端点构建HTTP触发器(云函数)
用于创建用户并返回自定义令牌的API端点
由Firebase Admin SDK生成。
获取特定用户详细信息的API端点。
虽然第一个端点很好,但对于第二个端点,我希望仅为经过身份验证的用户保护它。意思是拥有我之前生成的令牌的人。
我该怎么解呢?
我知道我们可以在云函数中使用Header参数
request.get('x-myheader')
但是有没有一种方法可以像保护实时数据库一样保护端点呢?
在Firebase中,为了简化你的代码和工作,这只是一个架构设计的问题:
For public accessible sites/contents, use HTTPS triggers with Express. To restrict only samesite or specific site only, use CORS to control this aspect of security. This make sense because Express is useful for SEO due to its server-side rendering content.
For apps that require user authentication, use HTTPS Callable Firebase Functions, then use the context parameter to save all the hassles. This also makes sense, because such as a Single Page App built with AngularJS -- AngularJS is bad for SEO, but since it's a password protected app, you don't need much of the SEO either. As for templating, AngularJS has built-in templating, so no need for sever-side template with Express. Then Firebase Callable Functions should be good enough.
有了以上的思想,没有更多的麻烦,让生活更容易。
有一个很好的官方例子,使用Express -可能在未来很方便:https://github.com/firebase/functions-samples/blob/master/authorized-https-endpoint/functions/index.js(贴在下面只是为了确定)
记住,出口。App使你的函数在/ App slug下可用(在这种情况下只有一个函数,在<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
}
上述方法使用函数内部的逻辑对用户进行身份验证,因此仍然必须调用函数来进行检查。
这是一个非常好的方法,但为了全面起见,还有另一种选择:
你可以将一个函数设置为“私有”,这样除了注册用户(由你决定权限),它不能被调用。在这种情况下,在函数上下文中拒绝未经身份验证的请求,并且根本不会调用函数。
这里引用(a)将函数配置为公共/私有,然后(b)对最终用户进行函数的身份验证。
请注意,上面的文档是针对谷歌云平台的,实际上,这是可行的,因为每个Firebase项目也是一个GCP项目。与此方法相关的一个警告是,在撰写本文时,它仅适用于基于google帐户的身份验证。
正如@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));
在Firebase中,为了简化你的代码和工作,这只是一个架构设计的问题:
For public accessible sites/contents, use HTTPS triggers with Express. To restrict only samesite or specific site only, use CORS to control this aspect of security. This make sense because Express is useful for SEO due to its server-side rendering content.
For apps that require user authentication, use HTTPS Callable Firebase Functions, then use the context parameter to save all the hassles. This also makes sense, because such as a Single Page App built with AngularJS -- AngularJS is bad for SEO, but since it's a password protected app, you don't need much of the SEO either. As for templating, AngularJS has built-in templating, so no need for sever-side template with Express. Then Firebase Callable Functions should be good enough.
有了以上的思想,没有更多的麻烦,让生活更容易。