Wear OS 上 Firebase 授权的最佳实践
Posted
技术标签:
【中文标题】Wear OS 上 Firebase 授权的最佳实践【英文标题】:Best practice for firebase authorization on Wear OS 【发布时间】:2021-09-26 21:11:41 【问题描述】:我正在 Wear OS 上为连接到 android 设备的随附应用程序实现一个 firebase 实时数据库,我想知道在佩戴手表上验证用户身份的最佳做法是什么。在小屏幕上输入电子邮件和密码不是很方便。是否可以通过 Wear os 数据层传递 Firebase 授权令牌,如果可以,您将如何使用来自 Android 设备的令牌在 Wear 手表上对用户进行身份验证?
谢谢你, 唐尼
【问题讨论】:
【参考方案1】:文档涵盖了您可以使用的different authentication approaches。
最终,您至少需要一种基于网络的方法来验证手表,因为您无法保证用户会安装您的配套应用程序或手表未连接到 ios 设备。
您有两种可用的方法(我能想到):
选项 1:短期令牌交换
在此方法中,您执行以下步骤:
-
提示用户打开登录网页或打开配套应用(或发送
RemoteIntent
为他们打开)
通过身份验证后,调用创建身份验证代码(大约 5-6 个字母数字字符长)的云函数,并将其安全地存储在您选择的数据库中,有效期为 1 到 2 分钟。
让用户直接在手表上输入代码(或使用数据层将其发送到手表)。
将代码发送到另一个 Cloud Function 以将其交换为 Firebase ID 令牌。
const functions = require('firebase-functions');
const sha256 = (s) => require('crypto').createHash('sha256').update(s).digest('base64');
const lazyFirebaseAdmin = () =>
const admin = require('firebase-admin');
try
admin.app();
catch
admin.initializeApp();
return admin;
const createUserAuthCode = async (uid) =>
const chars = "0123456789abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ"; // omitted O, I, l
let code = "", charsLen = chars.length;
for (let i=0; i<6; i++)
code += chars[Math.floor(Math.random() * charsLen)];
const encoded = sha256(code);
await lazyFirebaseAdmin()
.firestore()
.collection('_server/auth/userCodes')
.doc(encoded)
.create(
created: admin.firestore.FieldValue.serverTimestamp(),
uid
);
return code;
const validateUserAuthCode = async (code) =>
const encoded = sha256(code);
const codeRef = lazyFirebaseAdmin()
.firestore()
.collection('_server/auth/userCodes')
.doc(encoded);
const snapshot = await codeRef.get();
if (!snapshot.exists)
return null; // not found
const uid, created = snapshot.data();
await codeRef.delete();
if (created.toMillis() < Date.now() - (2 * 60 * 1000))
return null; // too old
return uid || null;
const getDeviceCode = functions.https.onCall(async (data, context) =>
if (context.app === undefined) // If you want to use Firebase App Check to mitigate abuse
throw new HttpsError(
'failed-precondition',
'Unrecognized caller');
if (!context.auth)
throw new HttpsError(
'failed-precondition',
'You must be authenticated to request a device code');
try
return
code: await createUserAuthCode(context.auth.uid)
;
catch (error)
throw new HttpsError(
'unknown',
'Couldn\'t generate device code',
message: error.code || error.message
);
);
const exchangeDeviceCode = functions.https.onRequest(async (req, res) =>
if (req.method !== "GET")
console.log("Rejected unexpected " + req.method + " request");
res.status(405)
.set("Allow", "GET")
.end();
return;
const code = req.query.code;
if (typeof code !== "string")
res.status(400)
.json( message: "Missing code param" );
return;
try
const uid = await validateUserAuthCode(code);
const token = await admin.auth()
.createCustomToken(uid,
isDeviceToken: true // by having this, you can prevent the watch
// auth tokens from doing privileged actions
);
const response = await fetch(
url: "https://identitytoolkit.googleapis.com/v1/accounts:signInWithCustomToken?key=[API_KEY]", // TODO: Replace with Web API key
method: "POST",
headers: "Content-Type": "application/json" ,
body: JSON.stringify( token, returnSecureToken: true )
);
// idToken - Firebase ID token (access token)
// refreshToken - refresh token for this device authentication token
// expiresIn - number of seconds to ID token expiry
res
.status(response.status)
.set("Content-Type", "application/json")
.send(response.text());
catch (err)
res.status(500)
.json( error: "Encountered unexpected error" );
);
在客户端,您可以使用以下任一方式调用第一个函数(登录后):
// Java
var getDeviceCodeFunc = FirebaseFunctions.getInstance().getHttpsCallable("getDeviceCode")
getDeviceCodeFunc.call()
.addOnCompleteListener( task ->
if (task.isSuccessful())
// got code!
else
// failed!
);
// Web/javascript
const getDeviceCode = firebase.functions().httpsCallable("getDeviceCode");
const code = await getDeviceCode();
然后,一旦用户输入代码,将其发送到
GET https://us-central1-[PROJECT_ID].cloudfunctions.net/exchangeDeviceCode?code=[TYPED_CODE]
选项2:PKCE
在此方法中,您执行以下步骤:
-
[观看]启动
sendAuthorizationRequest()
流
[网页] 验证用户身份(如果需要)并请求连接设备的权限
[Cloud Function] 解析上一步的允许/拒绝请求并为该用户生成自定义身份验证令牌
[Cloud Function] 将自定义身份验证令牌交换为 Firebase ID 令牌并使用 GET 参数 accessToken
和 refreshToken
重定向到 https://wear.googleapis.com/3p_auth/com.your.package.name
。
[观看]解析响应
注意:这对于您正在尝试做的事情可能有点过头了。但是,如果您真的不希望有人在手表上输入代码,则可以选择使用它。您可以使用 oauth2-server
来代理发布 Firebase ID 令牌(访问令牌)。
【讨论】:
感谢您的详细回复。我阅读了您所指的文档,但不确定如何实施。你的解释帮助我解决了所有的问题。只是一个后续问题。所以不可能使用数据层将firebase令牌id发送到手表(假设用户在android设备上拥有应用程序)? @donnyrewq 发送 ID 令牌有点没有意义,因为它只会持续一个小时,这意味着需要至少每小时重新连接到手机。使用此处描述的方法,您可以生成可供手表使用的新访问令牌和刷新令牌对,因此它不需要一直与您的应用程序对话。 有道理,感谢您为我澄清。以上是关于Wear OS 上 Firebase 授权的最佳实践的主要内容,如果未能解决你的问题,请参考以下文章
Wear OS 上 Jetpack Compose 中的 BasicTextField 问题
Wear OS 更新一览 | 2021 Android 开发者峰会