ระบบ Google OAuth 2.0 รองรับการโต้ตอบแบบเซิร์ฟเวอร์ต่อเซิร์ฟเวอร์ เช่น การโต้ตอบระหว่างเว็บแอปพลิเคชันและบริการของ Google ในสถานการณ์นี้ คุณต้องมีบัญชีบริการ ซึ่งเป็นบัญชีที่เป็นของแอปพลิเคชัน แทนที่จะเป็นของผู้ใช้ปลายทางแต่ละคน แอปพลิเคชันของคุณเรียกใช้ Google API ในนามของบัญชีบริการ ดังนั้นผู้ใช้จึงไม่เกี่ยวข้องโดยตรง สถานการณ์นี้บางครั้งเรียกว่า "OAuth แบบ 2 ทาง" หรือ "2LO" (คำที่เกี่ยวข้อง "OAuth แบบ 3 ทาง" หมายถึงสถานการณ์ที่แอปพลิเคชันของคุณเรียกใช้ Google API ในนามของผู้ใช้ปลายทาง บางครั้งต้องได้รับความยินยอมจากผู้ใช้)
โดยปกติแล้ว แอปพลิเคชันจะใช้บัญชีบริการเมื่อแอปพลิเคชันใช้ Google API เพื่อทำงานกับข้อมูลของตัวเองแทนที่จะใช้ข้อมูลของผู้ใช้ เช่น แอปพลิเคชันที่ใช้ Google Cloud Datastore เพื่อความต่อเนื่องของข้อมูลจะใช้บัญชีบริการเพื่อตรวจสอบสิทธิ์การเรียกใช้ไปยัง Google Cloud Datastore API
นอกจากนี้ ผู้ดูแลระบบโดเมน Google Workspace ยังให้สิทธิ์บัญชีบริการทั่วทั้งโดเมนเข้าถึงข้อมูลผู้ใช้ในนามของผู้ใช้ในโดเมนได้ด้วย
เอกสารนี้จะอธิบายวิธีที่แอปพลิเคชันจะดำเนินการขั้นตอน OAuth 2.0 แบบเซิร์ฟเวอร์ต่อเซิร์ฟเวอร์ให้เสร็จสมบูรณ์โดยใช้ไลบรารีของไคลเอ็นต์ Google APIs (แนะนำ) หรือ HTTP
ภาพรวม
หากต้องการรองรับการโต้ตอบแบบเซิร์ฟเวอร์ต่อเซิร์ฟเวอร์ ก่อนอื่นให้สร้างบัญชีบริการสำหรับโปรเจ็กต์ของคุณใน API Consoleหากต้องการเข้าถึงข้อมูลผู้ใช้สำหรับผู้ใช้ในบัญชี Google Workspace ให้มอบสิทธิ์การเข้าถึงทั่วทั้งโดเมนสำหรับบัญชีบริการ
จากนั้นแอปพลิเคชันจะเตรียมการเรียก API ที่ได้รับอนุญาตโดยใช้ข้อมูลเข้าสู่ระบบของบัญชีบริการเพื่อขอโทเค็นเพื่อการเข้าถึงจากเซิร์ฟเวอร์การตรวจสอบสิทธิ์ OAuth 2.0
สุดท้ายนี้ แอปพลิเคชันของคุณสามารถใช้โทเค็นเพื่อการเข้าถึงเพื่อเรียกใช้ Google API
การสร้างบัญชีบริการ
ข้อมูลเข้าสู่ระบบของบัญชีบริการประกอบด้วยอีเมลที่สร้างขึ้นซึ่งไม่ซ้ำกัน และมีคู่คีย์สาธารณะ/ส่วนตัวอย่างน้อย 1 คู่ หากเปิดใช้การมอบสิทธิ์ทั่วทั้งโดเมน รหัสไคลเอ็นต์ก็จะเป็นส่วนหนึ่งของข้อมูลเข้าสู่ระบบของบัญชีบริการด้วย
หากแอปพลิเคชันของคุณทำงานบน Google App Engine ระบบจะสร้างบัญชีบริการโดยอัตโนมัติเมื่อคุณสร้างโปรเจ็กต์
หากแอปพลิเคชันของคุณทำงานบน Google Compute Engine บัญชีบริการจะมีการตั้งค่าโดยอัตโนมัติเมื่อคุณสร้างโปรเจ็กต์ด้วย แต่คุณต้องระบุขอบเขตที่แอปพลิเคชันต้องการเข้าถึงเมื่อสร้างอินสแตนซ์ Google Compute Engine ดูข้อมูลเพิ่มเติมได้ที่การเตรียมอินสแตนซ์เพื่อใช้บัญชีบริการ
หากแอปพลิเคชันของคุณไม่ทำงานใน Google App Engine หรือ Google Compute Engine คุณจะต้องขอรับข้อมูลเข้าสู่ระบบเหล่านี้ใน Google API Consoleหากต้องการสร้างข้อมูลเข้าสู่ระบบของบัญชีบริการหรือดูข้อมูลเข้าสู่ระบบสาธารณะที่คุณสร้างไว้แล้ว ให้ทำดังนี้
ขั้นแรก สร้างบัญชีบริการ:
- เปิด Service accounts page
- If prompted, select a project, or create a new one.
- คลิก สร้างบัญชีบริการ
- ภายใต้ รายละเอียดบัญชีบริการ ให้พิมพ์ชื่อ ID และคำอธิบายสำหรับบัญชีบริการ จากนั้นคลิก สร้างและดำเนิน การต่อ
- ทางเลือก: ภายใต้ ให้สิทธิ์การเข้าถึงบัญชีบริการนี้กับโครงการ เลือกบทบาท IAM เพื่อมอบให้กับบัญชีบริการ
- คลิกดำเนินการ ต่อ
- ไม่บังคับ: ภายใต้ ให้สิทธิ์ผู้ใช้เข้าถึงบัญชีบริการนี้ ให้ เพิ่มผู้ใช้หรือกลุ่มที่ได้รับอนุญาตให้ใช้และจัดการบัญชีบริการ
- คลิก เสร็จสิ้น
จากนั้น สร้างรหัสบัญชีบริการ:
- คลิกที่อยู่อีเมลสำหรับบัญชีบริการที่คุณสร้างขึ้น
- คลิกแท็บ คีย์
- ในรายการดรอปดาวน์ เพิ่มคีย์ เลือก สร้างคีย์ใหม่
- คลิก สร้าง
คู่คีย์สาธารณะ/ส่วนตัวใหม่ของคุณถูกสร้างขึ้นและดาวน์โหลดลงในเครื่องของคุณ มันทำหน้าที่เป็นสำเนาเดียวของคีย์ส่วนตัว คุณมีหน้าที่รับผิดชอบในการจัดเก็บอย่างปลอดภัย หากคุณทำคู่คีย์นี้หาย คุณจะต้องสร้างคู่คีย์ใหม่
คุณกลับไปที่ API Console ได้ทุกเมื่อเพื่อดูอีเมล ลายนิ้วมือคีย์สาธารณะ และข้อมูลอื่นๆ หรือสร้างคู่คีย์สาธารณะ/คีย์ส่วนตัวเพิ่มเติม โปรดดูรายละเอียดเพิ่มเติมเกี่ยวกับข้อมูลเข้าสู่ระบบของบัญชีบริการใน API Consoleที่บัญชีบริการในไฟล์ความช่วยเหลือ API Console
จดอีเมลของบัญชีบริการและเก็บไฟล์คีย์ส่วนตัวของบัญชีบริการไว้ในตำแหน่งที่แอปพลิเคชันของคุณเข้าถึงได้ แอปพลิเคชันต้องใช้คีย์ในการเรียก API ที่ได้รับอนุญาต
การมอบสิทธิ์ทั่วทั้งโดเมนให้กับบัญชีบริการ
ผู้ดูแลระบบ Workspace ขององค์กรสามารถใช้บัญชี Google Workspace เพื่อให้สิทธิ์แอปพลิเคชันในการเข้าถึงข้อมูลผู้ใช้ Workspace ในนามของผู้ใช้ในโดเมน Google Workspace ได้ เช่น แอปพลิเคชันที่ใช้ Google Calendar API เพื่อเพิ่มกิจกรรมในปฏิทินของผู้ใช้ทุกคนในโดเมน Google Workspace จะใช้บัญชีบริการเพื่อเข้าถึง Google Calendar API ในนามของผู้ใช้ บางครั้งการให้สิทธิ์บัญชีบริการเพื่อเข้าถึงข้อมูลในนามของผู้ใช้ในโดเมนอาจเรียกว่า "การมอบสิทธิ์ทั่วทั้งโดเมน" ให้กับบัญชีบริการ
หากต้องการมอบสิทธิ์ทั่วทั้งโดเมนให้กับบัญชีบริการ ผู้ดูแลระบบขั้นสูงของโดเมน Google Workspace จะต้องทำตามขั้นตอนต่อไปนี้
- จาก คอนโซลผู้ดูแลระบบของโดเมน Google Workspace ให้ไปที่เมนูหลัก > ความปลอดภัย > การเข้าถึงและการควบคุมข้อมูล > การควบคุม API
- เลือกจัดการการมอบสิทธิ์ทั่วทั้งโดเมนในแผงการมอบสิทธิ์ทั่วทั้งโดเมน
- คลิกเพิ่มใหม่
- กรอกรหัสไคลเอ็นต์ของบัญชีบริการในช่องรหัสไคลเอ็นต์ คุณดูรหัสไคลเอ็นต์ของบัญชีบริการได้ใน Service accounts page
- ในช่องขอบเขต OAuth (คั่นด้วยคอมมา) ให้ป้อนรายการขอบเขตที่แอปพลิเคชันควรได้รับสิทธิ์ในการเข้าถึง เช่น หากแอปพลิเคชันต้องการสิทธิ์เข้าถึง Google Drive API และ Google Calendar API ทั้งโดเมน ให้ป้อน https://www.googleapis.com/auth/drive, https://www.googleapis.com/auth/calendar
- คลิกให้สิทธิ์
ขณะนี้แอปพลิเคชันของคุณมีสิทธิ์เรียก API ในฐานะผู้ใช้ในโดเมน Workspace ของคุณ (เพื่อ "แอบอ้างเป็นผู้ใช้") แล้ว เมื่อเตรียมเรียก API ที่มอบสิทธิ์เหล่านี้ คุณจะต้องระบุผู้ใช้ที่จะแอบอ้างเป็นบุคคลอื่นอย่างชัดแจ้ง
กำลังเตรียมเรียก API ที่ได้รับมอบสิทธิ์
Java
หลังจากได้รับอีเมลไคลเอ็นต์และคีย์ส่วนตัวจาก API Consoleแล้ว ให้ใช้ไลบรารีของไคลเอ็นต์ Google APIs สำหรับ Java เพื่อสร้างออบเจ็กต์ GoogleCredential
จากข้อมูลเข้าสู่ระบบของบัญชีบริการและขอบเขตที่แอปพลิเคชันต้องการเข้าถึง เช่น
import com.google.api.client.googleapis.auth.oauth2.GoogleCredential; import com.google.api.services.sqladmin.SQLAdminScopes; // ... GoogleCredential credential = GoogleCredential.fromStream(new FileInputStream("MyProject-1234.json")) .createScoped(Collections.singleton(SQLAdminScopes.SQLSERVICE_ADMIN));
หากจะพัฒนาแอปใน Google Cloud Platform คุณอาจใช้ข้อมูลเข้าสู่ระบบเริ่มต้นของแอปพลิเคชันแทน ซึ่งจะช่วยให้ขั้นตอนนี้ง่ายขึ้น
มอบสิทธิ์ทั่วทั้งโดเมน
หากคุณมอบสิทธิ์การเข้าถึงบัญชีบริการทั่วทั้งโดเมนและต้องการแอบอ้างบัญชีผู้ใช้ ให้ระบุอีเมลของบัญชีผู้ใช้ด้วยเมธอด createDelegated
ของออบเจ็กต์ GoogleCredential
เช่น
GoogleCredential credential = GoogleCredential.fromStream(new FileInputStream("MyProject-1234.json")) .createScoped(Collections.singleton(SQLAdminScopes.SQLSERVICE_ADMIN)) .createDelegated("workspace-user@example.com");
โค้ดด้านบนใช้ออบเจ็กต์ GoogleCredential
เพื่อเรียกใช้เมธอด createDelegated()
อาร์กิวเมนต์ของเมธอด createDelegated()
ต้องเป็นผู้ใช้ที่เป็นของบัญชี Workspace ของคุณ โค้ดที่ส่งคำขอจะใช้ข้อมูลเข้าสู่ระบบนี้เพื่อเรียกใช้ Google API โดยใช้บัญชีบริการของคุณ
Python
หลังจากได้รับอีเมลและคีย์ส่วนตัวของไคลเอ็นต์จาก API Consoleแล้ว ให้ใช้ไลบรารีของไคลเอ็นต์ Google APIs สำหรับ Python เพื่อทำตามขั้นตอนต่อไปนี้
- สร้างออบเจ็กต์
Credentials
จากข้อมูลเข้าสู่ระบบของบัญชีบริการและขอบเขตที่แอปพลิเคชันต้องการสิทธิ์เข้าถึง ตัวอย่างเช่นfrom google.oauth2 import service_account SCOPES = ['https://www.googleapis.com/auth/sqlservice.admin'] SERVICE_ACCOUNT_FILE = '/path/to/service.json' credentials = service_account.Credentials.from_service_account_file( SERVICE_ACCOUNT_FILE, scopes=SCOPES)
หากจะพัฒนาแอปใน Google Cloud Platform คุณอาจใช้ข้อมูลเข้าสู่ระบบเริ่มต้นของแอปพลิเคชันแทน ซึ่งจะช่วยให้ขั้นตอนนี้ง่ายขึ้น
- มอบสิทธิ์ทั่วทั้งโดเมน
หากคุณมอบสิทธิ์ทั่วทั้งโดเมนเพื่อเข้าถึงบัญชีบริการและต้องการแอบอ้างเป็นบัญชีผู้ใช้ ให้ใช้เมธอด
with_subject
ของออบเจ็กต์ServiceAccountCredentials
ที่มีอยู่ เช่นdelegated_credentials = credentials.with_subject('user@example.org')
ใช้ออบเจ็กต์ข้อมูลเข้าสู่ระบบเพื่อเรียกใช้ Google API ในแอปพลิเคชันของคุณ
HTTP/REST
หลังจากได้รับรหัสไคลเอ็นต์และคีย์ส่วนตัวจาก API Consoleแล้ว แอปพลิเคชันจะต้องทำตามขั้นตอนต่อไปนี้
- สร้าง JSON Web Token (JWT ออกเสียงว่า "jot") ซึ่งประกอบด้วยส่วนหัว ชุดการอ้างสิทธิ์ และลายเซ็น
- ขอโทเค็นเพื่อการเข้าถึงจากเซิร์ฟเวอร์การให้สิทธิ์ของ Google OAuth 2.0
- จัดการการตอบสนอง JSON ที่เซิร์ฟเวอร์การให้สิทธิ์ส่งกลับมา
ส่วนต่อไปนี้อธิบายวิธีดำเนินการขั้นตอนเหล่านี้
หากการตอบกลับมีโทเค็นเพื่อการเข้าถึง คุณจะใช้โทเค็นเพื่อการเข้าถึงเพื่อเรียก Google API ได้ (หากการตอบกลับไม่มีโทเค็นเพื่อการเข้าถึง ระบบอาจสร้างคำขอ JWT และโทเค็นอย่างไม่ถูกต้อง หรือบัญชีบริการอาจไม่มีสิทธิ์เข้าถึงขอบเขตที่ขอ)
เมื่อโทเค็นเพื่อการเข้าถึงหมดอายุ แอปพลิเคชันจะสร้าง JWT อื่น ลงนาม และขอโทเค็นเพื่อการเข้าถึงใหม่
![แอปพลิเคชันเซิร์ฟเวอร์ใช้ JWT เพื่อขอโทเค็นจากเซิร์ฟเวอร์การให้สิทธิ์ของ Google จากนั้นจึงใช้โทเค็นเพื่อเรียกใช้ปลายทาง Google API ไม่มีผู้ใช้ปลายทางมาเกี่ยวข้อง](https://developers-dot-devsite-v2-prod.appspot.com/static/identity/protocols/oauth2/images/flows/jwt.png?authuser=4&hl=th)
เนื้อหาที่เหลือของส่วนนี้จะอธิบายรายละเอียดในการสร้าง JWT, การลงนาม JWT, การสร้างคำขอโทเค็นเพื่อการเข้าถึง และการจัดการการตอบกลับ
การสร้าง JWT
JWT ประกอบด้วย 3 ส่วน ได้แก่ ส่วนหัว ชุดการอ้างสิทธิ์ และลายเซ็น ส่วนหัวและชุดการอ้างสิทธิ์เป็นออบเจ็กต์ JSON ออบเจ็กต์ JSON เหล่านี้ได้รับการทำให้เป็นอนุกรมเป็น UTF-8 ไบต์ จากนั้นเข้ารหัสด้วยการเข้ารหัส Base64url การเข้ารหัสนี้ช่วยให้สามารถรับมือกับการเปลี่ยนแปลงการเข้ารหัสที่เกิดจากการเข้ารหัสซ้ำๆ ส่วนหัว ชุดการอ้างสิทธิ์ และลายเซ็นจะต่อกันด้วยอักขระเครื่องหมายจุด (.
)
JWT ประกอบด้วยข้อมูลต่อไปนี้
{Base64url encoded header}.{Base64url encoded claim set}.{Base64url encoded signature}
สตริงฐานสำหรับลายเซ็นมีดังนี้
{Base64url encoded header}.{Base64url encoded claim set}
การสร้างส่วนหัว JWT
ส่วนหัวประกอบด้วย 3 ช่องที่ระบุอัลกอริทึมการรับรอง รูปแบบของการยืนยัน และ[รหัสคีย์ของคีย์บัญชีบริการ](https://cloud.google.com/iam/docs/reference/rest/v1/projects.serviceAccounts.keys) ที่ใช้ในการลงนาม JWT คุณต้องระบุอัลกอริทึมและรูปแบบ โดยแต่ละช่องจะมีค่าเพียงค่าเดียว เมื่อมีการเปิดตัวอัลกอริทึมและรูปแบบเพิ่มเติม ส่วนหัวนี้จะเปลี่ยนไปตามนั้น คุณจะใส่รหัสคีย์หรือไม่ก็ได้ หากระบุรหัสคีย์ไม่ถูกต้อง GCP จะลองใช้คีย์ทั้งหมดที่เชื่อมโยงกับบัญชีบริการเพื่อยืนยันโทเค็น และปฏิเสธโทเค็นหากไม่พบคีย์ที่ถูกต้อง Google ขอสงวนสิทธิ์ในการปฏิเสธโทเค็นที่มีรหัสคีย์ไม่ถูกต้องในอนาคต
บัญชีบริการใช้อัลกอริทึม RSA SHA-256 และรูปแบบโทเค็น JWT ดังนั้น การแสดงส่วนหัวในรูปแบบ JSON จึงเป็นดังนี้
{"alg":"RS256","typ":"JWT", "kid":"370ab79b4513eb9bad7c9bd16a95cb76b5b2a56a"}
การแทน Base64url มีดังนี้
eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsICJraWQiOiIzNzBhYjc5YjQ1MTNlYjliYWQ3YzliZDE2YTk1Y2I3NmI1YjJhNTZhIn0=
การก่อตั้งชุดการอ้างสิทธิ์ JWT
ชุดการอ้างสิทธิ์ JWT มีข้อมูลเกี่ยวกับ JWT รวมถึงสิทธิ์ที่มีการขอ (ขอบเขต), เป้าหมายของโทเค็น, ผู้ออก, เวลาที่ออกโทเค็น และอายุการใช้งานของโทเค็น ช่องส่วนใหญ่เป็นช่องที่จำเป็น ชุดการอ้างสิทธิ์ JWT เป็นออบเจ็กต์ JSON และใช้ในการคำนวณลายเซ็น เช่นเดียวกับส่วนหัว JWT
การอ้างสิทธิ์ที่จำเป็น
การอ้างสิทธิ์ที่จำเป็นในชุดการอ้างสิทธิ์ JWT จะแสดงอยู่ด้านล่าง เนื้อหาเหล่านี้อาจปรากฏในลำดับใดก็ได้ในชุดการอ้างสิทธิ์
ชื่อ | คำอธิบาย |
---|---|
iss |
อีเมลของบัญชีบริการ |
scope |
รายการสิทธิ์ที่คั่นด้วยช่องว่างซึ่งแอปพลิเคชันส่งคำขอ |
aud |
ตัวบ่งชี้ของเป้าหมายที่มุ่งหวังของการยืนยัน เมื่อส่งคำขอโทเค็นเพื่อการเข้าถึงค่านี้จะเป็น https://oauth2.googleapis.com/token เสมอ |
exp |
เวลาหมดอายุของการยืนยัน ระบุเป็นวินาทีตั้งแต่ 00:00:00 น. UTC วันที่ 1 มกราคม 1970 ค่านี้ต้องไม่เกิน 1 ชั่วโมงหลังจากเวลาที่ออก |
iat |
เวลาที่ออกการยืนยัน ระบุเป็นวินาทีตั้งแต่ 00:00:00 น. UTC วันที่ 1 มกราคม 1970 |
การนําเสนอ JSON ของช่องที่ต้องกรอกในชุดการอ้างสิทธิ์ JWT จะแสดงอยู่ด้านล่าง
{ "iss": "761326798069-r5mljlln1rd4lrbhg75efgigp36m78j5@developer.gserviceaccount.com", "scope": "https://www.googleapis.com/auth/devstorage.read_only", "aud": "https://oauth2.googleapis.com/token", "exp": 1328554385, "iat": 1328550785 }
การอ้างสิทธิ์เพิ่มเติม
ในบางกรณีขององค์กร แอปพลิเคชันจะใช้การมอบสิทธิ์ทั่วทั้งโดเมนเพื่อดำเนินการในนามของผู้ใช้บางรายในองค์กรได้ แอปจะต้องได้รับอนุญาตให้ดำเนินการแอบอ้างเป็นบุคคลอื่นประเภทนี้ก่อน แอปพลิเคชันจึงจะสวมบทบาทเป็นผู้ใช้ได้ และโดยทั่วไปจะจัดการโดยผู้ดูแลระบบขั้นสูง ดูข้อมูลเพิ่มเติมได้ที่ควบคุมการเข้าถึง API ด้วยการมอบสิทธิ์ทั่วทั้งโดเมน
หากต้องการรับโทเค็นเพื่อการเข้าถึงที่ให้สิทธิ์แอปพลิเคชันในการเข้าถึงทรัพยากร ให้ระบุอีเมลของผู้ใช้ในชุดการอ้างสิทธิ์ JWT เป็นค่าของช่อง sub
ชื่อ | คำอธิบาย |
---|---|
sub |
อีเมลของผู้ใช้ที่แอปพลิเคชันกำลังขอสิทธิ์การเข้าถึงที่ได้รับมอบอำนาจ |
หากแอปพลิเคชันไม่มีสิทธิ์แอบอ้างเป็นผู้ใช้ การตอบกลับคำขอโทเค็นเพื่อการเข้าถึงที่มีช่อง sub
จะเป็นข้อผิดพลาด
ตัวอย่างของชุดการอ้างสิทธิ์ JWT ที่มีช่อง sub
จะแสดงอยู่ด้านล่าง
{ "iss": "761326798069-r5mljlln1rd4lrbhg75efgigp36m78j5@developer.gserviceaccount.com", "sub": "some.user@example.com", "scope": "https://www.googleapis.com/auth/prediction", "aud": "https://oauth2.googleapis.com/token", "exp": 1328554385, "iat": 1328550785 }
การเข้ารหัสชุดการอ้างสิทธิ์ JWT
เช่นเดียวกับส่วนหัว JWT ชุดการอ้างสิทธิ์ JWT ควรแปลงเป็นรูปแบบ UTF-8 และมีการเข้ารหัสแบบ Base64url ด้านล่างคือตัวอย่างการแสดงไฟล์ JSON ของชุดการอ้างสิทธิ์ JWT
{ "iss": "761326798069-r5mljlln1rd4lrbhg75efgigp36m78j5@developer.gserviceaccount.com", "scope": "https://www.googleapis.com/auth/prediction", "aud": "https://oauth2.googleapis.com/token", "exp": 1328554385, "iat": 1328550785 }
กำลังคำนวณลายเซ็น
JSON Web Signature (JWS) เป็นข้อกำหนดที่ใช้เป็นแนวทางการสร้างลายเซ็นสำหรับ JWT อินพุตสำหรับลายเซ็นคืออาร์เรย์ไบต์ของเนื้อหาต่อไปนี้
{Base64url encoded header}.{Base64url encoded claim set}
ต้องใช้อัลกอริทึมการลงชื่อในส่วนหัว JWT เมื่อประมวลผลลายเซ็น อัลกอริทึมการรับรองเพียงอย่างเดียวที่เซิร์ฟเวอร์การให้สิทธิ์ของ Google OAuth 2.0 รองรับคือ RSA ที่ใช้อัลกอริทึมการแฮช SHA-256 ซึ่งจะแสดงเป็น RS256
ในช่อง alg
ในส่วนหัว JWT
ลงนามการแทนค่า UTF-8 ของอินพุตโดยใช้ SHA256withRSA (หรือที่เรียกว่า RSASSA-PKCS1-V1_5-SIGN ที่มีฟังก์ชันแฮช SHA-256) ด้วยคีย์ส่วนตัวที่ได้รับจาก Google API Consoleเอาต์พุตจะเป็นไบต์อาร์เรย์
จากนั้นลายเซ็นจะต้องเข้ารหัส Base64url ส่วนหัว ชุดการอ้างสิทธิ์ และลายเซ็นจะต่อกันด้วยอักขระเครื่องหมายจุด (.
) ผลที่ได้คือ JWT ซึ่งควรมีลักษณะดังนี้ (เพิ่มการขึ้นบรรทัดเพื่อความชัดเจน)
{Base64url encoded header}. {Base64url encoded claim set}. {Base64url encoded signature}
ด้านล่างคือตัวอย่างของ JWT ก่อนการเข้ารหัส Base64url:
{"alg":"RS256","typ":"JWT"}. { "iss":"761326798069-r5mljlln1rd4lrbhg75efgigp36m78j5@developer.gserviceaccount.com", "scope":"https://www.googleapis.com/auth/prediction", "aud":"https://oauth2.googleapis.com/token", "exp":1328554385, "iat":1328550785 }. [signature bytes]
ด้านล่างคือตัวอย่างของ JWT ที่ลงนามแล้วและพร้อมสำหรับการส่ง
eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiI3NjEzMjY3OTgwNjktcjVtbGpsbG4xcmQ0bHJiaGc3NWVmZ2lncDM2bTc4ajVAZGV2ZWxvcGVyLmdzZXJ2aWNlYWNjb3VudC5jb20iLCJzY29wZSI6Imh0dHBzOi8vd3d3Lmdvb2dsZWFwaXMuY29tL2F1dGgvcHJlZGljdGlvbiIsImF1ZCI6Imh0dHBzOi8vd3d3Lmdvb2dsZWFwaXMuY29tL29hdXRoMi92NC90b2tlbiIsImV4cCI6MTMyODU1NDM4NSwiaWF0IjoxMzI4NTUwNzg1fQ.UFUt59SUM2_AW4cRU8Y0BYVQsNTo4n7AFsNrqOpYiICDu37vVt-tw38UKzjmUKtcRsLLjrR3gFW3dNDMx_pL9DVjgVHDdYirtrCekUHOYoa1CMR66nxep5q5cBQ4y4u2kIgSvChCTc9pmLLNoIem-ruCecAJYgI9Ks7pTnW1gkOKs0x3YpiLpzplVHAkkHztaXiJdtpBcY1OXyo6jTQCa3Lk2Q3va1dPkh_d--GU2M5flgd8xNBPYw4vxyt0mP59XZlHMpztZt0soSgObf7G3GXArreF_6tpbFsS3z2t5zkEiHuWJXpzcYr5zWTRPDEHsejeBSG8EgpLDce2380ROQ
ส่งคำขอโทเค็นเพื่อการเข้าถึง
หลังจากสร้าง JWT ที่ลงชื่อแล้ว แอปพลิเคชันจะใช้ JWT เพื่อขอโทเค็นเพื่อการเข้าถึงได้
คำขอโทเค็นเพื่อการเข้าถึงนี้เป็นคำขอ HTTPS POST
และเนื้อความมีการเข้ารหัส URL URL มีดังต่อไปนี้
https://oauth2.googleapis.com/token
ต้องมีพารามิเตอร์ต่อไปนี้ในคำขอ POST
ของ HTTPS
ชื่อ | คำอธิบาย |
---|---|
grant_type |
ใช้สตริงต่อไปนี้โดยเข้ารหัส URL ตามที่จำเป็น
urn:ietf:params:oauth:grant-type:jwt-bearer |
assertion |
JWT รวมทั้งลายเซ็น |
ด้านล่างนี้คือไฟล์ดิบของคำขอ HTTPS POST
ที่ใช้ในคำขอโทเค็นเพื่อการเข้าถึง
POST /token HTTP/1.1 Host: oauth2.googleapis.com Content-Type: application/x-www-form-urlencoded grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Ajwt-bearer&assertion=eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiI3NjEzMjY3OTgwNjktcjVtbGpsbG4xcmQ0bHJiaGc3NWVmZ2lncDM2bTc4ajVAZGV2ZWxvcGVyLmdzZXJ2aWNlYWNjb3VudC5jb20iLCJzY29wZSI6Imh0dHBzOi8vd3d3Lmdvb2dsZWFwaXMuY29tL2F1dGgvcHJlZGljdGlvbiIsImF1ZCI6Imh0dHBzOi8vYWNjb3VudHMuZ29vZ2xlLmNvbS9vL29hdXRoMi90b2tlbiIsImV4cCI6MTMyODU3MzM4MSwiaWF0IjoxMzI4NTY5NzgxfQ.ixOUGehweEVX_UKXv5BbbwVEdcz6AYS-6uQV6fGorGKrHf3LIJnyREw9evE-gs2bmMaQI5_UbabvI4k-mQE4kBqtmSpTzxYBL1TCd7Kv5nTZoUC1CmwmWCFqT9RE6D7XSgPUh_jF1qskLa2w0rxMSjwruNKbysgRNctZPln7cqQ
ด้านล่างนี้เป็นคำขอเดียวกันที่ใช้ curl
:
curl -d 'grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Ajwt-bearer&assertion=eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiI3NjEzMjY3OTgwNjktcjVtbGpsbG4xcmQ0bHJiaGc3NWVmZ2lncDM2bTc4ajVAZGV2ZWxvcGVyLmdzZXJ2aWNlYWNjb3VudC5jb20iLCJzY29wZSI6Imh0dHBzOi8vd3d3Lmdvb2dsZWFwaXMuY29tL2F1dGgvcHJlZGljdGlvbiIsImF1ZCI6Imh0dHBzOi8vYWNjb3VudHMuZ29vZ2xlLmNvbS9vL29hdXRoMi90b2tlbiIsImV4cCI6MTMyODU3MzM4MSwiaWF0IjoxMzI4NTY5NzgxfQ.RZVpzWygMLuL-n3GwjW1_yhQhrqDacyvaXkuf8HcJl8EtXYjGjMaW5oiM5cgAaIorrqgYlp4DPF_GuncFqg9uDZrx7pMmCZ_yHfxhSCXru3gbXrZvAIicNQZMFxrEEn4REVuq7DjkTMyCMGCY1dpMa8aWfTQFt3Eh7smLchaZsU ' https://oauth2.googleapis.com/token
การจัดการกับคำตอบ
หากคำขอ JWT และโทเค็นเพื่อการเข้าถึงมีรูปแบบที่เหมาะสมและบัญชีบริการมีสิทธิ์ดำเนินการ การตอบกลับ JSON จากเซิร์ฟเวอร์การให้สิทธิ์จะมีโทเค็นเพื่อการเข้าถึง ตัวอย่างคำตอบมีดังนี้
{ "access_token": "1/8xbJqaOZXSUZbHLl5EOtu1pxz3fmmetKx9W8CV4t79M", "scope": "https://www.googleapis.com/auth/prediction" "token_type": "Bearer", "expires_in": 3600 }
คุณนำโทเค็นเพื่อการเข้าถึงมาใช้ซ้ำได้ในช่วงระยะเวลาที่ระบุไว้ในค่า expires_in
การเรียกใช้ Google API
Java
ใช้ออบเจ็กต์ GoogleCredential
เพื่อเรียกใช้ Google API โดยทำตามขั้นตอนต่อไปนี้
- สร้างออบเจ็กต์บริการสำหรับ API ที่ต้องการเรียกใช้โดยใช้ออบเจ็กต์
GoogleCredential
ตัวอย่างเช่นSQLAdmin sqladmin = new SQLAdmin.Builder(httpTransport, JSON_FACTORY, credential).build();
- ส่งคำขอไปยังบริการ API โดยใช้อินเทอร์เฟซที่ออบเจ็กต์บริการมีให้
เช่น หากต้องการแสดงรายการอินสแตนซ์ของฐานข้อมูล Cloud SQL ในโปรเจ็กต์ตัวอย่าง-123 ที่น่าสนใจ ให้ทำดังนี้
SQLAdmin.Instances.List instances = sqladmin.instances().list("exciting-example-123").execute();
Python
ใช้ออบเจ็กต์ Credentials
ที่ได้รับอนุญาตเพื่อเรียกใช้ Google API โดยทำตามขั้นตอนต่อไปนี้
- สร้างออบเจ็กต์บริการสำหรับ API ที่ต้องการเรียกใช้ คุณสร้างออบเจ็กต์บริการได้โดยเรียกใช้ฟังก์ชัน
build
ด้วยชื่อและเวอร์ชันของ API รวมถึงออบเจ็กต์Credentials
ที่ได้รับอนุญาต เช่น หากต้องการเรียกเวอร์ชัน 1beta3 ของ Cloud SQL Administration API ให้ทำดังนี้import googleapiclient.discovery sqladmin = googleapiclient.discovery.build('sqladmin', 'v1beta3', credentials=credentials)
- ส่งคำขอไปยังบริการ API โดยใช้อินเทอร์เฟซที่ออบเจ็กต์บริการมีให้
เช่น หากต้องการแสดงรายการอินสแตนซ์ของฐานข้อมูล Cloud SQL ในโปรเจ็กต์ตัวอย่าง-123 ที่น่าสนใจ ให้ทำดังนี้
response = sqladmin.instances().list(project='exciting-example-123').execute()
HTTP/REST
หลังจากแอปพลิเคชันได้รับโทเค็นเพื่อการเข้าถึง คุณจะใช้โทเค็นดังกล่าวเพื่อเรียก Google API ในนามของบัญชีบริการหรือบัญชีผู้ใช้ที่กำหนดได้หาก API ดังกล่าวได้รับขอบเขตการเข้าถึงที่ API กำหนด ซึ่งทำได้โดยการรวมโทเค็นเพื่อการเข้าถึงไว้ในคำขอที่ส่งไปยัง API ด้วยการใส่พารามิเตอร์การค้นหา access_token
หรือค่า Bearer
ของส่วนหัว HTTP ของ Authorization
หากเป็นไปได้ เราขอแนะนำให้ใช้ส่วนหัว HTTP เนื่องจากสตริงการค้นหามีแนวโน้มที่จะมองเห็นได้ในบันทึกของเซิร์ฟเวอร์ ในกรณีส่วนใหญ่ คุณจะใช้ไลบรารีของไคลเอ็นต์เพื่อตั้งค่าการเรียก Google API ได้ (เช่น เมื่อเรียกใช้ Drive Files API)
คุณลองใช้ Google API ทั้งหมดและดูขอบเขตได้ที่ OAuth 2.0 Playground
ตัวอย่าง HTTP GET
การเรียกใช้ปลายทาง
drive.files
(Drive Files API) โดยใช้ส่วนหัว HTTP ของ Authorization: Bearer
อาจมีลักษณะดังต่อไปนี้ โปรดทราบว่าคุณต้องระบุโทเค็นเพื่อการเข้าถึงของตนเอง ดังนี้
GET /drive/v2/files HTTP/1.1 Host: www.googleapis.com Authorization: Bearer access_token
ต่อไปนี้คือการเรียก API เดียวกันสำหรับผู้ใช้ที่ตรวจสอบสิทธิ์แล้วโดยใช้พารามิเตอร์สตริงคำค้นหา access_token
GET https://www.googleapis.com/drive/v2/files?access_token=access_token
ตัวอย่างของ curl
ทดสอบคำสั่งเหล่านี้ได้ด้วยแอปพลิเคชันบรรทัดคำสั่ง curl
ต่อไปนี้คือตัวอย่างที่ใช้ตัวเลือกส่วนหัว HTTP (แนะนำ)
curl -H "Authorization: Bearer access_token" https://www.googleapis.com/drive/v2/files
หรืออีกวิธีหนึ่ง คือตัวเลือกพารามิเตอร์สตริงข้อความค้นหา
curl https://www.googleapis.com/drive/v2/files?access_token=access_token
เมื่อโทเค็นเพื่อการเข้าถึงหมดอายุ
โทเค็นเพื่อการเข้าถึงที่ออกโดยเซิร์ฟเวอร์การให้สิทธิ์ของ Google OAuth 2.0 จะหมดอายุหลังจากระยะเวลาที่ค่า expires_in
ระบุไว้ เมื่อโทเค็นเพื่อการเข้าถึงหมดอายุ แอปพลิเคชันควรสร้าง JWT เพิ่ม ลงนาม และขอโทเค็นเพื่อการเข้าถึงใหม่
รหัสข้อผิดพลาดของ JWT
error ช่อง |
error_description ช่อง |
ความหมาย | วิธีแก้ไข |
---|---|---|---|
unauthorized_client |
Unauthorized client or scope in request. |
หากคุณพยายามใช้การมอบสิทธิ์ทั่วทั้งโดเมน บัญชีบริการนั้นจะไม่ได้รับอนุญาตในคอนโซลผู้ดูแลระบบของโดเมนผู้ใช้ |
ตรวจสอบว่าบัญชีบริการได้รับสิทธิ์ในหน้า
การมอบสิทธิ์ทั่วทั้งโดเมนของคอนโซลผู้ดูแลระบบสำหรับผู้ใช้ในการอ้างสิทธิ์ แม้ว่าโดยปกติจะใช้เวลา 2-3 นาที แต่ระบบอาจใช้เวลาถึง 24 ชั่วโมงก่อนที่การให้สิทธิ์จะมีผลกับผู้ใช้ทั้งหมดในบัญชี Google |
unauthorized_client |
Client is unauthorized to retrieve access tokens using this method, or client not
authorized for any of the scopes requested. |
มีการให้สิทธิ์บัญชีบริการโดยใช้อีเมลของไคลเอ็นต์แทนรหัสไคลเอ็นต์ (ตัวเลข) ในคอนโซลผู้ดูแลระบบ | ในหน้า การมอบสิทธิ์ทั่วทั้งโดเมนในคอนโซลผู้ดูแลระบบ ให้นำไคลเอ็นต์ออก แล้วเพิ่มไคลเอ็นต์อีกครั้งด้วยรหัสตัวเลข |
access_denied |
(ค่าใดก็ได้) | หากคุณใช้การมอบสิทธิ์ทั่วทั้งโดเมน ขอบเขตที่ขออย่างน้อย 1 รายการจะไม่ได้รับอนุญาตในคอนโซลผู้ดูแลระบบ |
ตรวจสอบว่าบัญชีบริการได้รับสิทธิ์ในหน้า
การมอบสิทธิ์ทั่วทั้งโดเมนของคอนโซลผู้ดูแลระบบสำหรับผู้ใช้ในการอ้างสิทธิ์ (ช่อง) แม้ว่าโดยปกติจะใช้เวลา 2-3 นาที แต่ระบบอาจใช้เวลาถึง 24 ชั่วโมงก่อนที่การให้สิทธิ์จะมีผลกับผู้ใช้ทั้งหมดในบัญชี Google |
admin_policy_enforced |
(ค่าใดก็ได้) | บัญชี Google ให้สิทธิ์ขอบเขตที่ขออย่างน้อย 1 ขอบเขตไม่ได้เนื่องจากนโยบายของผู้ดูแลระบบ Google Workspace |
ดูบทความช่วยเหลือสำหรับผู้ดูแลระบบ Google Workspace ควบคุมว่าจะให้แอปของบุคคลที่สามและแอปภายในรายการใดเข้าถึงข้อมูล Google Workspace ได้บ้าง เพื่ออ่านข้อมูลเพิ่มเติมเกี่ยวกับวิธีที่ผู้ดูแลระบบอาจจำกัดการเข้าถึงขอบเขตทั้งหมดหรือขอบเขตที่ละเอียดอ่อนและจำกัด จนกว่าจะมีการให้สิทธิ์เข้าถึงรหัสไคลเอ็นต์ OAuth อย่างชัดเจน |
invalid_client |
(ค่าใดก็ได้) |
ไคลเอ็นต์ OAuth หรือโทเค็น JWT ไม่ถูกต้องหรือกำหนดค่าไว้ไม่ถูกต้อง โปรดดูรายละเอียดในคำอธิบายข้อผิดพลาด |
ตรวจสอบว่าโทเค็น JWT ถูกต้องและมีการอ้างสิทธิ์ที่ถูกต้อง ตรวจสอบว่าได้กำหนดค่าไคลเอ็นต์และบัญชีบริการ OAuth อย่างถูกต้อง และคุณใช้อีเมลที่ถูกต้อง ตรวจสอบว่าโทเค็น JWT ถูกต้องและออกให้รหัสไคลเอ็นต์ในคำขอ |
invalid_grant |
Not a valid email. |
ไม่มีผู้ใช้นี้ | ตรวจสอบว่าอีเมลในการอ้างสิทธิ์ sub (ช่อง) ถูกต้อง |
invalid_grant |
|
โดยทั่วไปหมายความว่าเวลาของระบบท้องถิ่นไม่ถูกต้อง หรืออาจเกิดขึ้นหากค่า exp ใช้เวลามากกว่า 65 นาทีในอนาคตจากค่า iat หรือค่า exp ต่ำกว่าค่า iat |
ตรวจสอบว่านาฬิกาในระบบที่สร้าง JWT นั้นถูกต้อง หากจำเป็น ให้ซิงค์เวลากับ Google NTP |
invalid_grant |
Invalid JWT Signature. |
การยืนยัน JWT มีการลงนามด้วยคีย์ส่วนตัวที่ไม่ได้เชื่อมโยงกับบัญชีบริการที่ระบุโดยอีเมลไคลเอ็นต์หรือคีย์ที่ใช้ถูกลบ ปิดใช้ หรือหมดอายุ อีกทางเลือกหนึ่งคือ การยืนยัน JWT อาจมีการเข้ารหัสไม่ถูกต้อง โดยต้องเข้ารหัสแบบ Base64 โดยไม่มีการขึ้นบรรทัดใหม่หรือระยะห่างจากขอบเท่ากับ |
ถอดรหัสชุดการอ้างสิทธิ์ JWT และยืนยันว่าคีย์ที่รับรองการยืนยันเชื่อมโยงกับบัญชีบริการ ลองใช้ไลบรารี OAuth ที่ Google จัดเตรียมไว้ให้เพื่อให้แน่ใจว่าสร้าง JWT อย่างถูกต้อง |
invalid_scope |
Invalid OAuth scope or ID token audience provided. |
ไม่มีคำขอขอบเขต (รายการขอบเขตที่ว่างเปล่า) หรือไม่มีขอบเขตที่ขอรายการใดรายการหนึ่ง (กล่าวคือ ไม่ถูกต้อง) |
ตรวจสอบว่ามีการป้อนข้อมูลการอ้างสิทธิ์ โปรดทราบว่ารายการขอบเขตในการอ้างสิทธิ์ |
disabled_client |
The OAuth client was disabled. |
คีย์ที่ใช้ในการลงนามการยืนยัน JWT ปิดใช้อยู่ |
ไปที่ Google API Consoleและในส่วน IAM และผู้ดูแลระบบ > บัญชีบริการ ให้เปิดใช้บัญชีบริการที่มี "รหัสคีย์" ที่ใช้ในการลงนามการยืนยัน |
org_internal |
This client is restricted to users within its organization. |
รหัสไคลเอ็นต์ OAuth ในคำขอเป็นส่วนหนึ่งของโปรเจ็กต์ที่จำกัดการเข้าถึงบัญชี Google ใน องค์กร Google Cloud ที่เฉพาะเจาะจง |
ใช้บัญชีบริการจากองค์กรในการตรวจสอบสิทธิ์ ยืนยันการกำหนดค่าประเภทผู้ใช้สำหรับแอปพลิเคชัน OAuth |
ภาคผนวก: การให้สิทธิ์บัญชีบริการที่ไม่มี OAuth
เมื่อใช้ Google API บางตัว คุณจะเรียก API ที่ได้รับอนุญาตได้โดยใช้ JWT ที่ลงชื่อโดยตรงเป็นโทเค็นสำหรับผู้ถือ แทนที่จะใช้โทเค็นเพื่อการเข้าถึง OAuth 2.0 เมื่อเป็นไปได้ คุณจะหลีกเลี่ยงการส่งคำขอเครือข่ายไปยังเซิร์ฟเวอร์การให้สิทธิ์ของ Google ก่อนเรียก API ได้
หาก API ที่ต้องการเรียกมีคำจำกัดความบริการที่เผยแพร่ในที่เก็บ GitHub ของ Google APIs คุณจะเรียกใช้ API ที่ได้รับอนุญาตโดยใช้ JWT แทนโทเค็นเพื่อการเข้าถึงได้ โดยทำตามวิธีต่อไปนี้
- สร้างบัญชีบริการตามที่อธิบายไว้ข้างต้น โปรดเก็บไฟล์ JSON ที่คุณได้รับเมื่อสร้างบัญชีเอาไว้
- เมื่อใช้ไลบรารี JWT มาตรฐาน เช่น ไลบรารีที่พบใน jwt.io ให้สร้าง JWT ที่มีส่วนหัวและเพย์โหลดตามตัวอย่างต่อไปนี้
{ "alg": "RS256", "typ": "JWT", "kid": "abcdef1234567890" } . { "iss": "123456-compute@developer.gserviceaccount.com", "sub": "123456-compute@developer.gserviceaccount.com", "aud": "https://firestore.googleapis.com/", "iat": 1511900000, "exp": 1511903600 }
- สำหรับช่อง
kid
ในส่วนหัว ให้ระบุรหัสคีย์ส่วนตัวของบัญชีบริการ คุณจะพบค่านี้ในช่องprivate_key_id
ของไฟล์ JSON ของบัญชีบริการ - สำหรับช่อง
iss
และsub
ให้ระบุอีเมลของบัญชีบริการ คุณจะพบค่านี้ในช่องclient_email
ของไฟล์ JSON ของบัญชีบริการ - สำหรับช่อง
aud
ให้ระบุปลายทาง API ตัวอย่างเช่นhttps://SERVICE.googleapis.com/
- สำหรับช่อง
iat
ให้ระบุเวลา Unix ปัจจุบัน และช่องexp
ให้ระบุเวลาในอีก 3,600 วินาทีหลังจากนั้นเมื่อ JWT จะหมดอายุ
ลงนาม JWT ด้วย RSA-256 โดยใช้คีย์ส่วนตัวที่พบในไฟล์ JSON ของบัญชีบริการ
เช่น
Java
หากใช้ google-api-java-client และ java-jwt ให้ทำดังนี้
GoogleCredential credential = GoogleCredential.fromStream(new FileInputStream("MyProject-1234.json")); PrivateKey privateKey = credential.getServiceAccountPrivateKey(); String privateKeyId = credential.getServiceAccountPrivateKeyId(); long now = System.currentTimeMillis(); try { Algorithm algorithm = Algorithm.RSA256(null, privateKey); String signedJwt = JWT.create() .withKeyId(privateKeyId) .withIssuer("123456-compute@developer.gserviceaccount.com") .withSubject("123456-compute@developer.gserviceaccount.com") .withAudience("https://firestore.googleapis.com/") .withIssuedAt(new Date(now)) .withExpiresAt(new Date(now + 3600 * 1000L)) .sign(algorithm); } catch ...
Python
การใช้ PyJWT
iat = time.time() exp = iat + 3600 payload = {'iss': '123456-compute@developer.gserviceaccount.com', 'sub': '123456-compute@developer.gserviceaccount.com', 'aud': 'https://firestore.googleapis.com/', 'iat': iat, 'exp': exp} additional_headers = {'kid': PRIVATE_KEY_ID_FROM_JSON} signed_jwt = jwt.encode(payload, PRIVATE_KEY_FROM_JSON, headers=additional_headers, algorithm='RS256')
- เรียก API โดยใช้ JWT ที่ลงชื่อเป็นโทเค็นสำหรับผู้ถือ:
GET /v1/projects/abc/databases/123/indexes HTTP/1.1 Authorization: Bearer SIGNED_JWT Host: firestore.googleapis.com