Google Sign-In สำหรับ Assistant มอบประสบการณ์ของผู้ใช้ที่ง่ายและสะดวกที่สุด ทั้งผู้ใช้และนักพัฒนาซอฟต์แวร์สำหรับการลิงก์บัญชีและการสร้างบัญชี การดำเนินการของคุณ สามารถขอสิทธิ์เข้าถึงโปรไฟล์ Google ของผู้ใช้ในระหว่างการสนทนา รวมถึงชื่อ อีเมล และรูปโปรไฟล์ของผู้ใช้
สามารถใช้ข้อมูลโปรไฟล์เพื่อสร้างประสบการณ์ที่ปรับเปลี่ยนในแบบของผู้ใช้ได้ ในการดําเนินการของคุณ หากคุณมีแอปในแพลตฟอร์มอื่นๆ ที่แอปเหล่านั้นใช้ Google Sign-In คุณยังสามารถค้นหาและลิงก์กับบัญชีของผู้ใช้ที่มีอยู่ สร้างบัญชีใหม่ และสร้างช่องทางการสื่อสารโดยตรงกับผู้ใช้
คุณต้องขอให้ผู้ใช้ให้ความยินยอมเพื่อทำการลิงก์บัญชีกับ Google Sign-In เข้าถึงโปรไฟล์ Google ของตนเอง จากนั้นใช้ข้อมูลในโปรไฟล์ของผู้ใช้เพื่อ เช่น ที่อยู่อีเมล เพื่อระบุผู้ใช้ในระบบของคุณ
ใช้การลิงก์บัญชี Google Sign-In
ทําตามขั้นตอนในส่วนต่อไปนี้เพื่อเพิ่มการลิงก์บัญชี Google Sign-In กับ แอ็กชัน
กำหนดค่าโปรเจ็กต์
หากต้องการกำหนดค่าโปรเจ็กต์ให้ใช้การลิงก์บัญชี Google Sign-In ให้ทำตามขั้นตอนต่อไปนี้
- เปิดคอนโซล Actions และเลือกโปรเจ็กต์
- คลิกแท็บพัฒนา แล้วเลือกการลิงก์บัญชี
- เปิดใช้สวิตช์ข้างการลิงก์บัญชี
- ในส่วนการสร้างบัญชี ให้เลือกใช่
ในส่วนประเภทการลิงก์ ให้เลือก Google Sign In
เปิดข้อมูลลูกค้า และจดบันทึกค่าของรหัสไคลเอ็นต์ที่ Google ออกให้กับการดำเนินการของคุณ
คลิกบันทึก
เริ่มขั้นตอนการตรวจสอบสิทธิ์
ใช้จุดประสงค์ของตัวช่วยในการลงชื่อเข้าใช้บัญชี เพื่อเริ่มขั้นตอนการตรวจสอบสิทธิ์
หลังจากผู้ใช้ให้สิทธิ์การทำงานของคุณในการเข้าถึงโปรไฟล์ Google ของตน คุณจะได้รับ โทเค็นรหัส Google ที่มีข้อมูลโปรไฟล์ Google ของผู้ใช้ในทุกๆ หน้า คำขอให้ดำเนินการของคุณ
หากต้องการเข้าถึงข้อมูลโปรไฟล์ของผู้ใช้ คุณจะต้องตรวจสอบและถอดรหัสโทเค็นก่อน โดยทำดังนี้
- ใช้ไลบรารีการถอดรหัส JWT สำหรับภาษาของคุณเพื่อถอดรหัส โทเค็น และใช้คีย์สาธารณะของ Google (พร้อมใช้งานใน JWK หรือรูปแบบ PEM) เพื่อยืนยันลายเซ็นของโทเค็น
- ยืนยันว่าผู้ออกโทเค็น (ช่อง
iss
ในโทเค็นที่ถอดรหัสแล้ว) คือ https://accounts.google.com และกลุ่มเป้าหมาย (ช่องaud
ในโทเค็นถอดรหัส) เป็นค่าของ รหัสไคลเอ็นต์ที่ Google ออกให้กับการดำเนินการของคุณ ซึ่งกำหนดให้กับโปรเจ็กต์ของคุณ ในคอนโซล Actions on Google
ต่อไปนี้เป็นตัวอย่างของโทเค็นที่ถอดรหัส
{ "sub": 1234567890, // The unique ID of the user's Google Account "iss": "https://accounts.google.com", // The token's issuer "aud": "123-abc.apps.googleusercontent.com", // Client ID assigned to your Actions project "iat": 233366400, // Unix timestamp of the token's creation time "exp": 233370000, // Unix timestamp of the token's expiration time "name": "Jan Jansen", "given_name": "Jan", "family_name": "Jansen", "email": "jan@gmail.com", // If present, the user's email address "locale": "en_US" }
หากคุณใช้ไลบรารีของไคลเอ็นต์ Actions on Google สำหรับ Node.js หรือไลบรารีไคลเอ็นต์ Java จะดูแลการตรวจสอบและถอดรหัสโทเค็นให้คุณ รวมทั้งให้สิทธิ์การเข้าถึง เนื้อหาโปรไฟล์ ดังที่แสดงในข้อมูลโค้ดต่อไปนี้ โปรดทราบว่า JSON ด้านล่างอธิบายถึงคำขอเว็บฮุคสำหรับ Dialogflow และ Actions SDK ตามลำดับ
ข้อมูลโค้ดต่อไปนี้ใช้ Dialogflow สำหรับการลงชื่อเข้าใช้
const {dialogflow, SignIn} = require('actions-on-google'); const app = dialogflow({ // REPLACE THE PLACEHOLDER WITH THE CLIENT_ID OF YOUR ACTIONS PROJECT clientId: CLIENT_ID, }); // Intent that starts the account linking flow. app.intent('Start Signin', (conv) => { conv.ask(new SignIn('To get your account details')); }); // Create a Dialogflow intent with the `actions_intent_SIGN_IN` event. app.intent('Get Signin', (conv, params, signin) => { if (signin.status === 'OK') { const payload = conv.user.profile.payload; conv.ask(`I got your account details, ${payload.name}. What do you want to do next?`); } else { conv.ask(`I won't be able to save your data, but what do you want to do next?`); } });
private String clientId = "<your_client_id>"; @ForIntent("Start Signin") public ActionResponse text(ActionRequest request) { ResponseBuilder rb = getResponseBuilder(request); return rb.add(new SignIn().setContext("To get your account details")).build(); } @ForIntent("actions.intent.SIGN_IN") public ActionResponse getSignInStatus(ActionRequest request) { ResponseBuilder responseBuilder = getResponseBuilder(request); if (request.isSignInGranted()) { GoogleIdToken.Payload profile = getUserProfile(request.getUser().getIdToken()); responseBuilder.add( "I got your account details, " + profile.get("given_name") + ". What do you want to do next?"); } else { responseBuilder.add("I won't be able to save your data, but what do you want to do next?"); } return responseBuilder.build(); } private GoogleIdToken.Payload getUserProfile(String idToken) { GoogleIdToken.Payload profile = null; try { profile = decodeIdToken(idToken); } catch (Exception e) { LOGGER.error("error decoding idtoken"); LOGGER.error(e.toString()); } return profile; } private GoogleIdToken.Payload decodeIdToken(String idTokenString) throws GeneralSecurityException, IOException { HttpTransport transport = GoogleNetHttpTransport.newTrustedTransport(); JacksonFactory jsonFactory = JacksonFactory.getDefaultInstance(); GoogleIdTokenVerifier verifier = new GoogleIdTokenVerifier.Builder(transport, jsonFactory) // Specify the CLIENT_ID of the app that accesses the backend: .setAudience(Collections.singletonList(clientId)) .build(); GoogleIdToken idToken = verifier.verify(idTokenString); return idToken.getPayload(); }
{ "responseId": "", "queryResult": { "queryText": "", "action": "", "parameters": {}, "allRequiredParamsPresent": true, "fulfillmentText": "", "fulfillmentMessages": [], "outputContexts": [], "intent": { "name": "Get Signin", "displayName": "Get Signin" }, "intentDetectionConfidence": 1, "diagnosticInfo": {}, "languageCode": "" }, "originalDetectIntentRequest": { "source": "google", "version": "2", "payload": { "isInSandbox": true, "surface": { "capabilities": [ { "name": "actions.capability.SCREEN_OUTPUT" }, { "name": "actions.capability.AUDIO_OUTPUT" }, { "name": "actions.capability.MEDIA_RESPONSE_AUDIO" }, { "name": "actions.capability.WEB_BROWSER" } ] }, "inputs": [ { "rawInputs": [], "intent": "", "arguments": [ { "name": "SIGN_IN", "extension": { "@type": "type.googleapis.com/google.actions.v2.SignInValue", "status": "OK" } } ] } ], "user": { "idToken": "peJaCGci..." }, "conversation": {}, "availableSurfaces": [ { "capabilities": [ { "name": "actions.capability.SCREEN_OUTPUT" }, { "name": "actions.capability.AUDIO_OUTPUT" }, { "name": "actions.capability.MEDIA_RESPONSE_AUDIO" }, { "name": "actions.capability.WEB_BROWSER" } ] } ] } }, "session": "" }
ข้อมูลโค้ดต่อไปนี้ใช้ Actions SDK สำหรับการลงชื่อเข้าใช้
const {actionssdk, SignIn} = require('actions-on-google'); const app = actionssdk({ // REPLACE THE PLACEHOLDER WITH THE CLIENT_ID OF YOUR ACTIONS PROJECT clientId: CLIENT_ID, }); // Intent that starts the account linking flow. app.intent('actions.intent.TEXT', (conv) => { conv.ask(new SignIn('To get your account details')); }); // Create an Actions SDK intent with the `actions_intent_SIGN_IN` event. app.intent('actions.intent.SIGN_IN', (conv, params, signin) => { if (signin.status === 'OK') { const payload = conv.user.profile.payload; conv.ask(`I got your account details, ${payload.name}. What do you want to do next?`); } else { conv.ask(`I won't be able to save your data, but what do you want to do next?`); } });
private String clientId = "<your_client_id>"; @ForIntent("actions.intent.TEXT") public ActionResponse text(ActionRequest request) { ResponseBuilder rb = getResponseBuilder(request); return rb.add(new SignIn().setContext("To get your account details")).build(); } @ForIntent("actions.intent.SIGN_IN") public ActionResponse getSignInStatus(ActionRequest request) { ResponseBuilder responseBuilder = getResponseBuilder(request); if (request.isSignInGranted()) { GoogleIdToken.Payload profile = getUserProfile(request.getUser().getIdToken()); responseBuilder.add( "I got your account details, " + profile.get("given_name") + ". What do you want to do next?"); } else { responseBuilder.add("I won't be able to save your data, but what do you want to do next?"); } return responseBuilder.build(); } private GoogleIdToken.Payload getUserProfile(String idToken) { GoogleIdToken.Payload profile = null; try { profile = decodeIdToken(idToken); } catch (Exception e) { LOGGER.error("error decoding idtoken"); LOGGER.error(e.toString()); } return profile; } private GoogleIdToken.Payload decodeIdToken(String idTokenString) throws GeneralSecurityException, IOException { HttpTransport transport = GoogleNetHttpTransport.newTrustedTransport(); JacksonFactory jsonFactory = JacksonFactory.getDefaultInstance(); GoogleIdTokenVerifier verifier = new GoogleIdTokenVerifier.Builder(transport, jsonFactory) // Specify the CLIENT_ID of the app that accesses the backend: .setAudience(Collections.singletonList(this.clientId)) .build(); GoogleIdToken idToken = verifier.verify(idTokenString); return idToken.getPayload(); }
{ "user": { "idToken": "peJaCGci..." }, "device": {}, "surface": { "capabilities": [ { "name": "actions.capability.SCREEN_OUTPUT" }, { "name": "actions.capability.AUDIO_OUTPUT" }, { "name": "actions.capability.MEDIA_RESPONSE_AUDIO" }, { "name": "actions.capability.WEB_BROWSER" } ] }, "conversation": {}, "inputs": [ { "rawInputs": [], "intent": "actions.intent.SIGN_IN", "arguments": [ { "name": "SIGN_IN", "extension": { "@type": "type.googleapis.com/google.actions.v2.SignInValue", "status": "OK" } } ] } ], "availableSurfaces": [ { "capabilities": [ { "name": "actions.capability.SCREEN_OUTPUT" }, { "name": "actions.capability.AUDIO_OUTPUT" }, { "name": "actions.capability.MEDIA_RESPONSE_AUDIO" }, { "name": "actions.capability.WEB_BROWSER" } ] } ] }
จัดการคำขอเข้าถึงข้อมูล
หากต้องการจัดการคำขอเข้าถึงข้อมูล เพียงยืนยันว่าผู้ใช้ยืนยันด้วยรหัส Google แล้ว มีโทเค็นอยู่แล้วในฐานข้อมูล ข้อมูลโค้ดต่อไปนี้แสดง ตัวอย่างวิธีตรวจสอบว่ามีบัญชีผู้ใช้ในฐานข้อมูล Firestore อยู่แล้วหรือไม่
const admin = require('firebase-admin'); const functions = require('firebase-functions'); admin.initializeApp(); const auth = admin.auth(); const db = admin.firestore(); // Save the user in the Firestore DB after successful signin app.intent('Get Sign In', async (conv, params, signin) => { if (signin.status !== 'OK') { return conv.close(`Let's try again next time.`); } const color = conv.data[Fields.COLOR]; const {email} = conv.user; if (!conv.data.uid && email) { try { conv.data.uid = (await auth.getUserByEmail(email)).uid; } catch (e) { if (e.code !== 'auth/user-not-found') { throw e; } // If the user is not found, create a new Firebase auth user // using the email obtained from the Google Assistant conv.data.uid = (await auth.createUser({email})).uid; } } if (conv.data.uid) { conv.user.ref = db.collection('users').doc(conv.data.uid); } conv.close(`I saved ${color} as your favorite color for next time.`); }); // Retrieve the user's favorite color if an account exists, ask if it doesn't. app.intent('Default Welcome Intent', async (conv) => { const {payload} = conv.user.profile; const name = payload ? ` ${payload.given_name}` : ''; conv.ask(`Hi${name}!`); // conv.user.ref contains the id of the record for the user in a Firestore DB if (conv.user.ref) { const doc = await conv.user.ref.get(); if (doc.exists) { const color = doc.data()[Fields.COLOR]; return conv.ask(`Your favorite color was ${color}. ` + 'Tell me a color to update it.'); } } conv.ask(`What's your favorite color?`); });
private class FirestoreManager { private final Firestore db; private final DocumentReference userDocRef; private final String uid; public FirestoreManager(String databaseUrl, String email) throws IOException, FirebaseAuthException { if (FirebaseApp.getApps().isEmpty()) { // Use the application default credentials (works on GCP based hosting). FirebaseOptions options = new FirebaseOptions.Builder() .setCredentials(GoogleCredentials.getApplicationDefault()) .setDatabaseUrl(databaseUrl) .build(); FirebaseApp.initializeApp(options); } this.db = FirestoreClient.getFirestore(); UserRecord userRecord; try { userRecord = FirebaseAuth.getInstance().getUserByEmail(email); } catch (FirebaseAuthException e) { if (e.getErrorCode() == FIREBASE_USER_NOT_FOUND_ERROR) { UserRecord.CreateRequest createRequest = new UserRecord.CreateRequest().setEmail(email); userRecord = FirebaseAuth.getInstance().createUser(createRequest); } else { throw e; } } uid = userRecord.getUid(); userDocRef = db.collection(FIRESTORE_USERS_PATH).document(uid); } public String readUserColor() throws ExecutionException, InterruptedException { ApiFuture<DocumentSnapshot> future = userDocRef.get(); // future.get() blocks on response DocumentSnapshot document = future.get(); if (document.exists()) { return document.get(COLOR_KEY).toString(); } else { return ""; } } public Timestamp writeUserColor(String color) throws ExecutionException, InterruptedException { Map<String, Object> docData = new HashMap<>(); docData.put(COLOR_KEY, color); ApiFuture<WriteResult> future = userDocRef.set(docData); // future.get() blocks on response return future.get().getUpdateTime(); } } @ForIntent("Get Sign In") public ActionResponse getSignIn(ActionRequest request) { LOGGER.info("Get sign in intent start."); ResponseBuilder responseBuilder = getResponseBuilder(request); if (request.isSignInGranted()) { String color = request.getConversationData().get(COLOR_KEY).toString(); GoogleIdToken.Payload profile = getUserProfile(request.getUser().getIdToken()); try { FirestoreManager firestoreManager = new FirestoreManager(DATABASE_URL, profile.getEmail()); saveColor(firestoreManager, color); } catch (Exception e) { LOGGER.error(e.toString()); } responseBuilder .add("I saved " + color + " as your favorite color for next time.") .endConversation(); } else { responseBuilder.add("Let's try again next time"); } LOGGER.info("Get sign in intent end."); return responseBuilder.build(); } private void saveColor(FirestoreManager firestoreManager, String color) { try { Timestamp updateTime = firestoreManager.writeUserColor(color); LOGGER.info(String.format("Update time: %s", updateTime.toString())); } catch (Exception e) { LOGGER.error(e.toString()); } }