Google ile Oturum Açma ile hesap bağlama (Dialogflow)

Asistan için Google ile Oturum Açma, hem hesap bağlama hem de hesap oluşturma açısından kullanıcılara ve geliştiricilere en basit ve en kolay kullanıcı deneyimini sunar. İşleminiz görüşme sırasında kullanıcının Google profiline (kullanıcının adı, e-posta adresi ve profil resmi dahil) erişim isteğinde bulunabilir.

Profil bilgileri, İşleminizde kişiselleştirilmiş bir kullanıcı deneyimi oluşturmak için kullanılabilir. Başka platformlarda çalışan uygulamalarınız varsa ve bu uygulamalar Google ile Oturum Açma özelliğini kullanıyorsa mevcut bir kullanıcının hesabını bulup bağlayabilir, yeni hesap oluşturabilir ve kullanıcıyla doğrudan bir iletişim kanalı oluşturabilirsiniz.

Google ile Oturum Açma ile hesap bağlama işlemi gerçekleştirmek için kullanıcıdan Google profiline erişim izni vermesini istersiniz. Ardından, sisteminizdeki kullanıcıyı tanımlamak için kullanıcının profilindeki bilgileri (ör. e-posta adresi) kullanırsınız.

Google ile Oturum Açma hesap bağlantısını uygulayın

İşleminize Google ile Oturum Açma hesap bağlamasını eklemek için aşağıdaki bölümlerde yer alan adımları izleyin.

Projeyi yapılandırma

Projenizi Google ile Oturum Açma hesap bağlamayı kullanacak şekilde yapılandırmak için aşağıdaki adımları uygulayın:

  1. Actions Console'u açıp bir proje seçin.
  2. Geliştirme sekmesini tıklayın ve Hesap bağlama'yı seçin.
  3. Hesap bağlama'nın yanındaki anahtarı etkinleştirin.
  4. Hesap oluşturma bölümünde Evet'i seçin.
  5. Bağlantı türü bölümünde Google ile Oturum Açma'yı seçin.

  6. Müşteri Bilgileri'ni açın ve Google tarafından İşlemlerinize verilen İstemci Kimliği'nin değerini not edin.

  7. Kaydet'i tıklayın.

Kimlik doğrulama akışını başlatma

Kimlik doğrulama akışını başlatmak için Hesapta Oturum Açma yardımcı amacını kullanın.

Kullanıcı, Google profiline erişmesi için işleminizi yetkilendirdikten sonra, işleminize yönelik her bir sonraki istekte kullanıcının Google profil bilgilerini içeren bir Google kimliği jetonu alırsınız.

Kullanıcının profil bilgilerine erişmek için önce aşağıdakileri yaparak jetonu doğrulamanız ve kodunu çözmeniz gerekir:

  1. Jetonun kodunu çözmek için dilinize yönelik bir JWT kod çözme kitaplığı, jetonun imzasını doğrulamak için ise Google'ın ortak anahtarlarını (JWK veya PEM biçiminde mevcuttur) kullanın.
  2. Jetonu verenin (kodu çözülmüş jetondaki iss alanı) https://accounts.google.com olduğunu ve kitlenin (kodu çözülmüş jetondaki aud alanı), Actions on Google konsolunda projenize atanan Google tarafından Actions'ınıza verilen İstemci Kimliği değeri olduğunu doğrulayın.

Aşağıda kodu çözülmüş bir jeton örneği verilmiştir:

{
  "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"
}

Node.js için Actions on Google istemci kitaplığı'nı veya Java istemci kitaplığı'nı kullanırsanız jetonun doğrulanması ve kodunun çözülmesiyle ilgilenir ve aşağıdaki kod snippet'lerinde gösterildiği gibi profil içeriğine erişmenizi sağlar. Aşağıdaki JSON'un, sırasıyla Dialogflow ve Actions SDK'sı için bir webhook isteğini tanımladığını unutmayın.

Aşağıdaki snippet'ler oturum açmak için Dialogflow'u kullanır:

Node.js
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?`);
  }
});
Java
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();
}
Dialogflow JSON
{
  "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": ""
}

Aşağıdaki snippet'ler oturum açmak için Actions SDK'sını kullanır:

Node.js
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?`);
  }
});
Java
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();
}
Actions SDK JSON
{
  "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"
        }
      ]
    }
  ]
}

Veri erişim isteklerini işleme

Veri erişim isteğini işlemek için Google kimlik jetonu tarafından hak talebinde bulunulan kullanıcının veritabanınızda zaten mevcut olduğunu doğrulamanız yeterlidir. Aşağıdaki kod snippet'inde bir kullanıcı hesabının zaten bir Firestore veritabanında olup olmadığının nasıl kontrol edileceğine dair bir örnek gösterilmektedir.

Node.js
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?`);
});
Java
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());
  }
}