簡體   English   中英

如何在 Flutter Android 上編寫 Firebase 用 Apple 登錄的功能

[英]How to write Firebase Functions for Sign in with Apple on Flutter Android

我正在與 Apple 在 Flutter Android 上登錄。

目前,使用 Firebase 使用 Apple 登錄 Auth 在 Flutter Android 上不起作用。 看到這個問題。

但是,按照此指南,我可以使用示例 Glitch 回調項目成功登錄 Apple。

但我想在 Firebase 函數上實現這個自定義的服務器端回調。

下面是我的 Firebase 函數代碼。 它會加載 Apple 登錄屏幕,但是當我登錄該屏幕並 select Continue 在下一頁時,它會重定向到 Google 登錄屏幕,這與 Glitch 項目不同,它重定向到我的 Flutter 應用程序。

我相信我遵循了 Firebase 和 sign_in_with_apple 文檔中的所有說明,因為它適用於 Glitch 項目。

我對 node.js 和 Express 了解不多。

請指出我出了什么問題。

const functions = require("firebase-functions");
const admin = require('firebase-admin');
admin.initializeApp();

const express = require("express");
const AppleAuth = require("apple-auth");
const jwt = require("jsonwebtoken");
const bodyParser = require("body-parser");
const package_name = "my.package.name";
const team_id = "myteamid";
const service_id = "myserviceid";
const bundle_id = "mybundleid";
const key_id = "mykeyid";
const key_contents = 
`
MIGTAgEAMBMGByqGSM49234CCqGSM49AwEHBHkwdwIBAQQg27klLz6CSd30LJhs
...............................................................
mTfM2jUHZzQGiAySweGo7BmigwasdBvToiLErq4YJldATys1zSRNpWnSB//RAYRa
gyMCp94Y
`;

const app = express();

app.use(bodyParser.urlencoded({ extended: false }));

// make all the files in 'public' available
// https://expressjs.com/en/starter/static-files.html
app.use(express.static("public"));

// https://expressjs.com/en/starter/basic-routing.html
app.get("/", (request, response) => {
    res.send('Got GET request');
});

// The callback route used for Android, which will send the callback parameters from Apple into the Android app.
// This is done using a deeplink, which will cause the Chrome Custom Tab to be dismissed and providing the parameters from Apple back to the app.
app.post("callbacks/sign_in_with_apple", (request, response) => {
  const redirect = `intent://callback?${new URLSearchParams(request.body).toString()}#Intent;package=${package_name};scheme=signinwithapple;end`;

  console.log(`Redirecting to ${redirect}`);

  response.redirect(307, redirect);
});

// Endpoint for the app to login or register with the `code` obtained during Sign in with Apple
//
// Use this endpoint to exchange the code (which must be validated with Apple within 5 minutes) for a session in your system
app.post("/sign_in_with_apple", async (request, response) => {
  const auth = new AppleAuth(
    {
      // use the bundle ID as client ID for native apps, else use the service ID for web-auth flows
      // https://forums.developer.apple.com/thread/118135
      client_id:
        request.query.useBundleId === "true"
          ? bundle_id
          : service_id,
      team_id: team_id,
      redirect_uri:
        "https://us-central1-project-id.cloudfunctions.net/callbacks/sign_in_with_apple", // does not matter here, as this is already the callback that verifies the token after the redirection
      key_id: key_id
    },
    key_contents.replace(/\|/g, "\n"),
    "text"
  );

  console.log(`request query = ${request.query}`);

  const accessToken = await auth.accessToken(request.query.code);

  const idToken = jwt.decode(accessToken.id_token);

  const userID = idToken.sub;

  console.log(`idToken = ${idToken}`);

  const sessionID = `NEW SESSION ID for ${userID} / ${idToken.email}`;

  console.log(`sessionID = ${sessionID}`);

  response.json({sessionId: sessionID});
});

exports.sign_in_with_apple = functions.https.onRequest(app);

這是我的 package.json

{
  "name": "functions",
  "description": "Cloud Functions for Firebase",
  "scripts": {
    "lint": "eslint",
    "serve": "firebase emulators:start --only functions",
    "shell": "firebase functions:shell",
    "start": "npm run shell",
    "deploy": "firebase deploy --only functions",
    "logs": "firebase functions:log"
  },
  "engines": {
    "node": "16"
  },
  "main": "index.js",
  "dependencies": {
    "firebase-admin": "^10.0.2",
    "firebase-functions": "^3.18.0",
    "apple-auth": "1.0.7",
    "body-parser": "1.19.0",
    "express": "^4.17.1",
    "jsonwebtoken": "^8.5.1"
  },
  "devDependencies": {
    "eslint": "^8.9.0",
    "eslint-config-google": "^0.14.0",
    "firebase-functions-test": "^0.2.0"
  },
  "private": true
}

這是我的 Flutter 應用程序中的代碼。

Future<void> signInWithApple(BuildContext context) async {
    // To prevent replay attacks with the credential returned from Apple, we
    // include a nonce in the credential request. When signing in with
    // Firebase, the nonce in the id token returned by Apple, is expected to
    // match the sha256 hash of `rawNonce`.
    final rawNonce = generateNonce();
    final nonce = sha256ofString(rawNonce);

    // Request credential for the currently signed in Apple account.
    final appleCredential = await SignInWithApple.getAppleIDCredential(
      scopes: [
        AppleIDAuthorizationScopes.email,
        AppleIDAuthorizationScopes.fullName,
      ],
      webAuthenticationOptions: WebAuthenticationOptions(
        clientId: 'my.service.id',
        redirectUri: Uri.parse('https://us-central1-project-name.cloudfunctions.net/sign_in_with_apple'),
      ),
      nonce: nonce,
    );

    // Create an `OAuthCredential` from the credential returned by Apple.
    final credential = OAuthProvider("apple.com").credential(
      idToken: appleCredential.identityToken,
      rawNonce: rawNonce,
      accessToken: '123'  // why this? https://github.com/firebase/flutterfire/issues/8865
    );

    debugPrint('Credential : $credential');

    // Sign in the user with Firebase. If the nonce we generated earlier does
    // not match the nonce in `appleCredential.identityToken`, sign in will fail.
    try {
      await _authInstance.signInWithCredential(credential);
    } on FirebaseAuthException catch (error) {
      _showErrorDialog(error, context);
    }
  } 

GGirotto 的解決方案完全有效。 我最初遇到了實現它的問題,因為我不知道 JavaScript 是如何工作的。 因此,我正在為可能遇到與我相同問題的人寫這篇文章。 如果您在系統上安裝/初始化雲功能時強制使用 lint,它將在文件“.eslintrc.js”下的 lint 選項中添加“google”

lint 告訴我要進行一些更改,例如用雙撇號替換 BACKTICKS `。

現在這使得字符串中的變量無法正確處理。 那些反引號 ` 很重要。 總而言之,您需要在 lint 強制文件“.eslintrc.js”中禁用 google,這就是我的文件現在的樣子。

module.exports = {
  root: true,
  env: {
    es6: true,
    node: true,
  },
  extends: [
    "eslint:recommended",
    // "google",
  ],
  rules: {
    quotes: ["error", "double"],
  },
};

一旦我這樣做了,一切都開始工作了。 您還需要進行一些小的更改,例如 app.post 部分。 就是現在

app.post("/", (request, response) => {
  var redirect = `intent://callback?${new URLSearchParams(request.body).toString()}#Intent;package=${package_name};scheme=signinwithapple;end`;

  console.log(`Redirecting to ${redirect}`);

  response.redirect(307, redirect);
});

此外,您實際上並不需要 app.get 部分。 我禁用了它。 一切都還不錯。 我確實相信該部分僅適用於可能在瀏覽器中瀏覽到 url 的人。 此特定雲 function 不需要。

我希望接下來在雲 function 上實現 App Check,希望一切順利。 祝大家一切順利。 再次感謝 GGirotto 的貢獻,它幫助很大。 非常感激。

不確定您是否需要您編寫的/sign_in_with_apple function,因為該應用程序已經通過 SDK 生成了 session id。 在您的服務器端嘗試以下操作:

const functions = require("firebase-functions");
const admin = require('firebase-admin');
admin.initializeApp();

const express = require("express");
const bodyParser = require("body-parser");
const package_name = "my.package.name";

const app = express();

app.use(bodyParser.urlencoded({ extended: false }));

// make all the files in 'public' available
// https://expressjs.com/en/starter/static-files.html
app.use(express.static("public"));

// https://expressjs.com/en/starter/basic-routing.html
app.get("/", (request, response) => {
    res.send('Got GET request');
});

// The callback route used for Android, which will send the callback parameters from Apple into the Android app.
// This is done using a deeplink, which will cause the Chrome Custom Tab to be dismissed and providing the parameters from Apple back to the app.
app.post("/sign_in_with_apple", (request, response) => {
  const redirect = `intent://callback?${new URLSearchParams(request.body).toString()}#Intent;package=${package_name};scheme=signinwithapple;end`;

  console.log(`Redirecting to ${redirect}`);

  response.redirect(307, redirect);
});

exports.sign_in_with_apple = functions.https.onRequest(app);

根據@GGirotto 和@Monkey Drone 的回答,我發布了完整的 Cloud Function 代碼。

index.js

const functions = require("firebase-functions");
const admin = require('firebase-admin');
admin.initializeApp();

const express = require("express");
const bodyParser = require("body-parser");
const package_name = "com.appxotica.keyone";

const app = express();

app.use(bodyParser.urlencoded({ extended: false }));

// make all the files in 'public' available
// https://expressjs.com/en/starter/static-files.html
app.use(express.static("public"));

// The callback route used for Android, which will send the callback parameters from Apple into the Android app.
// This is done using a deeplink, which will cause the Chrome Custom Tab to be dismissed and providing the parameters from Apple back to the app.
app.post("/", (request, response) => {
  const redirect = `intent://callback?${new URLSearchParams(request.body).toString()}#Intent;package=${package_name};scheme=signinwithapple;end`;

  console.log(`Redirecting to ${redirect}`);

  response.redirect(307, redirect);
});


exports.sign_in_with_apple = functions.https.onRequest(app);

.eslintrc.js

module.exports = {
  root: true,
  env: {
    es6: true,
    node: true,
  },
  extends: [
    "eslint:recommended",
  ],
  rules: {
    quotes: ["error", "double"],
    "no-unused-vars": "warn"
  },
};

package.json

{
  "name": "functions",
  "description": "Cloud Functions for Firebase",
  "scripts": {
    "lint": "eslint",
    "serve": "firebase emulators:start --only functions",
    "shell": "firebase functions:shell",
    "start": "npm run shell",
    "deploy": "firebase deploy --only functions",
    "logs": "firebase functions:log"
  },
  "engines": {
    "node": "16"
  },
  "main": "index.js",
  "dependencies": {
    "firebase-admin": "^10.0.2",
    "firebase-functions": "^3.18.0",
    "body-parser": "1.19.0",
    "express": "^4.17.1"
  },
  "devDependencies": {
    "eslint": "^8.9.0",
    "eslint-config-google": "^0.14.0",
    "firebase-functions-test": "^0.2.0"
  },
  "private": true
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM