คำแนะนำในการย้ายข้อมูลโฟลว์ Out-Of-Band (OOB)

ภาพรวม

เมื่อวันที่ 16 กุมภาพันธ์ 2022 เราได้ ประกาศ แผนที่จะทำให้การโต้ตอบของ Google OAuth ปลอดภัยยิ่งขึ้นโดยใช้ขั้นตอน OAuth ที่ปลอดภัยกว่า คู่มือนี้จะช่วยให้คุณเข้าใจการเปลี่ยนแปลงและขั้นตอนที่จำเป็นในการ ย้ายข้อมูลจากโฟลว์ OAuth นอกแบนด์ (OOB) ไปยังทางเลือกที่รองรับได้สำเร็จ

ความพยายามนี้เป็นมาตรการป้องกันการโจมตีแบบฟิชชิงและการแอบอ้างเป็นแอป ในระหว่างการโต้ตอบกับปลายทางการให้สิทธิ์ OAuth 2.0 ของ Google

OOB คืออะไร

OAuth นอกแบนด์ (OOB) หรือที่เรียกว่าตัวเลือกการคัดลอก/วางด้วยตนเอง เป็นขั้นตอนเดิม ที่พัฒนาขึ้นเพื่อรองรับไคลเอ็นต์เนทีฟที่ไม่มี URI เปลี่ยนเส้นทาง เพื่อยอมรับข้อมูลเข้าสู่ระบบหลังจากที่ผู้ใช้อนุมัติคำขอความยินยอมของ OAuth ขั้นตอน OOB มีความเสี่ยงต่อฟิชชิงจากระยะไกล และไคลเอ็นต์ต้องย้ายข้อมูล ไปยังวิธีการอื่นเพื่อป้องกันช่องโหว่นี้

เราจะเลิกใช้งานโฟลว์ OOB สำหรับไคลเอ็นต์ทุกประเภท ได้แก่ เว็บแอปพลิเคชัน Android, iOS, Universal Windows Platform (UWP), แอป Chrome, ทีวี และ อุปกรณ์ที่มีการป้อนข้อมูลจำกัด รวมถึงแอปบนเดสก์ท็อป

วันที่สำคัญในการปฏิบัติตามข้อกำหนด

  • 28 กุมภาพันธ์ 2022 - บล็อกการใช้งาน OAuth ใหม่สำหรับขั้นตอน OOB
  • 5 กันยายน 2022 - ข้อความเตือนที่แสดงต่อผู้ใช้อาจแสดงต่อคำขอ OAuth ที่ไม่เป็นไปตามข้อกำหนด
  • 3 ตุลาคม 2022 - เลิกใช้งานโฟลว์ OOB สำหรับไคลเอ็นต์ OAuth ที่สร้าง ก่อนวันที่ 28 กุมภาพันธ์ 2022
  • 31 มกราคม 2023 - ไคลเอ็นต์ที่มีอยู่ทั้งหมดจะถูกบล็อก (รวมถึงไคลเอ็นต์ที่ได้รับการยกเว้น)

ระบบจะแสดงข้อความแสดงข้อผิดพลาดที่ผู้ใช้เห็นสำหรับคำขอที่ไม่เป็นไปตามข้อกำหนด ข้อความจะแจ้งให้ผู้ใช้ทราบว่าระบบบล็อกแอปในขณะที่ แสดงอีเมลสนับสนุนที่คุณลงทะเบียนไว้ใน หน้าจอขอความยินยอม OAuth ในคอนโซล Google API

ขั้นตอนหลัก 2 ขั้นตอนในการย้ายข้อมูลให้เสร็จสมบูรณ์มีดังนี้
  1. ตรวจสอบว่าคุณได้รับผลกระทบหรือไม่
  2. โปรดเปลี่ยนไปใช้ทางเลือกอื่นที่มีความปลอดภัยมากขึ้นหากคุณได้รับผลกระทบ

ตรวจสอบว่าคุณได้รับผลกระทบหรือไม่

การเลิกใช้งานนี้มีผลกับแอปเวอร์ชันที่ใช้งานจริงเท่านั้น (เช่น แอปที่มี สถานะการเผยแพร่เป็น ในเวอร์ชันที่ใช้งานจริง) ขั้นตอนการทำงานจะยังคงใช้ได้กับแอปที่มี สถานะการเผยแพร่ "กำลังทดสอบ"

ตรวจสอบสถานะการเผยแพร่ใน OAuth Branding page ของ Google Cloud Console และไปยังขั้นตอนถัดไปหากคุณใช้ โฟลว์ OOB ในโปรเจ็กต์ที่มีสถานะการเผยแพร่เป็น "ใช้งานจริง"

วิธีตรวจสอบว่าแอปใช้โฟลว์ OOB หรือไม่

ตรวจสอบโค้ดของแอปหรือ การเรียกเครือข่ายขาออก (ในกรณีที่ แอปใช้ไลบรารี OAuth) เพื่อพิจารณาว่า Google OAuth คำขอให้สิทธิ์ ที่แอปของคุณสร้างขึ้นใช้ค่า URI การเปลี่ยนเส้นทาง OOB หรือไม่

ตรวจสอบโค้ดของแอปพลิเคชัน

ตรวจสอบส่วนของโค้ดแอปพลิเคชันที่คุณทำการเรียกไปยัง Google OAuth ปลายทางการให้สิทธิ์ และพิจารณาว่าพารามิเตอร์ redirect_uri มีค่าใดค่าหนึ่งต่อไปนี้หรือไม่
  • redirect_uri=urn:ietf:wg:oauth:2.0:oob
  • redirect_uri=urn:ietf:wg:oauth:2.0:oob:auto
  • redirect_uri=oob
คำขอการเปลี่ยนเส้นทาง OOB ตัวอย่างจะมีลักษณะดังตัวอย่างด้านล่าง
https://accounts.google.com/o/oauth2/v2/auth?
response_type=code&
scope=<SCOPES>&
state=<STATE>&
redirect_uri=urn:ietf:wg:oauth:2.0:oob&
client_id=<CLIENT_ID>

ตรวจสอบการเรียกเครือข่ายขาออก

วิธีการตรวจสอบการเรียกเครือข่ายจะแตกต่างกันไปตามประเภทไคลเอ็นต์ของแอปพลิเคชัน
ขณะตรวจสอบการเรียกเครือข่าย ให้มองหาคำขอที่ส่งไปยัง Google OAuth ปลายทางการให้สิทธิ์ และพิจารณาว่าพารามิเตอร์ redirect_uri มีค่าใดค่าหนึ่งต่อไปนี้หรือไม่
  • redirect_uri=urn:ietf:wg:oauth:2.0:oob
  • redirect_uri=urn:ietf:wg:oauth:2.0:oob:auto
  • redirect_uri=oob
คำขอการเปลี่ยนเส้นทาง OOB ตัวอย่างจะมีลักษณะดังตัวอย่างด้านล่าง
https://accounts.google.com/o/oauth2/v2/auth?
response_type=code&
scope=<SCOPES>&
state=<STATE>&
redirect_uri=urn:ietf:wg:oauth:2.0:oob&
client_id=<CLIENT_ID>

ย้ายข้อมูลไปยังทางเลือกที่ปลอดภัย

ไคลเอ็นต์บนอุปกรณ์เคลื่อนที่ (Android / iOS)

หากพิจารณาแล้วว่าแอปของคุณใช้โฟลว์ OOB กับไคลเอ็นต์ OAuth ประเภท Android หรือ iOS คุณควรย้ายข้อมูลไปใช้ SDK ที่แนะนํา (Android, iOS)

SDK ช่วยให้เข้าถึง Google APIs ได้ง่ายและจัดการการเรียกไปยัง ปลายทางการให้สิทธิ์ OAuth 2.0 ของ Google ทั้งหมด

ลิงก์เอกสารประกอบด้านล่างนี้จะให้ข้อมูลเกี่ยวกับวิธีใช้ SDK ที่แนะนําเพื่อเข้าถึง Google APIs โดยไม่ต้องใช้ URI เปลี่ยนเส้นทาง OOB

เข้าถึง Google APIs ใน Android

การเข้าถึงฝั่งไคลเอ็นต์

ตัวอย่างต่อไปนี้แสดงวิธีเข้าถึง Google APIs ในฝั่งไคลเอ็นต์บน Android โดยใช้ไลบรารี Android ของบริการระบุตัวตนของ Google ที่แนะนำ

  List requestedScopes = Arrays.asList(DriveScopes.DRIVE_APPDATA);
    AuthorizationRequest authorizationRequest = AuthorizationRequest.builder().setRequestedScopes(requestedScopes).build();
    Identity.getAuthorizationClient(activity)
            .authorize(authorizationRequest)
            .addOnSuccessListener(
                authorizationResult -> {
                  if (authorizationResult.hasResolution()) {
                    // Access needs to be granted by the user
                    PendingIntent pendingIntent = authorizationResult.getPendingIntent();
                    try {
    startIntentSenderForResult(pendingIntent.getIntentSender(),
    REQUEST_AUTHORIZE, null, 0, 0, 0, null);
                    } catch (IntentSender.SendIntentException e) {
                    Log.e(TAG, "Couldn't start Authorization UI: " + e.getLocalizedMessage());
                    }
                  } else {
                    // Access already granted, continue with user action
                    saveToDriveAppFolder(authorizationResult);
                  }
                })
            .addOnFailureListener(e -> Log.e(TAG, "Failed to authorize", e));

ส่ง authorizationResult ไปยังเมธอดที่กำหนดเพื่อบันทึกเนื้อหาลงในโฟลเดอร์ไดรฟ์ของผู้ใช้ authorizationResult มีเมธอด getAccessToken() ที่แสดงผลโทเค็นเพื่อการเข้าถึง

การเข้าถึงฝั่งเซิร์ฟเวอร์ (ออฟไลน์)
ตัวอย่างต่อไปนี้แสดงวิธีเข้าถึง Google APIs ในฝั่งเซิร์ฟเวอร์บน Android
  List requestedScopes = Arrays.asList(DriveScopes.DRIVE_APPDATA);
    AuthorizationRequest authorizationRequest = AuthorizationRequest.builder()
    .requestOfflineAccess(webClientId)
            .setRequestedScopes(requestedScopes)
            .build();
    Identity.getAuthorizationClient(activity)
            .authorize(authorizationRequest)
            .addOnSuccessListener(
                authorizationResult -> {
                  if (authorizationResult.hasResolution()) {
                    // Access needs to be granted by the user
                    PendingIntent pendingIntent = authorizationResult.getPendingIntent();
                    try {
    startIntentSenderForResult(pendingIntent.getIntentSender(),
    REQUEST_AUTHORIZE, null, 0, 0, 0, null);
                    } catch (IntentSender.SendIntentException e) {
                    Log.e(TAG, "Couldn't start Authorization UI: " + e.getLocalizedMessage());
                    }
                  } else {
                    String authCode = authorizationResult.getServerAuthCode();
                  }
                })
            .addOnFailureListener(e -> Log.e(TAG, "Failed to authorize", e));

authorizationResult มีเมธอด getServerAuthCode() ที่แสดงรหัสการให้สิทธิ์ซึ่งคุณสามารถส่งไปยัง แบ็กเอนด์เพื่อรับโทเค็นการเข้าถึงและการรีเฟรช

เข้าถึง Google APIs ในแอป iOS

การเข้าถึงฝั่งไคลเอ็นต์

ตัวอย่างด้านล่างแสดงวิธีเข้าถึง Google APIs ในฝั่งไคลเอ็นต์บน iOS

user.authentication.do { authentication, error in
  guard error == nil else { return }
  guard let authentication = authentication else { return }
  
  // Get the access token to attach it to a REST or gRPC request.
  let accessToken = authentication.accessToken
  
  // Or, get an object that conforms to GTMFetcherAuthorizationProtocol for
  // use with GTMAppAuth and the Google APIs client library.
  let authorizer = authentication.fetcherAuthorizer()
}

ใช้โทเค็นการเข้าถึงเพื่อเรียก API โดยการรวมโทเค็นการเข้าถึงไว้ใน ส่วนหัวของคำขอ REST หรือ gRPC (Authorization: Bearer ACCESS_TOKEN) หรือใช้เครื่องมือให้สิทธิ์ของตัวดึงข้อมูล (GTMFetcherAuthorizationProtocol) กับ ไลบรารีของไคลเอ็นต์ Google APIs สำหรับ Objective-C สำหรับ REST

ดู คู่มือการเข้าถึงฝั่งไคลเอ็นต์เกี่ยวกับวิธีเข้าถึง Google API ในฝั่งไคลเอ็นต์ เกี่ยวกับวิธีเข้าถึง Google APIs ในฝั่งไคลเอ็นต์

การเข้าถึงฝั่งเซิร์ฟเวอร์ (ออฟไลน์)
ตัวอย่างด้านล่างแสดงวิธีเข้าถึง Google API ในฝั่งเซิร์ฟเวอร์เพื่อรองรับไคลเอ็นต์ iOS
GIDSignIn.sharedInstance.signIn(with: signInConfig, presenting: self) { user, error in
  guard error == nil else { return }
  guard let user = user else { return }
  
  // request a one-time authorization code that your server exchanges for
  // an access token and refresh token
  let authCode = user.serverAuthCode
}

โปรดอ่านคู่มือการเข้าถึงฝั่งเซิร์ฟเวอร์ เกี่ยวกับวิธีเข้าถึง Google API จากฝั่งเซิร์ฟเวอร์

ไคลเอ็นต์แอป Chrome

หากพิจารณาแล้วว่าแอปของคุณใช้ขั้นตอน OOB ในไคลเอ็นต์แอป Chrome คุณควรเปลี่ยนไปใช้ Chrome Identity API

ตัวอย่างด้านล่างแสดงวิธีรับรายชื่อติดต่อของผู้ใช้ทั้งหมดโดยไม่ต้องใช้ URI เปลี่ยนเส้นทาง OOB

window.onload = function() {
  document.querySelector('button').addEventListener('click', function() {

  
  // retrieve access token
  chrome.identity.getAuthToken({interactive: true}, function(token) {
  
  // ..........


  // the example below shows how to use a retrieved access token with an appropriate scope
  // to call the Google People API contactGroups.get endpoint

  fetch(
    'https://people.googleapis.com/v1/contactGroups/all?maxMembers=20&key=API_KEY',
    init)
    .then((response) => response.json())
    .then(function(data) {
      console.log(data)
    });
   });
 });
};

โปรดดูข้อมูลเพิ่มเติมเกี่ยวกับวิธีเข้าถึงผู้ใช้ที่ตรวจสอบสิทธิ์และเรียกปลายทางของ Google ด้วย Chrome Identity API ใน คู่มือ Chrome Identity API

เว็บแอปพลิเคชัน

หากพิจารณาแล้วว่าแอปของคุณใช้ขั้นตอน OOB สำหรับเว็บแอปพลิเคชัน คุณควรย้ายข้อมูลไปใช้ไลบรารีของไคลเอ็นต์ Google API อย่างใดอย่างหนึ่งของเรา ไลบรารีของไคลเอ็นต์ สำหรับภาษาโปรแกรมต่างๆ แสดงอยู่ที่นี่

ไลบรารีช่วยให้เข้าถึง Google APIs และจัดการการเรียกไปยัง ปลายทางของ Google ทั้งหมดได้ง่าย

การเข้าถึงฝั่งเซิร์ฟเวอร์ (ออฟไลน์)
โหมดการเข้าถึงฝั่งเซิร์ฟเวอร์ (ออฟไลน์) กําหนดให้คุณต้องทําสิ่งต่อไปนี้
  • สร้างเซิร์ฟเวอร์และกำหนดปลายทางที่เข้าถึงได้แบบสาธารณะ (URI เปลี่ยนเส้นทาง) เพื่อรับรหัสการให้สิทธิ์
  • กำหนดค่า URI การเปลี่ยนเส้นทาง ใน Clients page ของ Google Cloud Console

ข้อมูลโค้ดด้านล่างแสดงตัวอย่าง NodeJS ของการใช้ Google ไดรฟ์ API เพื่อแสดงไฟล์ Google ไดรฟ์ของผู้ใช้ในฝั่งเซิร์ฟเวอร์โดยไม่ต้องใช้ OOB URI การเปลี่ยนเส้นทาง

async function main() {
  const server = http.createServer(async function (req, res) {

  if (req.url.startsWith('/oauth2callback')) {
    let q = url.parse(req.url, true).query;

    if (q.error) {
      console.log('Error:' + q.error);
    } else {
      
      // Get access and refresh tokens (if access_type is offline)
      let { tokens } = await oauth2Client.getToken(q.code);
      oauth2Client.setCredentials(tokens);

      // Example of using Google Drive API to list filenames in user's Drive.
      const drive = google.drive('v3');
      drive.files.list({
        auth: oauth2Client,
        pageSize: 10,
        fields: 'nextPageToken, files(id, name)',
      }, (err1, res1) => {
        // TODO(developer): Handle response / error.
      });
    }
  }
}

โปรดอ่าน คู่มือเว็บแอปฝั่งเซิร์ฟเวอร์ เกี่ยวกับวิธีเข้าถึง Google APIs จากฝั่งเซิร์ฟเวอร์

การเข้าถึงฝั่งไคลเอ็นต์

ข้อมูลโค้ดด้านล่างใน JavaScript แสดงตัวอย่างการใช้ Google API เพื่อเข้าถึงกิจกรรมในปฏิทินของผู้ใช้ในฝั่งไคลเอ็นต์


// initTokenClient() initializes a new token client with your
// web app's client ID and the scope you need access to

const client = google.accounts.oauth2.initTokenClient({
  client_id: 'YOUR_GOOGLE_CLIENT_ID',
  scope: 'https://www.googleapis.com/auth/calendar.readonly',
  
  // callback function to handle the token response
  callback: (tokenResponse) => {
    if (tokenResponse && tokenResponse.access_token) { 
      gapi.client.setApiKey('YOUR_API_KEY');
      gapi.client.load('calendar', 'v3', listUpcomingEvents);
    }
  },
});

function listUpcomingEvents() {
  gapi.client.calendar.events.list(...);
}

ดู คู่มือเว็บแอปฝั่งไคลเอ็นต์ เกี่ยวกับวิธีเข้าถึง Google API จากฝั่งไคลเอ็นต์

ไคลเอ็นต์บนเดสก์ท็อป

หากพิจารณาแล้วว่าแอปของคุณใช้โฟลว์ OOB ในไคลเอ็นต์เดสก์ท็อป คุณควรย้ายข้อมูลไปใช้ โฟลว์ที่อยู่ IP ของ Loopback (localhost หรือ 127.0.0.1)