Authentication
The Authentication module manages sign-in, account creation, token storage, and logout. After a successful sign-in, AFCore stores the session securely and attaches authentication tokens to all subsequent API requests.
How It Works
- Your app calls
signInOrCreateMemberwith an external user identifier (from your SSO, SAML, or custom identity provider). - If the member exists, AFCore signs them in. If not, it creates the member and signs them in automatically.
- AFCore stores the access and refresh tokens in platform-secure storage (Android EncryptedSharedPreferences / iOS Keychain).
- All subsequent SDK calls include the token automatically -- no manual header management required.
Sign In or Create a Member
Use signInOrCreateMember to authenticate using an external identity. Optional profile fields seed the member's profile on first creation.
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
externalUserId | String | Yes | Unique identifier from your identity provider. |
email | String? | No | Member's email address. |
firstName | String? | No | Given name. |
middleName | String? | No | Middle name. |
lastName | String? | No | Family name. |
gender | String? | No | "M" or "F". |
dateOfBirth | String? | No | ISO-8601 format (YYYY-MM-DD). |
phoneNumber | String? | No | Contact number. |
address1 | String? | No | Primary street address. |
address2 | String? | No | Secondary address line. |
city | String? | No | City. |
state | String? | No | Two-letter state code. |
zip | String? | No | Postal code. |
attributes | Map<String, String?>? | No | Freeform key-value pairs for custom profile data. |
Returns
AFResult -- check result.status (Boolean) for success and result.statusMessage for details on failure.
- Android (Kotlin)
- iOS (Swift)
lifecycleScope.launch {
try {
val result = AFCore.authentication().signInOrCreateMember(
externalUserId = "sso-user-98765",
email = "alex@example.com",
firstName = "Alex",
lastName = "Rivera",
dateOfBirth = "1988-07-12",
attributes = mapOf("groupId" to "GR-2504")
)
if (result.status) {
// Session is active. Navigate to the main screen.
navigateToHome()
} else {
// The server rejected the request. Show the reason.
showError("Sign-in failed: ${result.statusMessage}")
}
} catch (e: Exception) {
// Network or unexpected error.
showError("Could not connect: ${e.message}")
}
}
do {
let result = try await AFCore.shared.authentication().signInOrCreateMember(
externalUserId: "sso-user-98765",
email: "alex@example.com",
firstName: "Alex",
lastName: "Rivera",
dateOfBirth: "1988-07-12",
attributes: ["groupId": "GR-2504"]
)
if result.status {
navigateToHome()
} else {
showError("Sign-in failed: \(result.statusMessage ?? "Unknown error")")
}
} catch {
showError("Could not connect: \(error)")
}
Sign In with Credentials
Use authenticateWithCredentials to sign in a member with a username and password issued by your organization. On success, AFCore stores the session and attaches tokens to subsequent requests automatically.
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
username | String | Yes | Member's username. |
password | String | Yes | Member's password. |
Returns
AFResult -- check result.status (Boolean) for success and result.statusMessage for details on failure.
- Android (Kotlin)
- iOS (Swift)
lifecycleScope.launch {
try {
val result = AFCore.authentication().authenticateWithCredentials(
username = usernameField.text.toString(),
password = passwordField.text.toString()
)
if (result.status) {
navigateToHome()
} else {
showError("Invalid credentials: ${result.statusMessage}")
}
} catch (e: Exception) {
showError("Could not connect: ${e.message}")
}
}
Task {
do {
let result = try await AFCore.shared.authentication().authenticateWithCredentials(
username: usernameField.text ?? "",
password: passwordField.text ?? ""
)
if result.status {
navigateToHome()
} else {
showError("Invalid credentials: \(result.statusMessage ?? "Unknown error")")
}
} catch {
showError("Could not connect: \(error)")
}
}
Authenticate with Tokens (Experimental)
authenticateWithTokens is marked experimental and should not be adopted in production yet. The signature and behaviour may change in any future release without a major-version bump. The server-side contract (claim shape, refresh semantics, error envelope) is still being validated. Wire it up behind a feature flag if you are evaluating it.
Use authenticateWithTokens when your own auth backend (or a server-side handshake) has already issued an AFCore-compatible access + refresh token pair and you want the SDK to adopt that session without running its own credential exchange. The SDK parses the access token's JWT payload for identity claims, persists both tokens, and emits LOGGED_IN on AFCore.authentication().sessionState (the deprecated AFCore.sessionState alias reflects the same change).
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
accessToken | String | Yes | JWT access token. Must contain an exp claim and one of advantaMemberId / memberId, otherwise the call returns a failed AFResult without persisting anything. |
refreshToken | String | Yes | Companion refresh token the SDK uses to obtain a new access token on subsequent 401 responses. Must be non-blank. |
Returns
AFResult -- status = true when tokens were persisted and LOGGED_IN was emitted; false when required claims were missing, when either token was blank, or when the extracted memberId was "0".
Re-authentication
On a 401 during normal traffic, the SDK first tries to refresh with the supplied refreshToken. If that also fails, the SDK clears the local session and emits SESSION_EXPIRED. Subscribe to session state and, on SESSION_EXPIRED, obtain a fresh token pair and call authenticateWithTokens again -- re-calling overwrites the previously stored pair and re-emits LOGGED_IN.
- Android (Kotlin)
- iOS (Swift)
lifecycleScope.launch {
val (access, refresh) = myAuthClient.exchange()
val result = AFCore.authentication().authenticateWithTokens(access, refresh)
if (result.status) {
navigateToHome()
} else {
showError("Token auth failed: ${result.statusMessage}")
}
}
AFCore.shared.authentication().subscribeToSessionState { state in
if state == .sessionExpired {
Task {
do {
let pair = await myAuthClient.exchange()
let result = try await AFCore.shared.authentication()
.authenticateWithTokens(accessToken: pair.access,
refreshToken: pair.refresh)
if result.status {
navigateToHome()
}
} catch {
showError("Token auth failed: \(error)")
}
}
}
}
Logout
logout() clears all stored tokens and local SDK state for the current member. Call this when the user explicitly signs out.
- Android (Kotlin)
- iOS (Swift)
lifecycleScope.launch {
val result = AFCore.authentication().logout()
if (result.status) {
// Tokens cleared. Return to the login screen.
navigateToSignIn()
}
}
Task {
do {
let result = try await AFCore.shared.authentication().logout()
if result.status {
navigateToSignIn()
}
} catch {
showError("Logout failed: \(error)")
}
}
After logout, stop any active geofence monitoring and cancel background sync tasks. The member must sign in again before making any further SDK calls.
Best Practices
- Call sign-in once per session. AFCore handles token refresh automatically. You do not need to re-authenticate on every app launch -- the stored session persists across launches.
- Pass profile fields on first sign-in. Optional parameters like
firstName,email, anddateOfBirthseed the server-side profile. If the member already exists, these fields are ignored unless the server is configured to update them. - Handle token expiry gracefully. If a token refresh fails (for example, after extended offline periods), AFCore throws an exception on the next API call. Catch it and redirect to sign-in.
- Secure the external user ID. The
externalUserIdis the bridge between your identity system and AFCore. Validate it server-side if possible.
Error Handling
| Scenario | Recommended Action |
|---|---|
result.status == false | Display result.statusMessage to the user. Common causes: invalid credentials, disabled account, or server maintenance. |
| Network exception | Show a retry option. AFCore does not retry authentication automatically. |
| Token refresh failure | Redirect to sign-in. The session has expired. |
Quick Reference
- Android (Kotlin)
- iOS (Swift)
// Android
AFCore.authentication().signInOrCreateMember(externalUserId = "id", ...)
AFCore.authentication().authenticateWithCredentials(username = "user", password = "pass")
AFCore.authentication().authenticateWithTokens(accessToken = "...", refreshToken = "...") // experimental
AFCore.authentication().logout()
// iOS
try await AFCore.shared.authentication().signInOrCreateMember(externalUserId: "id", ...)
try await AFCore.shared.authentication().authenticateWithCredentials(username: "user", password: "pass")
try await AFCore.shared.authentication().authenticateWithTokens(accessToken: "...", refreshToken: "...") // experimental
try await AFCore.shared.authentication().logout()