เพิ่มการยืนยันตัวตนให้กับแอป Android (Kotlin / Java) ของคุณ
คู่มือนี้จะแสดงวิธีการเชื่อมต่อ Logto เข้ากับแอป Android ของคุณ
- ตัวอย่างนี้อ้างอิงจาก View system และ View Model แต่แนวคิดเดียวกันนี้ใช้ได้กับ Jetpack Compose
- ตัวอย่างเขียนด้วย Kotlin แต่แนวคิดเดียวกันนี้ใช้ได้กับ Java
- มีโปรเจกต์ตัวอย่างทั้ง Kotlin และ Java ให้ดูใน SDK repository ของเรา
- วิดีโอแนะนำสามารถรับชมได้ที่ YouTube channel ของเรา
ข้อกำหนดเบื้องต้น
- บัญชี Logto Cloud หรือ Logto ที่ติดตั้งเอง
- สร้างแอปเนทีฟของ Logto แล้ว
- โปรเจกต์แอป Android ที่ใช้ Kotlin
การติดตั้ง
ระดับ API ขั้นต่ำของ Android ที่รองรับโดย Logto Android SDK คือระดับ 24
Logto Android SDK มาในสองเวอร์ชันหลัก:
- v2: เปิดประสบการณ์การลงชื่อเข้าใช้ใน WebView แบบฝัง ซึ่งจำเป็นสำหรับ social connector แบบเนทีฟ แต่ไม่รองรับ การลงชื่อเข้าใช้ด้วย passkey (WebView ไม่รองรับ WebAuthn ซึ่งเป็นมาตรฐานพื้นฐานของ passkey)
- v3 (beta): เปิดประสบการณ์การลงชื่อเข้าใช้ใน Chrome Custom Tabs (เบราว์เซอร์ของระบบ) ซึ่งเปิดใช้งานการลงชื่อเข้าใช้ด้วย passkey และแชร์ session ของเบราว์เซอร์ โปรดทราบว่า v3 ยกเลิกการรองรับ connector WeChat (Native) และ Alipay (Native) คุณสามารถใช้ WeChat (Web) และ Alipay (Web) แทนได้ ซึ่งทำงานผ่านเบราว์เซอร์ หากคุณพึ่งพา connector แบบเนทีฟ ให้ใช้ v2 ต่อไป
คู่มือนี้ครอบคลุมทั้งสองเวอร์ชัน เลือกเวอร์ชันของคุณในแท็บด้านล่าง และการเลือกจะถูกซิงค์ตลอดทั้งคู่มือนี้
ก่อนติดตั้ง Logto Android SDK โปรดตรวจสอบให้แน่ใจว่าได้เพิ่ม mavenCentral() ในการตั้งค่าที่เก็บ repository ในไฟล์ build ของโปรเจกต์ Gradle แล้ว:
dependencyResolutionManagement {
repositories {
mavenCentral()
}
}
เพิ่ม Logto Android SDK ลงใน dependencies ของคุณ:
- v2
- v3 (beta)
- Kotlin
- Groovy
dependencies {
implementation("io.logto.sdk:android:2.0.3")
}
dependencies {
implementation 'io.logto.sdk:android:2.0.3'
}
v3 จะถูกปล่อยเป็น prerelease 3.0.0-beta จนกว่าจะ GA ใช้ prerelease ล่าสุดเป็น version:
- Kotlin
- Groovy
dependencies {
implementation("io.logto.sdk:android:3.0.0-beta")
}
dependencies {
implementation 'io.logto.sdk:android:3.0.0-beta'
}
เนื่องจาก SDK ต้องการเข้าถึงอินเทอร์เน็ต คุณจำเป็นต้องเพิ่ม permission ต่อไปนี้ในไฟล์ AndroidManifest.xml ของคุณ:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<!-- เพิ่มสิทธิ์การใช้งานอินเทอร์เน็ต -->
<uses-permission android:name="android.permission.INTERNET" />
<!-- การตั้งค่าอื่น ๆ... -->
</manifest>
การเชื่อมต่อ
เริ่มต้น LogtoClient
สร้างไฟล์ LogtoViewModel.kt และเริ่มต้น LogtoClient ใน view model นี้:
//...with other imports
import io.logto.sdk.android.LogtoClient
import io.logto.sdk.android.type.LogtoConfig
class LogtoViewModel(application: Application) : AndroidViewModel(application) {
// กำหนดค่า LogtoConfig
private val logtoConfig = LogtoConfig(
endpoint = "<your-logto-endpoint>",
appId = "<your-app-id>",
scopes = null,
resources = null,
usingPersistStorage = true,
)
// เริ่มต้น LogtoClient ด้วย config และ application
private val logtoClient = LogtoClient(logtoConfig, application)
companion object {
val Factory: ViewModelProvider.Factory = object : ViewModelProvider.Factory {
@Suppress("UNCHECKED_CAST")
override fun <T : ViewModel> create(
modelClass: Class<T>,
extras: CreationExtras
): T {
// ดึง Application object จาก extras
val application = checkNotNull(extras[APPLICATION_KEY])
return LogtoViewModel(application) as T
}
}
}
}
จากนั้น สร้าง LogtoViewModel สำหรับ MainActivity.kt ของคุณ:
//...with other imports
class MainActivity : AppCompatActivity() {
// สร้าง logtoViewModel โดยใช้ Factory
private val logtoViewModel: LogtoViewModel by viewModels { LogtoViewModel.Factory }
//...โค้ดอื่น ๆ
}
กำหนดค่า redirect URI
ก่อนที่เราจะลงลึกในรายละเอียด นี่คือภาพรวมประสบการณ์ของผู้ใช้ปลายทาง กระบวนการลงชื่อเข้าใช้สามารถสรุปได้ดังนี้:
- แอปของคุณเรียกใช้งานเมธอดลงชื่อเข้าใช้
- ผู้ใช้จะถูกเปลี่ยนเส้นทางไปยังหน้าลงชื่อเข้าใช้ของ Logto สำหรับแอปเนทีฟ ระบบจะเปิดเบราว์เซอร์ของระบบ
- ผู้ใช้ลงชื่อเข้าใช้และถูกเปลี่ยนเส้นทางกลับไปยังแอปของคุณ (ตามที่กำหนดไว้ใน redirect URI)
เกี่ยวกับการลงชื่อเข้าใช้แบบเปลี่ยนเส้นทาง (redirect-based sign-in)
- กระบวนการยืนยันตัวตนนี้เป็นไปตามโปรโตคอล OpenID Connect (OIDC) และ Logto บังคับใช้มาตรการรักษาความปลอดภัยอย่างเข้มงวดเพื่อปกป้องการลงชื่อเข้าใช้ของผู้ใช้
- หากคุณมีหลายแอป คุณสามารถใช้ผู้ให้บริการข้อมูลระบุตัวตน (Logto) เดียวกันได้ เมื่อผู้ใช้ลงชื่อเข้าใช้แอปหนึ่งแล้ว Logto จะดำเนินการลงชื่อเข้าใช้โดยอัตโนมัติเมื่อผู้ใช้เข้าถึงแอปอื่น
หากต้องการเรียนรู้เพิ่มเติมเกี่ยวกับเหตุผลและประโยชน์ของการลงชื่อเข้าใช้แบบเปลี่ยนเส้นทาง โปรดดูที่ อธิบายประสบการณ์การลงชื่อเข้าใช้ของ Logto
ไปที่หน้ารายละเอียดแอปพลิเคชันของ Logto Console เพิ่ม Redirect URI io.logto.android://io.logto.sample/callback แล้วคลิก "บันทึกการเปลี่ยนแปลง" (Save changes)
ใน Android, redirect URI จะมีรูปแบบ: $(LOGTO_REDIRECT_SCHEME)://$(YOUR_APP_PACKAGE)/callback:
LOGTO_REDIRECT_SCHEMEควรเป็น custom scheme ในรูปแบบ reverse domainYOUR_APP_PACKAGEคือชื่อแพ็กเกจของแอปคุณ
สมมติว่าคุณใช้ io.logto.android เป็น custom LOGTO_REDIRECT_SCHEME และ io.logto.sample เป็นชื่อแพ็กเกจแอปของคุณ Redirect URI ควรเป็น io.logto.android://io.logto.sample/callback
- v2
- v3 (beta)
ไม่จำเป็นต้องตั้งค่าเพิ่มเติม ประสบการณ์การลงชื่อเข้าใช้จะเปิดใน WebView แบบฝัง และ SDK จะดักจับการ redirect ภายใน WebView
ใน v3 ประสบการณ์การลงชื่อเข้าใช้จะเปิดใน Custom Tab (เบราว์เซอร์ของระบบ) และการ redirect จะถูกส่งกลับไปยังแอปของคุณผ่าน intent filter ระดับ OS คุณจำเป็นต้องประกาศ scheme ของ redirect URI ด้วย logtoRedirectScheme manifest placeholder ในไฟล์ build ของแอปคุณ:
- Kotlin
- Groovy
android {
defaultConfig {
manifestPlaceholders["logtoRedirectScheme"] = "io.logto.android"
}
}
android {
defaultConfig {
manifestPlaceholders.logtoRedirectScheme = 'io.logto.android'
}
}
นอกจากนี้ v3 บังคับรูปแบบ redirect URI ผ่านการจับคู่ intent filter ของ Android ดังนั้น redirect URI ที่เบี่ยงเบนจากรูปแบบจะไม่ถูกส่งไปยังแอปของคุณ:
- scheme ต้องเท่ากับ
logtoRedirectSchememanifest placeholder - host ต้องเป็น
applicationIdของคุณ - path ต้องเป็น
/callback
ให้ scheme และ host เป็นตัวพิมพ์เล็ก เนื่องจากการจับคู่ intent filter คำนึงถึงตัวพิมพ์เล็กใหญ่ และเบราว์เซอร์จะแปลง scheme เป็นตัวพิมพ์เล็ก
ใช้ App Links แทน custom scheme?
หากต้องการใช้ Android App Links (redirect URI แบบ https บนโดเมนที่คุณเป็นเจ้าของ) แทน custom scheme:
-
โฮสต์ไฟล์ Digital Asset Links ที่
https://your.domain/.well-known/assetlinks.jsonโดยระบุ application ID และลายนิ้วมือ SHA-256 ของใบรับรองการลงนามของคุณ เมื่อเผยแพร่ด้วย Play App Signing คุณสามารถดูลายนิ้วมือ release ได้ใน Play Console ใต้ Setup > App signing ไฟล์ต้องให้บริการเป็นContent-Type: application/jsonพร้อม HTTP 200 และไม่มี redirect -
ประกาศ App Links intent filter บน activity รับ redirect ของ SDK
io.logto.sdk.android.auth.logto.LogtoRedirectReceiverActivityในไฟล์AndroidManifest.xmlของคุณ หากคุณไม่ได้ใช้ custom scheme เลย ให้ลบ filter ที่ติดมากับ SDK ด้วยtools:node="removeAll"และlogtoRedirectSchememanifest placeholder ก็จะไม่จำเป็นอีกต่อไป:AndroidManifest.xml<manifest xmlns:android="http://schemas.android.com/apk/res/android"xmlns:tools="http://schemas.android.com/tools"><application><activity android:name="io.logto.sdk.android.auth.logto.LogtoRedirectReceiverActivity"><!-- Omit this line to keep the custom-scheme redirect working alongside. --><intent-filter tools:node="removeAll" /><intent-filter android:autoVerify="true"><action android:name="android.intent.action.VIEW" /><category android:name="android.intent.category.DEFAULT" /><category android:name="android.intent.category.BROWSABLE" /><data android:scheme="https" android:host="your.domain" android:path="/callback" /></intent-filter></activity></application></manifest> -
เพิ่ม
https://your.domain/callbackเป็น redirect URI (และหากใช้สำหรับการออกจากระบบ ให้เพิ่มเป็น post sign-out redirect URI ด้วย) ในหน้ารายละเอียดแอปของ Logto Console แล้วส่งไปยังsignIn/signOut
โปรดทราบว่า callback ตอนนี้เป็น URL จริงบนโดเมนของคุณ ดังนั้นให้บริการหน้า fallback ที่นั่น (เช่น ปุ่ม "กลับไปยังแอป") สำหรับเบราว์เซอร์ที่ไม่เปิด App Links เมื่อ server redirect ปุ่มนั้นเพียงแค่ต้องลิงก์ไปยัง URL ปัจจุบัน (เช่น กำหนด href เป็น window.location.href): พารามิเตอร์การอนุมัติอยู่ใน query string และการคลิกของผู้ใช้จะให้ URL เดิมโอกาสอีกครั้งในการถูกส่งไปยังแอปของคุณ บน Android 12+ โดเมนที่ยังไม่ได้ยืนยันจะไม่เปิดแอปเลย ดังนั้น assetlinks.json ที่เสียหายจะล้มเหลวโดยไม่มีข้อความแจ้ง คุณสามารถตรวจสอบสถานะการยืนยันด้วย adb shell pm get-app-links <applicationId>
ดำเนินการลงชื่อเข้าใช้และออกจากระบบ
ก่อนเรียก logtoClient.signIn โปรดตรวจสอบให้แน่ใจว่าคุณได้กำหนดค่า Redirect URI ใน Admin Console อย่างถูกต้องแล้ว
คุณสามารถใช้ logtoClient.signIn เพื่อให้ผู้ใช้ลงชื่อเข้าใช้ และ logtoClient.signOut เพื่อให้ผู้ใช้ออกจากระบบ
- v2
- v3 (beta)
ตัวอย่างเช่น ในแอป Android:
//...with other imports
class LogtoViewModel(application: Application) : AndroidViewModel(application) {
// ...other codes
// เพิ่ม live data เพื่อสังเกตสถานะการยืนยันตัวตน
private val _authenticated = MutableLiveData(logtoClient.isAuthenticated)
val authenticated: LiveData<Boolean>
get() = _authenticated
fun signIn(context: Activity) {
logtoClient.signIn(context, "io.logto.android://io.logto.sample/callback") { logtoException ->
logtoException?.let { println(it) }
// อัปเดต live data
_authenticated.postValue(logtoClient.isAuthenticated)
}
}
fun signOut() {
logtoClient.signOut { logtoException ->
logtoException?.let { println(it) }
// อัปเดต live data
_authenticated.postValue(logtoClient.isAuthenticated)
}
}
}
จากนั้นเรียกใช้เมธอด signIn และ signOut ใน activity ของคุณ:
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
//...other codes
// สมมติว่าคุณมีปุ่มที่มี id "sign_in_button" ใน layout ของคุณ
val signInButton = findViewById<Button>(R.id.sign_in_button)
signInButton.setOnClickListener {
logtoViewModel.signIn(this)
}
// สมมติว่าคุณมีปุ่มที่มี id "sign_out_button" ใน layout ของคุณ
val signOutButton = findViewById<Button>(R.id.sign_out_button)
signOutButton.setOnClickListener {
if (logtoViewModel.authenticated) { // ตรวจสอบว่าผู้ใช้ได้รับการยืนยันตัวตนหรือไม่
logtoViewModel.signOut()
}
}
// สังเกตสถานะการยืนยันตัวตนเพื่ออัปเดต UI
logtoViewModel.authenticated.observe(this) { authenticated ->
if (authenticated) {
// ผู้ใช้ได้รับการยืนยันตัวตนแล้ว
signInButton.visibility = View.GONE
signOutButton.visibility = View.VISIBLE
} else {
// ผู้ใช้ยังไม่ได้รับการยืนยันตัวตน
signInButton.visibility = View.VISIBLE
signOutButton.visibility = View.GONE
}
}
}
}
ใน v3 logtoClient.signOut จะดำเนินการออกจากระบบอย่างสมบูรณ์: ล้างข้อมูลประจำตัว local เพิกถอน refresh token และสิ้นสุด Logto session โดยการเปิด end session endpoint ในเบราว์เซอร์ จากนั้นเบราว์เซอร์จะนำทางกลับไปยังแอปของคุณผ่าน post sign-out redirect URI ก่อนใช้งาน ให้ไปที่หน้ารายละเอียดแอปของ Logto Console เพิ่ม post sign-out redirect URI io.logto.android://io.logto.sample/callback และคลิก "Save changes" post sign-out redirect URI ใช้รูปแบบเดียวกับ redirect URI และ scheme ของมันต้องตรงกับ logtoRedirectScheme manifest placeholder ด้วย
ตัวอย่างเช่น ในแอป Android:
//...with other imports
class LogtoViewModel(application: Application) : AndroidViewModel(application) {
// ...other codes
// เพิ่ม live data เพื่อสังเกตสถานะการยืนยันตัวตน
private val _authenticated = MutableLiveData(logtoClient.isAuthenticated)
val authenticated: LiveData<Boolean>
get() = _authenticated
fun signIn(context: Activity) {
logtoClient.signIn(context, "io.logto.android://io.logto.sample/callback") { logtoException ->
logtoException?.let { println(it) }
// อัปเดต live data
_authenticated.postValue(logtoClient.isAuthenticated)
}
}
fun signOut(context: Activity) {
logtoClient.signOut(context, "io.logto.android://io.logto.sample/callback") { logtoException ->
logtoException?.let { println(it) }
// อัปเดต live data
_authenticated.postValue(logtoClient.isAuthenticated)
}
}
}
จากนั้นเรียกใช้เมธอด signIn และ signOut ใน activity ของคุณ:
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
//...other codes
// สมมติว่าคุณมีปุ่มที่มี id "sign_in_button" ใน layout ของคุณ
val signInButton = findViewById<Button>(R.id.sign_in_button)
signInButton.setOnClickListener {
logtoViewModel.signIn(this)
}
// สมมติว่าคุณมีปุ่มที่มี id "sign_out_button" ใน layout ของคุณ
val signOutButton = findViewById<Button>(R.id.sign_out_button)
signOutButton.setOnClickListener {
if (logtoViewModel.authenticated) { // ตรวจสอบว่าผู้ใช้ได้รับการยืนยันตัวตนหรือไม่
logtoViewModel.signOut(this)
}
}
// สังเกตสถานะการยืนยันตัวตนเพื่ออัปเดต UI
logtoViewModel.authenticated.observe(this) { authenticated ->
if (authenticated) {
// ผู้ใช้ได้รับการยืนยันตัวตนแล้ว
signInButton.visibility = View.GONE
signOutButton.visibility = View.VISIBLE
} else {
// ผู้ใช้ยังไม่ได้รับการยืนยันตัวตน
signInButton.visibility = View.VISIBLE
signOutButton.visibility = View.GONE
}
}
}
}
- คุณยังสามารถเรียก
logtoClient.signOut(context)โดยไม่มี post sign-out redirect URI ในกรณีนี้ไม่จำเป็นต้องตั้งค่าใน Console: เบราว์เซอร์จะแสดงหน้าออกจากระบบของ Logto และผู้ใช้กลับไปยังแอปโดยปิดด้วยตนเอง - หากไม่มี UI context ให้ใช้ คุณสามารถเรียก
logtoClient.clearCredentialsเพื่อล้างข้อมูลประจำตัว local และเพิกถอน refresh token โปรดทราบว่าการทำเช่นนี้จะเก็บ Logto session ไว้ในเบราว์เซอร์ ดังนั้นsignInครั้งต่อไปอาจลงชื่อเข้าใช้ผู้ใช้ซ้ำโดยอัตโนมัติผ่าน session นั้น
จุดตรวจสอบ: ทดสอบแอปพลิเคชันของคุณ
ตอนนี้คุณสามารถทดสอบแอปพลิเคชันของคุณได้แล้ว:
- รันแอปพลิเคชันของคุณ คุณจะเห็นปุ่มลงชื่อเข้าใช้
- คลิกปุ่มลงชื่อเข้าใช้ SDK จะเริ่มกระบวนการลงชื่อเข้าใช้และเปลี่ยนเส้นทางคุณไปยังหน้าลงชื่อเข้าใช้ของ Logto
- หลังจากที่คุณลงชื่อเข้าใช้แล้ว คุณจะถูกเปลี่ยนเส้นทางกลับไปยังแอปพลิเคชันของคุณและเห็นปุ่มลงชื่อออก
- คลิกปุ่มลงชื่อออกเพื่อเคลียร์ที่เก็บโทเค็นและออกจากระบบ
รับข้อมูลผู้ใช้
แสดงข้อมูลผู้ใช้
เพื่อแสดงข้อมูลของผู้ใช้ คุณสามารถใช้เมธอด logtoClient.getIdTokenClaims() ตัวอย่างเช่น คุณสามารถดึงข้อมูลผู้ใช้ใน ViewModel แล้วนำไปแสดงใน activity ของคุณ:
class LogtoViewModel(application: Application) : AndroidViewModel(application) {
// ...โค้ดอื่น ๆ
// เพิ่ม live data เพื่อสังเกต id token claims
private val _idTokenClaims = MutableLiveData<IdTokenClaims>()
val idTokenClaims: LiveData<IdTokenClaims>
get() = _idTokenClaims
fun getIdTokenClaims() {
logtoClient.getIdTokenClaims { logtoException, idTokenClaims ->
logtoException?.let { _logtoException.postValue(it) } ?: _idTokenClaims.postValue(idTokenClaims)
}
}
}
//...with other imports
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
//...โค้ดอื่น ๆ
// สมมติว่าคุณมี TextView ที่ id `user_info_text_view` ใน layout ของคุณ
val userInfoResponseTextView: TextView = findViewById(R.id.user_info_text_view)
logtoViewModel.userInfoResponse.observe(this) { userInfoResponse ->
userInfoResponseTextView.text = if (userInfoResponse !== null) {
val json = Gson().toJson(userInfoResponse, UserInfoResponse::class.java)
JSONObject(json).toString(2)
} else {
""
}
}
}
}
ขอ claims เพิ่มเติม
คุณอาจพบว่าข้อมูลผู้ใช้บางอย่างหายไปในอ็อบเจกต์ที่ส่งคืนจาก logtoClient.getIdTokenClaims() สาเหตุเนื่องจาก OAuth 2.0 และ OpenID Connect (OIDC) ถูกออกแบบมาให้สอดคล้องกับหลักการสิทธิ์น้อยที่สุด (principle of least privilege; PoLP) และ Logto ถูกสร้างขึ้นบนมาตรฐานเหล่านี้
โดยปกติแล้ว จะมีการส่งคืนการอ้างสิทธิ์ (claim) แบบจำกัด หากคุณต้องการข้อมูลเพิ่มเติม คุณสามารถร้องขอขอบเขต (scope) เพิ่มเติมเพื่อเข้าถึงการอ้างสิทธิ์ (claim) ที่มากขึ้นได้
"การอ้างสิทธิ์ (Claim)" คือการยืนยันข้อมูลบางอย่างเกี่ยวกับผู้ถูกอ้างถึง (subject); "ขอบเขต (Scope)" คือกลุ่มของการอ้างสิทธิ์ (claim) ในกรณีนี้ การอ้างสิทธิ์ (claim) คือข้อมูลบางอย่างเกี่ยวกับผู้ใช้
ตัวอย่างที่ไม่เป็นทางการของความสัมพันธ์ระหว่างขอบเขต (scope) กับการอ้างสิทธิ์ (claim) มีดังนี้:
การอ้างสิทธิ์ (claim) "sub" หมายถึง "ผู้ถูกอ้างถึง (subject)" ซึ่งคือตัวระบุที่ไม่ซ้ำของผู้ใช้ (เช่น user ID)
Logto SDK จะร้องขอขอบเขต (scope) สามรายการเสมอ ได้แก่ openid, profile และ offline_access
หากต้องการขอ scopes เพิ่มเติม คุณสามารถส่ง scopes ไปที่อ็อบเจกต์ LogtoConfig ตัวอย่างเช่น:
private val logtoConfig = LogtoConfig(
// ...การตั้งค่าอื่น ๆ
scopes = listOf("email", "phone"), // หรือ `listOf(UserScope.EMAIL, UserScope.PHONE)`
)
จากนั้นคุณสามารถเข้าถึง claims เพิ่มเติมในค่าที่คืนมาจาก logtoClient.getIdTokenClaims():
logtoClient.getIdTokenClaims { logtoException, idTokenClaims ->
println("IdTokenClaims:$idTokenClaims")
}
// ตอนนี้คุณสามารถเข้าถึง claims เพิ่มเติม เช่น `claims.email`, `claims.phone` เป็นต้น
การอ้างสิทธิ์ (Claims) ที่ต้องใช้การร้องขอผ่านเครือข่าย
เพื่อป้องกันไม่ให้โทเค็น ID (ID token) มีขนาดใหญ่เกินไป การอ้างสิทธิ์บางรายการจำเป็นต้องร้องขอผ่านเครือข่ายเพื่อดึงข้อมูล ตัวอย่างเช่น การอ้างสิทธิ์ custom_data จะไม่ถูกรวมอยู่ในอ็อบเจกต์ผู้ใช้ แม้ว่าจะร้องขอไว้ในขอบเขต (scopes) ก็ตาม หากต้องการเข้าถึงการอ้างสิทธิ์เหล่านี้ คุณสามารถใช้เมธอด logtoClient.fetchUserInfo():
logtoClient.fetchUserInfo {_, userInfoResponse ->
println("UserInfoResponse:$userInfoResponse")
}
// ตอนนี้คุณสามารถเข้าถึง claim `userInfo.custom_data`
ขอบเขตและการอ้างสิทธิ์ (Scopes and claims)
Logto ใช้แนวทาง ขอบเขต (Scope) และ การอ้างสิทธิ์ (Claim) ของ OIDC เพื่อกำหนดขอบเขตและการอ้างสิทธิ์สำหรับดึงข้อมูลผู้ใช้จากโทเค็น ID (ID token) และ OIDC จุดปลาย userinfo ทั้ง "ขอบเขต (Scope)" และ "การอ้างสิทธิ์ (Claim)" เป็นคำศัพท์จากข้อกำหนดของ OAuth 2.0 และ OpenID Connect (OIDC)
สำหรับการอ้างสิทธิ์ OIDC มาตรฐาน การรวมอยู่ในโทเค็น ID จะถูกกำหนดอย่างเคร่งครัดโดยขอบเขตที่ร้องขอ การอ้างสิทธิ์เพิ่มเติม (เช่น custom_data และ organizations) สามารถตั้งค่าเพิ่มเติมให้ปรากฏในโทเค็น ID ได้ผ่าน การตั้งค่า Custom ID token
ต่อไปนี้คือรายการขอบเขต (Scopes) ที่รองรับและการอ้างสิทธิ์ (Claims) ที่เกี่ยวข้อง:
ขอบเขต OIDC มาตรฐาน
openid (ค่าเริ่มต้น)
| ชื่อการอ้างสิทธิ์ | ประเภท | คำอธิบาย |
|---|---|---|
| sub | string | ตัวระบุที่ไม่ซ้ำของผู้ใช้ |
profile (ค่าเริ่มต้น)
| ชื่อการอ้างสิทธิ์ | ประเภท | คำอธิบาย |
|---|---|---|
| name | string | ชื่อเต็มของผู้ใช้ |
| username | string | ชื่อผู้ใช้ของผู้ใช้ |
| picture | string | URL ของรูปโปรไฟล์ผู้ใช้ปลายทาง (End-User) URL นี้ต้องอ้างถึงไฟล์รูปภาพ (เช่น PNG, JPEG, หรือ GIF) ไม่ใช่หน้าเว็บที่มีรูปภาพ โปรดทราบว่า URL นี้ควรอ้างอิงถึงรูปโปรไฟล์ของผู้ใช้ปลายทางโดยเฉพาะ เหมาะสำหรับแสดงเมื่ออธิบายผู้ใช้ปลายทาง ไม่ใช่รูปภาพใด ๆ ที่ผู้ใช้ถ่ายมาโดยพลการ |
| created_at | number | เวลาที่สร้างผู้ใช้ปลายทาง เวลานี้แสดงเป็นจำนวนมิลลิวินาทีตั้งแต่ Unix epoch (1970-01-01T00:00:00Z) |
| updated_at | number | เวลาที่ข้อมูลของผู้ใช้ปลายทางถูกอัปเดตล่าสุด เวลานี้แสดงเป็นจำนวนมิลลิวินาทีตั้งแต่ Unix epoch (1970-01-01T00:00:00Z) |
การอ้างสิทธิ์มาตรฐาน อื่น ๆ เช่น family_name, given_name, middle_name, nickname, preferred_username, profile, website, gender, birthdate, zoneinfo, และ locale จะถูกรวมอยู่ในขอบเขต profile ด้วยโดยไม่จำเป็นต้องร้องขอ endpoint userinfo ความแตกต่างเมื่อเทียบกับการอ้างสิทธิ์ข้างต้นคือ การอ้างสิทธิ์เหล่านี้จะถูกส่งกลับเมื่อค่าของมันไม่ว่างเปล่าเท่านั้น ในขณะที่การอ้างสิทธิ์ข้างต้นจะคืนค่า null หากค่าว่างเปล่า
ต่างจากการอ้างสิทธิ์มาตรฐาน การอ้างสิทธิ์ created_at และ updated_at ใช้หน่วยเป็นมิลลิวินาทีแทนที่จะเป็นวินาที
email
| ชื่อการอ้างสิทธิ์ | ประเภท | คำอธิบาย |
|---|---|---|
string | อีเมลของผู้ใช้ | |
| email_verified | boolean | อีเมลได้รับการยืนยันแล้วหรือไม่ |
phone
| ชื่อการอ้างสิทธิ์ | ประเภท | คำอธิบาย |
|---|---|---|
| phone_number | string | เบอร์โทรศัพท์ของผู้ใช้ |
| phone_number_verified | boolean | เบอร์โทรศัพท์ได้รับการยืนยันแล้วหรือไม่ |
address
โปรดดูรายละเอียดของการอ้างสิทธิ์ที่อยู่ได้ที่ OpenID Connect Core 1.0
ขอบเขตที่มีเครื่องหมาย (ค่าเริ่มต้น) จะถูกร้องขอเสมอโดย Logto SDK การอ้างสิทธิ์ภายใต้ขอบเขต OIDC มาตรฐานจะถูกรวมอยู่ในโทเค็น ID เสมอเมื่อมีการร้องขอขอบเขตที่เกี่ยวข้อง — ไม่สามารถปิดได้
ขอบเขตเพิ่มเติม
ขอบเขตต่อไปนี้เป็นขอบเขตที่ Logto ขยายขึ้นและจะคืนค่าการอ้างสิทธิ์ผ่าน userinfo endpoint การอ้างสิทธิ์เหล่านี้ยังสามารถตั้งค่าให้ถูกรวมอยู่ในโทเค็น ID ได้โดยตรงผ่าน Console > Custom JWT ดู Custom ID token สำหรับรายละเอียดเพิ่มเติม
custom_data
| ชื่อการอ้างสิทธิ์ | ประเภท | คำอธิบาย | รวมในโทเค็น ID โดยค่าเริ่มต้น |
|---|---|---|---|
| custom_data | object | ข้อมูลกำหนดเองของผู้ใช้ |
identities
| ชื่อการอ้างสิทธิ์ | ประเภท | คำอธิบาย | รวมในโทเค็น ID โดยค่าเริ่มต้น |
|---|---|---|---|
| identities | object | ข้อมูลตัวตนที่เชื่อมโยงของผู้ใช้ | |
| sso_identities | array | ข้อมูล SSO ที่เชื่อมโยงของผู้ใช้ |
roles
| ชื่อการอ้างสิทธิ์ | ประเภท | คำอธิบาย | รวมในโทเค็น ID โดยค่าเริ่มต้น |
|---|---|---|---|
| roles | string[] | บทบาท (Role) ของผู้ใช้ | ✅ |
urn:logto:scope:organizations
| ชื่อการอ้างสิทธิ์ | ประเภท | คำอธิบาย | รวมในโทเค็น ID โดยค่าเริ่มต้น |
|---|---|---|---|
| organizations | string[] | รหัสองค์กรที่ผู้ใช้สังกัด | ✅ |
| organization_data | object[] | ข้อมูลองค์กรที่ผู้ใช้สังกัด |
การอ้างสิทธิ์ขององค์กรเหล่านี้สามารถดึงได้ผ่าน userinfo endpoint เมื่อใช้ โทเค็นทึบ (Opaque token) อย่างไรก็ตาม โทเค็นทึบไม่สามารถใช้เป็นโทเค็นองค์กรสำหรับเข้าถึงทรัพยากรเฉพาะองค์กร ดู โทเค็นทึบและองค์กร สำหรับรายละเอียดเพิ่มเติม
urn:logto:scope:organization_roles
| ชื่อการอ้างสิทธิ์ | ประเภท | คำอธิบาย | รวมในโทเค็น ID โดยค่าเริ่มต้น |
|---|---|---|---|
| organization_roles | string[] | บทบาทขององค์กรที่ผู้ใช้สังกัดในรูปแบบ <organization_id>:<role_name> | ✅ |
ทรัพยากร API และองค์กร
เราแนะนำให้อ่าน 🔐 การควบคุมการเข้าถึงตามบทบาท (RBAC) ก่อน เพื่อทำความเข้าใจแนวคิดพื้นฐานของ RBAC ใน Logto และวิธีตั้งค่าทรัพยากร API อย่างถูกต้อง
กำหนดค่า Logto client
เมื่อคุณตั้งค่า ทรัพยากร API (API resources) เรียบร้อยแล้ว คุณสามารถเพิ่มทรัพยากรเหล่านี้ขณะกำหนดค่า Logto ในแอปของคุณได้:
val logtoConfig = LogtoConfig(
//...other configs
resources = listOf("https://shopping.your-app.com/api", "https://store.your-app.com/api"), // เพิ่มทรัพยากร API
)
แต่ละ ทรัพยากร API (API resource) จะมี สิทธิ์ (scopes) ของตัวเอง
ตัวอย่างเช่น ทรัพยากร https://shopping.your-app.com/api มีสิทธิ์ shopping:read และ shopping:write และทรัพยากร https://store.your-app.com/api มีสิทธิ์ store:read และ store:write
หากต้องการร้องขอสิทธิ์เหล่านี้ คุณสามารถเพิ่มขณะกำหนดค่า Logto ในแอปของคุณได้:
val logtoConfig = LogtoConfig(
// ..การตั้งค่าอื่น ๆ
scopes = listOf("shopping:read", "shopping:write", "store:read", "store:write"),
resources = listOf("https://shopping.your-app.com/api", "https://store.your-app.com/api"),
)
คุณอาจสังเกตได้ว่า ขอบเขต (scopes) ถูกกำหนดแยกจาก ทรัพยากร API (API resources) นี่เป็นเพราะ Resource Indicators for OAuth 2.0 ระบุว่า ขอบเขตสุดท้ายสำหรับคำขอจะเป็นผลคูณคาร์ทีเซียนของขอบเขตทั้งหมดในบริการเป้าหมายทั้งหมด
ดังนั้น ในกรณีข้างต้น ขอบเขต (scopes) สามารถทำให้เรียบง่ายขึ้นจากการกำหนดใน Logto โดยทั้งสอง ทรัพยากร API (API resources) สามารถมีขอบเขต read และ write ได้โดยไม่ต้องมีคำนำหน้า จากนั้น ในการตั้งค่า Logto:
val logtoConfig = LogtoConfig(
// ...การตั้งค่าอื่น ๆ
scopes = listOf("read", "write"),
resources = listOf("https://shopping.your-app.com/api", "https://store.your-app.com/api"),
)
สำหรับแต่ละ ทรัพยากร API (API resource) จะร้องขอทั้งขอบเขต read และ write
คุณสามารถร้องขอขอบเขต (scopes) ที่ไม่ได้กำหนดไว้ใน ทรัพยากร API (API resources) ได้ เช่น คุณสามารถร้องขอขอบเขต email ได้ แม้ว่า ทรัพยากร API (API resources) จะไม่มีขอบเขต email ให้ ขอบเขตที่ไม่มีจะถูกละเว้นอย่างปลอดภัย
หลังจากลงชื่อเข้าใช้สำเร็จ Logto จะออกขอบเขตที่เหมาะสมให้กับ ทรัพยากร API (API resources) ตามบทบาทของผู้ใช้
ดึงโทเค็นการเข้าถึงสำหรับทรัพยากร API
เพื่อดึงโทเค็นการเข้าถึง (Access token) สำหรับทรัพยากร API เฉพาะ คุณสามารถใช้เมธอด getAccessToken ได้ดังนี้:
logtoClient.getAccessToken("https://shopping.your-app.com/api") { logtoException, accessToken ->
logtoException?.let { println(it) }
accessToken?.let { println(it) }
}
เมธอดนี้จะส่งคืนโทเค็นการเข้าถึง (JWT access token) ที่สามารถใช้เข้าถึงทรัพยากร API ได้เมื่อผู้ใช้มีสิทธิ์ที่เกี่ยวข้อง หากโทเค็นการเข้าถึงที่แคชไว้หมดอายุแล้ว เมธอดนี้จะพยายามใช้โทเค็นรีเฟรช (Refresh token) เพื่อขอโทเค็นการเข้าถึงใหม่โดยอัตโนมัติ
ดึงโทเค็นองค์กร
หากคุณยังไม่คุ้นเคยกับ องค์กร (Organization) โปรดอ่าน 🏢 องค์กร (หลายผู้เช่า; Multi-tenancy) เพื่อเริ่มต้น
คุณต้องเพิ่ม UserScope.Organizations ขอบเขต (scope) ขณะตั้งค่า Logto client:
val logtoConfig = LogtoConfig(
// ...other configs
scopes = listOf(UserScope.Organizations), // ขอบเขต (scopes) = รายการของ UserScope.Organizations
)
เมื่อผู้ใช้ลงชื่อเข้าใช้แล้ว คุณสามารถดึงโทเค็นองค์กร (organization token) สำหรับผู้ใช้ได้:
// แทนที่พารามิเตอร์ด้วยรหัสองค์กรที่ถูกต้อง
// สามารถดูรหัสองค์กรที่ถูกต้องสำหรับผู้ใช้ได้ในการอ้างสิทธิ์ (claim) โทเค็น ID `organizations`
logtoClient.getOrganizationToken("organization-id") { logtoException, organizationToken ->
logtoException?.let { println(it) }
organizationToken?.let { println(it) }
}
// หรือ
logtoClient.getOrganizationTokenClaims("organization-id") { logtoException, claims ->
logtoException?.let { println(it) }
claims?.let { println(it) }
}
ทรัพยากร API ขององค์กร (Organization API resources)
หากต้องการดึงโทเค็นการเข้าถึง (Access token) สำหรับทรัพยากร API ในองค์กร คุณสามารถใช้เมธอด getAccessToken พร้อมทั้งระบุทั้งทรัพยากร API และรหัสองค์กรเป็นพารามิเตอร์:
logtoClient.getAccessToken(
'https://shopping.your-app.com/api',
organizationId
) { logtoException, accessToken ->
println("AccessToken:$accessToken")
}