Wear 上 Firebase 授权的最佳实践 OS

Best practice for firebase authorization on Wear OS

我正在 Wear OS 上为连接到 Android 设备的随附应用程序实施 firebase 实时数据库,我想知道在 Wear 手表上验证用户身份的最佳做法是什么。在小手表屏幕上输入电子邮件和密码不是很方便。 pos是否可以通过 wear os 数据层传递 firebase 授权令牌,如果是,您将如何使用来自 Android 设备的令牌对 wear 上的用户进行身份验证观看?

谢谢, 唐尼

文档涵盖了您可以使用的 different authentication approaches

最终,您至少需要一种基于 Web 的方法来验证手表,因为您无法保证用户会安装您的配套应用或手表未连接到 iOS设备。

你有两种方法(我能想到):

选项 1:短期代币交换

在此方法中,您执行以下步骤:

  1. 提示用户打开登录网页或打开配套应用(或发送 RemoteIntent 为他们打开)
  2. 一旦通过身份验证,调用创建身份验证代码(大约 5-6 个字母数字字符长)的 Cloud Functions 并将其安全地存储在您选择的数据库中,有效期为 1 到 2 分钟。
  3. 让用户直接在他们的手表上输入代码(或使用数据层将其发送到手表)。
  4. 将代码发送到另一个 Cloud Functions 以将其交换为 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

在此方法中,您执行以下步骤:

  1. [观看]开始sendAuthorizationRequest()
  2. [网页] 验证用户(如果需要)并请求连接设备的权限
  3. [Cloud Function] 解析来自上一步的 allow/deny 请求并为该用户生成自定义身份验证令牌
  4. [Cloud Function] 将自定义身份验证令牌交换为 Firebase ID 令牌,并使用 GET 参数 accessTokenrefreshToken.
  5. 重定向到 https://wear.googleapis.com/3p_auth/com.your.package.name
  6. [观看] 解析响应

注意: 这对于您正在尝试做的事情来说可能有点矫枉过正。但是,如果您真的不希望有人必须在他们的手表上输入代码,它可以作为一个选项提供。您可以使用 oauth2-server 来代理颁发 Firebase ID 令牌(访问令牌)。