[英]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.