【发布时间】:2018-07-30 18:05:15
【问题描述】:
我正在用原生 JavaScript 编写 Firebase 应用程序。我正在为 Web 使用 Firebase 身份验证和 FirebaseUI。我正在使用 Firebase Cloud Functions 来实现一个服务器,该服务器接收对我的页面路由的请求并返回呈现的 HTML。我正在努力寻找在客户端使用经过身份验证的 ID 令牌访问 Firebase Cloud Function 提供的受保护路由的最佳做法。
我相信我理解了基本流程:用户登录,这意味着一个ID令牌被发送到客户端,在onAuthStateChanged回调中接收它,然后插入到任何新HTTP的Authorization字段中使用正确的前缀请求,然后在用户尝试访问受保护的路由时由服务器检查。
我不明白应该如何处理 onAuthStateChanged 回调中的 ID 令牌,或者我应该如何修改客户端 JavaScript 以在必要时修改请求标头。
我正在使用 Firebase Cloud Functions 来处理路由请求。这是我的functions/index.js,它导出了所有请求都重定向到的app 方法以及检查ID 令牌的位置:
const functions = require('firebase-functions')
const admin = require('firebase-admin')
const express = require('express')
const cookieParser = require('cookie-parser')
const cors = require('cors')
const app = express()
app.use(cors({ origin: true }))
app.use(cookieParser())
admin.initializeApp(functions.config().firebase)
const firebaseAuthenticate = (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.__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 {
console.log('Found "__session" cookie')
// Read the ID Token from cookie.
idToken = req.cookies.__session
}
admin.auth().verifyIdToken(idToken).then(decodedIdToken => {
console.log('ID Token correctly decoded', decodedIdToken)
console.log('token details:', JSON.stringify(decodedIdToken))
console.log('User email:', decodedIdToken.firebase.identities['google.com'][0])
req.user = decodedIdToken
return next()
}).catch(error => {
console.error('Error while verifying Firebase ID token:', error)
res.status(403).send('Unauthorized')
})
}
const meta = `<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link type="text/css" rel="stylesheet" href="https://cdn.firebase.com/libs/firebaseui/2.6.0/firebaseui.css" />
const logic = `<!-- Intialization -->
<script src="https://www.gstatic.com/firebasejs/4.10.0/firebase.js"></script>
<script src="/init.js"></script>
<!-- Authentication -->
<script src="https://cdn.firebase.com/libs/firebaseui/2.6.0/firebaseui.js"></script>
<script src="/auth.js"></script>`
app.get('/', (request, response) => {
response.send(`<html>
<head>
<title>Index</title>
${meta}
</head>
<body>
<h1>Index</h1>
<a href="/user/fake">Fake User</a>
<div id="firebaseui-auth-container"></div>
${logic}
</body>
</html>`)
})
app.get('/user/:name', firebaseAuthenticate, (request, response) => {
response.send(`<html>
<head>
<title>User - ${request.params.name}</title>
${meta}
</head>
<body>
<h1>User ${request.params.name}</h1>
${logic}
</body>
</html>`)
})
exports.app = functions.https.onRequest(app)
她是我的functions/package.json,它描述了处理 HTTP 请求的服务器的配置,实现为 Firebase Cloud Function:
{
"name": "functions",
"description": "Cloud Functions for Firebase",
"scripts": {
"lint": "./node_modules/.bin/eslint .",
"serve": "firebase serve --only functions",
"shell": "firebase experimental:functions:shell",
"start": "npm run shell",
"deploy": "firebase deploy --only functions",
"logs": "firebase functions:log"
},
"dependencies": {
"cookie-parser": "^1.4.3",
"cors": "^2.8.4",
"eslint-config-standard": "^11.0.0-beta.0",
"eslint-plugin-import": "^2.8.0",
"eslint-plugin-node": "^6.0.0",
"eslint-plugin-standard": "^3.0.1",
"firebase-admin": "~5.8.1",
"firebase-functions": "^0.8.1"
},
"devDependencies": {
"eslint": "^4.12.0",
"eslint-plugin-promise": "^3.6.0"
},
"private": true
}
这是我的firebase.json,它将所有页面请求重定向到我导出的app 函数:
{
"functions": {
"predeploy": [
"npm --prefix $RESOURCE_DIR run lint"
]
},
"hosting": {
"public": "public",
"ignore": [
"firebase.json",
"**/.*",
"**/node_modules/**"
],
"rewrites": [
{
"source": "**",
"function": "app"
}
]
}
}
这是我的public/auth.js,在客户端请求和接收令牌。这就是我卡住的地方:
/* global firebase, firebaseui */
const uiConfig = {
// signInSuccessUrl: '<url-to-redirect-to-on-success>',
signInOptions: [
// Leave the lines as is for the providers you want to offer your users.
firebase.auth.GoogleAuthProvider.PROVIDER_ID,
// firebase.auth.FacebookAuthProvider.PROVIDER_ID,
// firebase.auth.TwitterAuthProvider.PROVIDER_ID,
// firebase.auth.GithubAuthProvider.PROVIDER_ID,
firebase.auth.EmailAuthProvider.PROVIDER_ID
// firebase.auth.PhoneAuthProvider.PROVIDER_ID
],
callbacks: {
signInSuccess () { return false }
}
// Terms of service url.
// tosUrl: '<your-tos-url>'
}
const ui = new firebaseui.auth.AuthUI(firebase.auth())
ui.start('#firebaseui-auth-container', uiConfig)
firebase.auth().onAuthStateChanged(function (user) {
if (user) {
firebase.auth().currentUser.getIdToken().then(token => {
console.log('You are an authorized user.')
// This is insecure. What should I do instead?
// document.cookie = '__session=' + token
})
} else {
console.warn('You are an unauthorized user.')
}
})
在客户端我应该如何处理经过身份验证的 ID 令牌?
Cookies/localStorage/webStorage 似乎不是完全安全的,至少不是我能找到的任何相对简单和可扩展的方式。可能有一个简单的基于 cookie 的过程,它与直接在请求标头中包含令牌一样安全,但我无法找到可以轻松应用于 Firebase 的代码。
我知道如何在 AJAX 请求中包含令牌,例如:
var xhr = new XMLHttpRequest()
xhr.open('GET', URL)
xmlhttp.setRequestHeader("Authorization", 'Bearer ' + token)
xhr.onload = function () {
if (xhr.status === 200) {
alert('Success: ' + xhr.responseText)
}
else {
alert('Request failed. Returned status of ' + xhr.status)
}
}
xhr.send()
但是,我不想制作单页应用程序,所以我不能使用 AJAX。我无法弄清楚如何将令牌插入到正常路由请求的标头中,例如通过单击具有有效href 的锚标记触发的请求。我应该拦截这些请求并以某种方式修改它们吗?
在 Firebase for Web 应用程序(不是单页应用程序)中实现可扩展客户端安全性的最佳做法是什么?我不需要复杂的身份验证流程。我愿意为一个我可以信任和简单实施的安全系统牺牲灵活性。
【问题讨论】:
-
这取决于您的应用架构(您使用的是其他 Firebase 服务、单页应用还是传统 Web 应用、托管您自己的服务器等)。一种选择是在登录后,将 ID 令牌发送到您的后端并进行验证。您可以检查
auth_time以获取最近的登录信息,然后使用 expressjs 会话发出会话 cookie。 -
我唯一的后端是托管在 firebase 云功能中的快速服务器。我不想做单页应用程序。快速服务器构建一个 html 字符串并将其直接发送给用户,如我的代码所示。有没有办法使用 cookie 的
auth_time来完全保护路由?你能提供一个答案来说明如何做到这一点吗? -
提供更多关于你的设置的信息,如果可能的话发布 package.json
-
我已经包含了我的
package.json和firebase.json。我的帖子现在包括我的所有应用程序,而不是我的public/init.js,它只运行从 Firebase 控制台复制的初始化代码。在服务器端,Firebase Cloud Function 接收所有请求并以呈现的 HTML 进行响应。我使用 FirebaseUI 文档中的代码来验证对受保护路由的请求。在客户端,用户使用 FirebaseUI 登录并接收 ID 令牌。他们需要在 HTTP 请求中将该令牌发送到服务器以访问受保护的路由。我怎样才能以完全安全的方式? -
您为什么认为 cookie 不安全?你在担心什么?
标签: javascript security firebase firebase-authentication firebaseui