Sign in. Ask permission. Save data.
Build Auth and Data into your app, one step at a time. Tour the platform, configure the requested data, then follow the guide for your language—with file locations, examples and expected results at each step.
ONE handles identity, login, consent and missing profile fields. Client is the developer workspace where you register and configure your app. The SDK connects your app to both.
Exact contracts, permissions and API evolution
Read the full profile and data contracts, null/error semantics, cross-platform patterns, security requirements and verification matrix. Future app metadata, legal documents, organisation and moderation methods are clearly labelled as proposals.
Open advanced API specification →Understand the platform
A guided tour of ONE and Client
ExtremeX Client is your developer workspace: register applications, configure credentials and define the data they request. ExtremeX ONE owns the user’s identity, sign-in, consent and isolated app data. The SDK connects your application to those services.
The callback carries a short-lived code and state. The SDK checks the callback and state, exchanges the code using its saved PKCE verifier, and then requests the allowed profile. Data requests use the resulting bearer token; they do not send the user’s ONE password.
1. Namespace and workspaces
Start in Dashboard. Your namespace is your company’s reverse domain, such as com.example. Registering the service myapp produces APP_ID com.example.myapp. Choose the intended workspace before creating an app; workspace access determines which controls you can use.
What this means: The namespace and app ID identify your integration. A namespace becomes permanent when apps are registered, keeping their app IDs and callbacks stable.
2. Overview
Open a registered app to see connected users, sign-ins, active refresh sessions and field-grant activity. Change the date range to inspect recent activity after testing a sign-in.
What this means: Use this to confirm that authorizations are reaching the intended app. A server-side authorization event is not proof that your device received its callback; check both.
3. Users
Inspect the recent users connected to this app and the fields they granted. Email visibility depends on whether that user granted the email field.
What this means: A missing optional field is not necessarily a bug. The user may have declined it or later revoked that permission.
4. Credentials
Copy APP_ID and CLIENT_ID, choose the app type, and inspect redirect URLs. Native clients receive an automatic HTTPS relay plus a custom URI scheme. Confidential web backends also use CLIENT_SECRET.
What this means: REDIRECT_URL is the URL sent in the authorization and code-exchange requests. CUSTOM_URI_SCHEME is only the scheme registered with the operating system. They are related but are not interchangeable.
5. Data access
Select the ONE profile fields the app needs. Mark each required or optional and explain its purpose. Enable App data to use the data API. Enable Stay signed in if the app should receive a refresh token.
What this means: This defines the consent screen. Required choices stay selected. The user can deselect optional choices; the app must handle their absence. Changing these settings does not silently add permission to existing tokens—run the consent flow again.
6. Create data
Declare the keys your application writes, their types and descriptions. For the tutorials, add theme as a string and high_score as a number. These are app-specific values, separate from ONE identity fields such as first_name or email.
What this means: Declaring a key defines its type; it does not create a value for every user. A missing key reads as null. Storage is isolated by app and user, even when two apps use the same key name.
7. Settings
Maintain the display name, description and other available app settings so users recognize the app on the consent screen. Review the app’s status before testing a new integration.
What this means: Keep the app identity recognizable. Developer metadata does not grant permission to read user data; the user still authorizes access through ONE.
8. ONE account and connected apps
ONE is the user-facing identity service. Users sign in there, maintain their profile and review connected apps. The login flow opens requested-data consent, then asks for selected profile fields that are missing.
What this means: Saving missing profile data is followed by another consent review. Local SDK sign-out clears the app’s stored session; revoking access from ONE’s Connected apps is a separate action.
The tab names above match the current Client UI. Team permissions may hide controls that your account cannot use.
Shared setup · every platform
Register and configure your application
Complete these steps once for the application you are integrating. The language-specific guide below assumes this registration already exists.
Sign in to Client and choose a workspace
Open Dashboard and sign in with ONE. On first use, claim your company’s reverse-domain namespace, such as
com.example. Choose the workspace that should own this application.Expected result: you can create an app under the intended namespace. Existing team permissions determine which controls are available.
Create the app and select its type
Use a service name such as
myapporapp.myappand a recognizable display name. The namespace and service produceAPP_ID=com.example.myapp. Choose Public client for native, desktop, React Native or browser-only apps. Choose Web app for a backend that can keep a secret, and provide its HTTPS callback.Why: app type determines how token exchange authenticates the client. Public clients use S256 PKCE; confidential web servers also use CLIENT_SECRET.
Define the profile fields and permissions
Open Data access. Select the ONE identity fields your app needs, explain their purpose and choose required or optional. Enable App data for the data examples. Enable Stay signed in to receive rotating refresh tokens.
Expected result: the consent screen shows these fields and reasons. Optional fields may be declined, so your app must handle their absence.
Declare your first app-data keys
Open Create data. Add
themewith type string andhigh_scorewith type number. Save the schema. These keys belong to your app; they are separate from ONE profile fields.Expected result: writes to declared keys are type-checked. Declaring a key does not initialize a value; it stays absent until your app writes it.
Copy the public configuration and register the callback
Open Credentials. Copy APP_ID and CLIENT_ID. Follow the credential table and your platform’s callback instructions below. Use the automatic native relay for the supplied Android/Apple clients, the direct custom callback for React Native AppAuth, or your registered HTTPS callback for a website/backend.
Expected result: the URL used by your SDK matches an entry in Client exactly. Keep CLIENT_SECRET on confidential backends only.
| Value | Where to use it |
|---|---|
APP_ID | Public identifier for your app. Pass as appId; determines the app’s data namespace and default native callback. |
CLIENT_ID | Public OAuth identifier. Pass as clientId in every SDK. |
CLIENT_SECRET | Confidential web backends only. Keep in server environment variables. Omit from native apps, browser code and distributed desktop builds. |
REDIRECT_URL | Exact registered callback URL. Pass as redirectUri for authorization and token exchange. Native apps get a hosted redirect automatically. |
CUSTOM_URI_SCHEME | Native apps: register the scheme alone (for example com.example.app) with the OS. The full callback is com.example.app://callback. Websites use their HTTPS callback instead. |
Native apps default to https://client.extremextechnology.com/callback/APP_ID, which opens APP_ID://callback. Browser-only apps must register their own HTTPS callback as an additional redirect and set both redirectUri and callbackUri to it.
Build your integration
Choose a platform and follow the steps
Each guide covers installation, configuration, the login return path, data access and verification. Complete the shared registration steps above first.
Set up permissions before calling the SDK
- Register your app. In Dashboard → Register application, choose a display name, description and public HTTPS icon URL. A service suffix such as app.myapp under com.example produces com.example.app.myapp. Copy the generated APP_ID and CLIENT_ID exactly.
- Choose Requested data. Open the app’s Data access tab → Requested data. Enable only the profile fields you need and explain why. Enable App data to use one.data.set/get/del/rep. Creating a variable in Create data defines its schema; it does not grant permission. Mark optional preferences optional.
- Register the callback. Use the exact REDIRECT_URL shown in Credentials. On Android, use separate MAIN/LAUNCHER and VIEW/BROWSABLE intent filters; only the VIEW filter contains the custom URI scheme and callback host. Never bundle CLIENT_SECRET in native, mobile or browser apps.
- Sign in and review consent. The user must allow App data to sync app variables. Allowed registration scopes are not user consent. Changing Requested data does not upgrade old access or refresh tokens: sign in again and review the updated permission screen.
- Navigate after authentication succeeds. Profile or theme requests are separate operations. A theme error must not overwrite a successful sign-in with “login failed”. Handle each data call independently; users who decline optional App data can still use the signed-in app.
- Render only shared profile fields. Use named one.auth.data helpers. Null means missing or unshared; false is a valid verification/age result. Show no raw date of birth. Refresh on return from ONE so permission changes and profile updates take effect. SDK missing-data prompts let users complete fields they already agreed to share.
- Test the choices. Try a fresh login, restored session, denied optional App data, missing profile fields, revoked access, offline mode and guest access. Guests have no ONE profile or cloud-data permission. Never infer a verification result from a formatted phone number or a browser redirect.
// Run after login succeeds. App data must be enabled AND consented.
one.data.get("theme", object : ExOneCallback<Any> {
override fun onSuccess(value: Any?) {
// Use the saved preference, or an app-local default when absent.
}
override fun onError(error: ExOneException) {
when (error.code) {
"insufficient_scope" -> { /* Offer sign-in again to review App data. */ }
"access_revoked" -> { /* Access was withdrawn; stop data access. */ }
"invalid_token" -> { /* Ask the user to sign in again. */ }
else -> { /* Show a retry message for this data operation. */ }
}
// Do not mark a successfully authenticated user as a failed login.
}
})Why did I see insufficient_scope after login?
ONE successfully authenticated the user, then the app called its data endpoint without the attributes (App data) scope. Enable App data in Data access → Requested data, save, and sign in again. Do not add permissions to tokens yourself or skip server consent checks. If the user declines optional access, keep cloud preferences disabled.
Why are only first and last name displayed?
Those may be the only fields the app requested and the user allowed. Adding UI rows does not authorize additional data. Configure the required fields in Client and let the user review consent again.
Why is age or phone verification unavailable?
Age verification requires a configured provider with remaining free quota. ONE stores validated provider results, not client claims. A free sandbox or one-time trial is not an unlimited production allowance. Phone formatting or carrier lookup does not prove ownership; a trusted OTP verification must succeed before the phone is marked verified.
Android · Kotlin
No bundle required. Gradle downloads the Android Auth/Data/UI artifact and its dependencies.
Optional source bundle · all platforms in one archive
Before you start
- Android API 24+
- An Android Studio project using Kotlin
- Public client registered in Client
Files and configuration you will touch
- settings.gradle.kts — ONE Maven repository
- app/build.gradle.kts — exone-android dependency
- AndroidManifest.xml — internet permission and callback
- MainActivity.kt — layout, callback and data calls
Quick code preview
Use the steps below for file placement and callback setup. Uppercase identifiers are placeholders; application-specific functions are explained in the relevant step.
import android.app.Activity
import android.content.Intent
import android.os.Bundle
import android.widget.LinearLayout
import android.widget.TextView
import com.extremex.one.ExOneCallback
import com.extremex.one.ExOneException
import com.extremex.one.auth.ExOneUser
import com.extremex.one.android.ExOneAndroid
class MainActivity : Activity() {
private lateinit var one: ExOneAndroid
private lateinit var status: TextView
private val result = object : ExOneCallback<ExOneUser> {
override fun onSuccess(user: ExOneUser) {
status.text = "Signed in — ready for app data"
}
override fun onError(error: ExOneException) {
status.text = error.message
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
one = ExOneAndroid(applicationContext, "APP_ID", "CLIENT_ID")
status = TextView(this)
val layout = LinearLayout(this).apply {
orientation = LinearLayout.VERTICAL
addView(one.loginView(this@MainActivity))
addView(status)
}
setContentView(layout)
one.handleCallback(intent, result)
}
override fun onNewIntent(intent: Intent) {
super.onNewIntent(intent)
setIntent(intent)
one.handleCallback(intent, result)
}
}Profile helper examples for Android · Kotlin
All results may be null when missing or denied. See the profile helper reference below for permissions, bitmap types and completion handlers.
// Inside a worker/coroutine after sign-in. Every result may be null.
val isUserTeenResult = one.auth.data.isUserTeen()
val isUserLegalResult = one.auth.data.isUserLegal()
val isUserAdultResult = one.auth.data.isUserAdult()
val isUserAgeVarifiedResult = one.auth.data.isUserAgeVarified()
val isUserBirthdayResult = one.auth.data.isUserBirthday()
val isUserEmailVarifiedResult = one.auth.data.isUserEmailVarified()
val isPhoneNumberVerifiedResult = one.auth.data.isPhoneNumberVerified()
val isWorkEmailVarifiedResult = one.auth.data.isWorkEmailVarified()
val isWorkPhoneNumberVerifiedResult = one.auth.data.isWorkPhoneNumberVerified()
val getEmailResult = one.auth.data.getEmail()
val getCountryResult = one.auth.data.getCountry()
val getWorkEmailResult = one.auth.data.getWorkEmail()
val getWorkCountryResult = one.auth.data.getWorkCountry()
val getNationalityResult = one.auth.data.getNationality()
val getCountryOfBirthResult = one.auth.data.getCountryOfBirth()
val getNameResult = one.auth.data.getName()
val getPhoneNumberResult = one.auth.data.getPhoneNumber()
val getAddressResult = one.auth.data.getAddress()
val getWorkPhoneNumberResult = one.auth.data.getWorkPhoneNumber()
val getWorkAddressResult = one.auth.data.getWorkAddress()
val getLanguagesResult = one.auth.data.getLanguages()
val getAvatarResult = one.auth.data.getAvatar()Android Studio → settings.gradle.kts and app/build.gradle.kts
Step 1: Install Auth, Data and Android views
Merge the repository into your existing dependency repositories. Add exone-android to the app module and sync Gradle. Keep minSdk at 24 or higher.
Why: The Android artifact brings in exone-data and exone-auth transitively, so you do not need three separate dependency declarations.
Gradle · Kotlin DSLStep 1 example // settings.gradle.kts dependencyResolutionManagement { repositories { google() mavenCentral() maven { url = uri("https://client.extremextechnology.com/repo") } } } // app/build.gradle.kts — inside dependencies { ... } implementation("com.extremex.one:exone-android:3.2.0")Expected result: Gradle resolves com.extremex.one.android.ExOneAndroid and the shared auth/data classes.
AndroidManifest.xml → your callback Activity
Step 2: Register the return route
Replace com.example.myapp below with your APP_ID. Merge the intent filter and attributes into the Activity that handles login. In Client, keep the generated HTTPS REDIRECT_URL.
Why: ONE returns to the HTTPS relay, which opens APP_ID://callback. Android must know which Activity receives that URL. INTERNET permission enables token and data requests.
XMLStep 2 example <!-- app/src/main/AndroidManifest.xml --> <manifest xmlns:android="http://schemas.android.com/apk/res/android"> <uses-permission android:name="android.permission.INTERNET" /> <application> <!-- Merge into your existing Activity; keep its launcher filter. --> <activity android:name=".MainActivity" android:exported="true" android:launchMode="singleTask"> <intent-filter> <action android:name="android.intent.action.VIEW" /> <category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.BROWSABLE" /> <data android:scheme="com.example.myapp" android:host="callback" /> </intent-filter> </activity> </application> </manifest>Expected result: The browser can return to your Activity after consent.
MainActivity.kt
Step 3: Create the client and mount the login layout
Replace APP_ID and CLIENT_ID with the public values from Credentials. Use the complete Activity skeleton below, or adapt its initialization and callback methods into your existing Activity.
Why: The native view opens ONE; the SDK stores the PKCE transaction and validates the return before reporting success. A client secret is not used.
KotlinStep 3 example import android.app.Activity import android.content.Intent import android.os.Bundle import android.widget.LinearLayout import android.widget.TextView import com.extremex.one.ExOneCallback import com.extremex.one.ExOneException import com.extremex.one.auth.ExOneUser import com.extremex.one.android.ExOneAndroid class MainActivity : Activity() { private lateinit var one: ExOneAndroid private lateinit var status: TextView private val result = object : ExOneCallback<ExOneUser> { override fun onSuccess(user: ExOneUser) { status.text = "Signed in — ready for app data" } override fun onError(error: ExOneException) { status.text = error.message } } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) one = ExOneAndroid(applicationContext, "APP_ID", "CLIENT_ID") status = TextView(this) val layout = LinearLayout(this).apply { orientation = LinearLayout.VERTICAL addView(one.loginView(this@MainActivity)) addView(status) } setContentView(layout) one.handleCallback(intent, result) } override fun onNewIntent(intent: Intent) { super.onNewIntent(intent) setIntent(intent) one.handleCallback(intent, result) } }Expected result: A Sign in with ONE button and Create an account link appear. The callback changes the status label.
On the device → Sign in with ONE
Step 4: Follow login, permission review and missing data
Tap sign in. ONE asks the user to log in, then displays requested fields and reasons. If a selected profile field is missing, add it, save, review again and allow access. Use one.showLoginDialog(this) if you prefer a dialog entry point.
Why: The library provides the native entry view; ONE hosts the actual login, consent and profile-completion screens. Signup opens the ONE signup page.
Expected result: onSuccess runs only after the code exchange and profile request succeed.
Your signed-in screen or onSuccess handler
Step 5: Write and read the first value
In Client, enable App data and declare theme as a string. Execute the example only after sign-in succeeds. Do not place the write unconditionally in onCreate.
Why: The data calls use the session created by auth, and the async variants avoid blocking Android’s main thread.
KotlinStep 5 example // Run after the sign-in callback succeeds. // Also: one.data.rep("theme", "light", callback). // update requires a non-null value; creates the key if missing. one.data.set("theme", "dark", object : ExOneCallback<Void> { override fun onSuccess(result: Void?) { one.data.get("theme", object : ExOneCallback<Any> { override fun onSuccess(value: Any?) { runOnUiThread { status.text = "Theme: $value" } } override fun onError(error: ExOneException) { runOnUiThread { status.text = error.message } } }) } override fun onError(error: ExOneException) { runOnUiThread { status.text = error.message } } })Expected result: The status label displays Theme: dark.
Emulator/device + ONE → Connected apps
Step 6: Verify session restoration and revocation
Sign in, save theme, close and reopen the app, and read the value again. Enable Stay signed in in Client to test refresh. Then revoke App data access in ONE and confirm the app handles a denied read.
Why: ExOneAndroid stores tokens and pending sign-in state in Keystore-encrypted storage excluded from backup. Its handleCallback result is delivered on the main thread, but the shared data callbacks run on a background thread.
Expected result: The app restores a valid session, and revoked access produces a visible error instead of stale data.
Glass login view and optional guest access
SDK 3.2 loads your registered app name, description and HTTPS icon alongside ExtremeX ONE branding. Set these in Client → Settings (or during registration). Metadata is public and contains no user profile or secret. Branding failures keep the standard ONE view usable. Use a small square PNG icon for the widest native compatibility.
- Choose the entry behaviour. required defaults to true, so existing calls show sign-in and signup. Set it to false to also show “Login as guest”. Signup always opens ONE signup.
- Handle guest navigation. Supply onGuest (on_guest in Python). The view clears the app’s local ONE session before invoking it. Open your own guest screen and keep account-only operations disabled. Without a callback, supported embedded views display a guest confirmation.
- Keep authentication separate. Guest mode does not create a ONE user, token, profile permission, or cloud-data access. The system browser may still be signed into ONE. Save guest preferences locally, or use your own guest backend. Mount a fresh login view when the guest chooses to sign in.
- Fit the host screen. Use a dark or softly coloured background, horizontal margins, safe-area padding and a scrollable host for short screens or large text. The card adapts up to 460–480 logical pixels. Browser and newer Apple platforms use backdrop material; other native toolkits use tinted surfaces and borders.
// Java-backed Android API: the second argument is required.
val view = one.loginView(this, false) {
openGuestScreen() // Your app's navigation; no ONE account data calls.
}
setContentView(view)
// true (or omitted) hides the guest button:
// one.loginView(this, true)
// Dialog: one.showLoginDialog(this, false) { openGuestScreen() }Names such as openGuestScreen and openSignedInScreen represent your app’s navigation functions. Android’s Java-backed overload uses a positional boolean from Kotlin; Compose and Swift support named required arguments. A required login view does not replace server-side access checks.
App variables and profile data
one.data.set saves app data; get reads it; del deletes it; rep replaces it with a non-null value (creating the key if missing). Existing update/remove aliases remain compatible.
Read profile data with the named one.auth.data helpers, such as getName() and getEmail(). Age checks mean 13+, 18+, and 25+ without exposing the birth date. Missing or denied values return null. isUserAgeVarified() offers a verification dialog when false; complete verification in ONE and retry. Authentication, network and revoked-access errors propagate. End the app session with one.auth.signOut().
// SDK 3.2.0. After sign-in; run blocking calls off the main thread.
Thread {
try {
one.data.set("theme", "dark")
val value = one.data.get("theme")
one.data.rep("theme", "light") // Non-null replacement
one.data.del("theme") // Deletes this app's value
val name = one.auth.data.getName()
val verified = one.auth.data.isUserEmailVarified()
val teen = one.auth.data.isUserTeen() // 13+
val legal = one.auth.data.isUserLegal() // 18+
val adult = one.auth.data.isUserAdult() // 25+
val phoneVerified = one.auth.data.isPhoneNumberVerified()
} catch (error: com.extremex.one.ExOneException) {
error.printStackTrace()
}
}.start()
// set/get/rep/del also accept ExOneCallback for asynchronous calls.Moderation errors show the saved reason. Java/Kotlin expose error.message and error.details; JavaScript exposes error.message, error.code, and error.banExpiresAt. Render reasons as plain text. The Users tab configures app-scoped bans and JSON rules; no client-side enforcement is required for ONE API requests.
Platform troubleshooting
The browser never returns to the app
Compare the manifest scheme with APP_ID exactly; use host callback. Keep the HTTPS relay as REDIRECT_URL. Reinstall after changing the manifest.
A new Activity opens but login does not finish
Handle the incoming Intent in both onCreate and onNewIntent. Pass the new Intent itself to handleCallback.
Data calls return 403
Enable App data in Client, then sign in and approve again. Check that the user has not revoked access.
Network/UI thread error
Use data async methods for network operations and runOnUiThread for UI updates inside their callbacks.
Platform reference: Android deep links
Signup opens ONE signup. The native entry layouts launch ONE’s hosted login, permission and missing-profile screens. See release verification notes for platform build/device-test coverage.
Read the ONE profile safely
- Request access. In Client → Data access, select the fields your app needs and explain why. Age and birthday checks use the existing date_of_birth permission identifier, but its value is never returned.
- Ask the user. Sign in through ONE and let the user review access. Optional fields can be denied. Do not interpret denial as false.
- Read after sign-in. Use one.auth.data helpers. Each call reads current consent. Use a worker/coroutine or callback on UI platforms.
- Handle three results. True means the check passed; false means the known value did not pass; null means missing or ungranted data. A revoked session, ban or network failure is an error, not a null result.
- Complete missing fields. For granted-but-missing fields, the SDK opens or offers ONE’s existing completion view. Save in ONE, return to your app, then call the helper again. A denied field never triggers this prompt.
| one.auth.data helper | Permission | Result | Meaning |
|---|---|---|---|
isUserTeen() | date_of_birth | Boolean or null | Age 13 or older. |
isUserLegal() | date_of_birth | Boolean or null | Age 18 or older. |
isUserAdult() | date_of_birth | Boolean or null | Age 25 or older. |
isUserAgeVarified() | date_of_birth | Boolean or null | Trusted ID, liveness and face-match approval; self-entered dates never set this flag. |
isUserBirthday() | date_of_birth | Boolean or null | Birthday is today using the UTC calendar. |
isUserEmailVarified() | email | Boolean or null | Requires the matching live consent. Verification flags are false for a present, unverified value. |
isPhoneNumberVerified() | phone | Boolean or null | Requires the matching live consent. Verification flags are false for a present, unverified value. |
isWorkEmailVarified() | work_email | Boolean or null | False until the saved work email is verified. |
isWorkPhoneNumberVerified() | work_phone | Boolean or null | False until the saved work phone is verified. |
getEmail() | email | String or null | Requires the matching live consent. Verification flags are false for a present, unverified value. |
getCountry() | address | String or null | Home address country code. |
getWorkEmail() | work_email | String or null | Requires the matching live consent. Verification flags are false for a present, unverified value. |
getWorkCountry() | work_address | String or null | Work address country code. |
getNationality() | nationality | String or null | Requires the matching live consent. Verification flags are false for a present, unverified value. |
getCountryOfBirth() | country_of_birth | String or null | Requires the matching live consent. Verification flags are false for a present, unverified value. |
getName() | first_name, last_name | String array or null | [first_name, last_name]; an ungranted or missing component is null. |
getPhoneNumber() | phone | String array or null | [country_code, phone_number], both strings; e.g. ["39", "0212345678"]. No + prefix in country_code. |
getAddress() | address | String array or null | [house/apt/building, street, town/city, county/state/province, country, postcode/ZIP]. |
getWorkPhoneNumber() | work_phone | String array or null | [country_code, phone_number], both strings. |
getWorkAddress() | work_address | String array or null | Same six fixed positions as getAddress(). |
getLanguages() | languages | String array or null | Saved language names. |
getAvatar() | avatar | Bitmap or null | Decoded ONE profile photo, at most 256 × 256 pixels. |
Name and address arrays preserve their positions with null for an unavailable component. Country values are the saved ISO country code, or OTHER. Age thresholds use the saved date, regardless of verification; combine a threshold with isUserAgeVarified() when your app needs a verified age. No helper returns a birth date or exact age.
Verify age when requested
When isUserAgeVarified() returns false, the supplied UI offers a “Verify your age” dialog linking to https://one.extremextechnology.com/account. Open Age verification in ONE, complete the ID and face checks, return, then retry the helper. The dialog is offered once per SDK client session; null or denied access never opens an age-verification prompt. Missing permitted data still opens the completion prompt first.
Bitmap types by platform
Android returns Bitmap; iOS returns UIImage; macOS returns NSImage; browsers return ImageBitmap; Go returns image.RGBA. Java returns AvatarBitmap with width, height and ARGB pixels. KMP, Python, Ruby, Rust, Node and React Native return a decoded RGBA bitmap with width, height and pixel bytes. Adapt those pixels to your UI toolkit. No additional profile image files are stored by ONE.
Connect a completion prompt to your UI
Browser, Android and Apple clients include a prompt. Android apps should call one.attach(activity) after activity recreation. The supplied Swing and Tkinter views install their prompt handlers; Compose installs its handler while OneLoginView is mounted. React Native’s createNativeClient installs an Alert prompt. For headless clients or custom layouts, use the following hooks and open the supplied ONE URL after the user chooses to continue:
- Java: one.auth.data.onMissingData; KMP: one.auth.data.onMissingData; Swift: one.auth.data.onMissingData.
- JavaScript: createClient({onMissingData: (event) => …}) or one.auth.data.setMissingDataHandler(handler).
- Python/Ruby: one.auth.data.on_missing_data; Go: one.Auth.Data.OnMissingData; Rust: one.on_missing_data.
Headless server code returns null and does not open a desktop browser. Forward the completion event through your authenticated UI, or provide your own dialog. For the age-verification action, the same handler receives fields ["age_verification"] and the fixed account URL; show “Verify your age” instead of a missing-data message. Handlers otherwise receive the missing fields and completion URL; JavaScript receives an event object with fields and url. JVM callbacks run on a worker; dispatch UI updates to the UI thread.
val legal: Boolean? = one.auth.data.isUserLegal()
val verified: Boolean? = one.auth.data.isUserAgeVarified()
when {
legal == null || verified == null -> showMissingOrDeniedState()
legal && verified -> continueWithVerifiedAdult()
else -> showAgeRequirement()
}
val phone = one.auth.data.getPhoneNumber() // ["44", "7911123456"] or null
// The three UI functions above belong to your app.Upgrade from SDK 1.x or 2.x
The generic one.auth.data.get and the old isUserVarified alias are removed. Use getName(), getEmail(), or another helper from the table. Sign-in/session support remains available for the login views. Use one.auth.signOut() to end the app session. Install version 3.2.0 using your platform’s normal install command below. Recompile and handle nullable flags: Java Boolean, Kotlin Boolean?, Swift/Objective-C NSNumber?, Go *bool, Rust Option<bool>, and null/None/nil in dynamic languages. Swift uses number.boolValue after unwrapping. Older release files remain available, but raw birth dates are removed from the server for every SDK version.
Go exports GetName/IsUserTeen and the other helpers with capitals; Rust uses get_name/is_user_teen and snake_case equivalents. Other platforms keep the spellings shown in the table, including Varified for compatibility.
How login, consent and views fit together
The Android library includes loginView and showLoginDialog. The source bundle includes SwiftUI, UIKit, Compose Multiplatform, React Native, Swing, Tkinter and browser views. Mount a layout or present it as a dialog/sheet.
- Login: the control opens ONE in the system authentication browser. “Create an account” opens one.extremextechnology.com/signup.
- Allow data access: ONE shows the app’s name, requested fields, reasons and required/optional choices. The user can decline optional fields or cancel.
- Add missing data: selected fields missing from the ONE profile are collected before consent. The user saves, reviews the request again, then allows access.
These consent and completion views are hosted by ONE and shared across platforms. They are opened by the library’s auth session; apps never collect the user’s ONE password or imitate the consent decision locally.
Android callback manifest
<uses-permission android:name="android.permission.INTERNET" />
<!-- On your callback Activity (deliver onCreate and onNewIntent to the SDK): -->
<activity android:name=".MainActivity" android:exported="true"
android:launchMode="singleTask">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="YOUR_CUSTOM_URI_SCHEME" android:host="callback" />
</intent-filter>
</activity>Apple and desktop callbacks
Apple: register CUSTOM_URI_SCHEME under Xcode → Info → URL Types. Windows: register a URL protocol handler. Linux: use a desktop entry with x-scheme-handler/CUSTOM_URI_SCHEME. Route callbacks to the same user’s running client instance. For web servers, use a per-user session store that keeps pending state/verifier across requests.
Understand app data and sessions
data.set(key, value), data.get(key), data.rep(key, value) and data.del(key) operate on the signed-in user’s data within your app’s namespace. Declared types are checked by ONE. Access requires the user’s current App data consent.
Accepted values: JSON strings, numbers, booleans, null, arrays and objects. Values are compressed with DEFLATE, then encrypted with AES-256-GCM. Existing encrypted records remain readable. Limits: 16 KiB of uncompressed UTF-8 JSON per value, 100 keys per write, 20 levels of nesting, and a 1.7 MB request body.
No file-upload or media-storage API is provided. Multipart uploads, data URLs, recognizable encoded files and binary byte arrays are rejected. Do not encode videos, images or files into data fields. The only image-upload exception is the user’s ONE profile photo.
Tokens last one hour; optional Stay signed in enables rotating refresh tokens. Persist each replacement refresh token in secure storage. Local sign-out clears the app’s session; users revoke access from ONE → Connected apps.
Verify your integration step by step
- Start signed out. Open the app’s login control. Confirm ONE shows the intended application name and requested fields.
- Test cancellation. Cancel the request. Your app should display a signed-out or cancelled state and should not enable data operations.
- Exercise missing data. Request a profile field that your test profile has not filled. Select it, complete it in ONE, then review and allow access. An unselected optional field should not block authorization.
- Complete a successful callback. Verify the SDK receives the full callback URL exactly once and the app switches to its signed-in screen.
- Round-trip a value. Save
theme = "dark", read it back, then delete it and confirm the missing-key result. Check that another app or user has a separate namespace. - Test session lifetime. Relaunch or reload with the supported secure storage configured. If Stay signed in was granted, confirm your storage saves replacement refresh tokens.
- Revoke access. Disconnect the app or revoke the relevant field from ONE’s Connected apps. The next protected request should fail clearly; your app should not quietly display stale privileged data.
Use your registered identifiers in a real target build. The release verification notes distinguish compiled SDK checks from platform/device behavior still requiring your validation.
Installation and libraries
The full bundle is not required for every integration. Android and JVM projects resolve dependencies with Gradle/Maven. Websites can import the hosted module. Python and Ruby use a single file; other source-based platforms can use the relevant files or folder listed in their guide.
Maven repository · Integration notes
repositories { maven { url = uri("https://client.extremextechnology.com/repo") } }
// Android: Auth + Data + layout/dialog + encrypted persistence
implementation("com.extremex.one:exone-android:3.2.0")
// Other JVM applications: Auth + Data
implementation("com.extremex.one:exone-data:3.2.0")Optional source bundle · all platforms in one archive
The Java, Android, browser, Python, Ruby, Go and Rust libraries share the same flow. The KMP and Apple sources are included for platform builds; review the verification notes in the download before shipping on devices.
API reference
Authorization: https://one.extremextechnology.com/oauth/authorize
API base: https://api.extremextechnology.com/one
| Endpoint | Usage |
|---|---|
GET /oauth/authorize | Browser flow: client_id, redirect_uri, state, scope and S256 code_challenge. This URL uses the ONE authorization host above. |
POST /oauth/token | JSON or form: grant_type=authorization_code, client_id, code, redirect_uri, code_verifier. Confidential web apps also send client_secret. Refresh uses grant_type=refresh_token, client_id and refresh_token. |
GET /oauth/userinfo | Bearer token. Returns only fields still granted by the user. |
GET /data?key=theme | Bearer token. Returns {key, value}; omit key to list data. |
PUT /data | Bearer token and application/json body: {"data":{"theme":"dark"}}. |
DELETE /data?key=theme | Bearer token. Removes one value. |
Errors: 401 requires a valid session or fresh sign-in; 403 indicates missing or revoked access; 400 indicates invalid input; 413 means the request is too large; 415 requires JSON. Do not automatically retry a consumed authorization code or reuse a rotated refresh token.
Native sign-in follows OAuth for Native Apps: an external browser, S256 PKCE, callback validation and no embedded client secret.
