Skip to main content

Connect your users

Link the users authenticated in your app to their Octopus community profile through SSO, then handle refused connections, entitlements and guest sessions.

info

New to the SDK? Start with the Quickstart.

Before you begin​

  • The SDK is installed and initialized in SSO mode: see the Quickstart.
  • Your backend has a route that returns a JWT for the signed-in user, signed with your community's secret: see Generate a signed JWT.

How it works​

Your app stays in charge of sign-in. When a user signs in, your app passes their id and, optionally, their nickname, bio and picture to connectUser. When they sign out, it calls disconnectUser. Between the two, members who are not signed in browse the community as guests: they can read, and the SDK asks your app to sign them in when they try an action that needs an account.

The userId is the string that identifies your user for Octopus. It must be unique and always refer to the same user.

Client user token​

To prove that the connected user is legitimate, connectUser takes a token provider: a callback that returns a JWT signed by your backend. The SDK calls it when it connects the user and again each time it needs a fresh token, for example when the JWT expires or when you refresh entitlements. Add a route such as /generateOctopusSsoToken to your backend and call it from the token provider. The signing secret stays on your servers: see Generate a signed JWT.

App-managed profile fields​

The nickname, bio and picture you pass to connectUser are used in one of two ways, depending on whether you declared the field as app-managed when you initialized the SDK:

  • App-managed field: your app owns it. Each call to connectUser overwrites the field in the member's community profile, so it always matches your app. Members cannot edit it in the community: when they try, the SDK asks your app to open its own profile editor.
  • Other fields: the value you pass only fills the community profile the first time. Once the member edits their community profile, it stays separate from yours, and later changes on either side are not copied to the other.

Call connectUser again each time the user's profile changes in your app. The back office can also lock profile fields for the whole community: see Profile field lock.


1. Declare app-managed fields​

Declare the fields your app owns when you initialize the SDK, and open your own profile editor when a member tries to edit one of them. Skip this step if members may edit every profile field in the community.

Pass the fields to ConnectionMode.SSO, and open your profile editor from onNavigateToProfileEdit. The ProfileField it receives is the field the member tried to edit, or null.

import com.octopuscommunity.sdk.domain.model.ConnectionMode
import com.octopuscommunity.sdk.domain.model.ProfileField

OctopusSDK.initialize(
context = this,
apiKey = "YOUR_API_KEY",
connectionMode = ConnectionMode.SSO(
appManagedFields = setOf(ProfileField.NICKNAME, ProfileField.PICTURE),
),
)

// Where you host the community:
OctopusHomeScreen(
navController = navController,
onNavigateToLogin = { /* Open your sign-in screen */ },
onNavigateToProfileEdit = { field -> /* Open your profile editor */ },
)

Call connectUser as soon as your user is signed in, and again whenever their profile changes.

Parameters of OctopusSDK.connectUser, a suspending function:

  • user: ClientUser?: ClientUser(userId, profile), where profile is an optional ClientUser.Profile(nickname, bio, picture). picture is a Resource?: Resource.Remote(url) for a URL, Resource.Local(uri) for a local file.
  • tokenProvider: suspend () -> String: returns a fresh JWT from your backend.

It returns OctopusResult<ConnectionState, ClientUserError>.

import com.octopuscommunity.sdk.OctopusSDK
import com.octopuscommunity.sdk.domain.model.ClientUser
import com.octopuscommunity.sdk.domain.model.Resource
import com.octopuscommunity.sdk.domain.network.OctopusResult
import com.octopuscommunity.sdk.domain.repository.ClientUserError

val result = OctopusSDK.connectUser(
user = ClientUser(
userId = user.id,
profile = ClientUser.Profile(
nickname = user.nickname,
bio = user.bio,
picture = user.pictureUrl?.let { Resource.Remote(it) },
),
),
tokenProvider = { yourBackend.fetchOctopusToken(user.id) },
)
when (result) {
is OctopusResult.Success -> { /* Connected */ }
is OctopusResult.Failure.InvalidArguments -> when (val error = result.error) {
is ClientUserError.UserBanned -> showMessage(error.errorMessage)
else -> { /* MissingToken, ProfileError or Other: see step 3 */ }
}
is OctopusResult.Failure.Connection -> { /* Network or server failure: retry later */ }
}

3. Handle a refused connection​

A connection can be refused, for example when the token is invalid or when the member is banned. Show the ban message to the member: it comes from the server and is written for display. For the other errors, show your own message.

connectUser returns OctopusResult.Failure.InvalidArguments holding a ClientUserError:

ErrorMeaning
ClientUserError.UserBannedThe member is banned. errorMessage is displayable.
ClientUserError.MissingTokenThe token provider returned no token.
ClientUserError.ProfileErrorA profile field was refused, for example a nickname already taken.
ClientUserError.OtherAny other refusal.

A network or server failure returns OctopusResult.Failure.Connection instead.


4. Disconnect your user​

Call disconnectUser when your user signs out of your app. The member goes back to browsing as a guest.

disconnectUser is a suspending function. isUserConnected is a Flow<Boolean> you can collect to update your own UI:

OctopusSDK.disconnectUser()

OctopusSDK.isUserConnected.collect { isConnected ->
// Update your UI
}

You can collect the OctopusSDK flows, such as isUserConnected, connectionState or profile, before initialize runs ≥ 1.13.2: they stay silent until the SDK is initialized, then emit the real state.


5. Read connected-user entitlements ≥ 1.12.0​

The SDK exposes the entitlements of the connected user through their profile. Entitlements are opaque identifiers that your backend puts in the SSO JWT. The SDK does not compare them with group requirements: the server decides group access and the SDK reports it in OctopusGroup.canAccess (see Gate access to locked groups). Use entitlements only for your own UI, such as badges. The profile is empty until a user is connected.

OctopusSDK.profile.collect { profile ->
val entitlements: Set<String> = profile?.entitlements ?: emptySet()
// Update your UI
}

6. Refresh entitlements ≥ 1.12.0​

When a user's entitlements change in your backend, for example after a purchase, call refreshEntitlements(). The SDK calls your token provider for a fresh JWT, updates the profile, and refetches the groups so that open screens show the new access.

Refreshing works only for a connected member who is not a guest and was connected with a token provider.

when (val result = OctopusSDK.refreshEntitlements()) {
is OctopusResult.Success -> { /* OctopusSDK.profile and OctopusSDK.groups emit again */ }
is OctopusResult.Failure.InvalidArguments -> when (val error = result.error) {
is RefreshEntitlementsError.NoClientTokenProvider -> {} // Not SSO, or no token provider
is RefreshEntitlementsError.UserNotConnected -> {} // No connected user
is RefreshEntitlementsError.NoNetwork -> {} // Device offline
is RefreshEntitlementsError.UserBanned -> showMessage(error.errorMessage)
is RefreshEntitlementsError.ServerError -> {} // Server failure
}
else -> { /* Other transport failures */ }
}

7. Detect a guest session ≥ 1.12.6​

Tell a guest apart from a signed-in member. When the community requires sign-in for actions, the SDK opens a guest session again right after disconnectUser(), so a connection state alone does not prove that a member is signed in. Check the guest flag before you enable features for signed-in members.

import com.octopuscommunity.sdk.domain.repository.ConnectionRepository.ConnectionState

OctopusSDK.connectionState.collect { state ->
val isGuest = (state as? ConnectionState.Connected)?.isGuest ?: false
// Enable member features when !isGuest
}

8. Handle guest connection failures ≥ 1.14.0​

A banned member cannot be connected: connectUser is refused with the ban message (see Handle a refused connection), and the member keeps browsing as a guest. From then on, when they try to post, comment, reply or open their own profile, the SDK shows the ban message from the server instead of your sign-in prompt, in an alert with an OK button only. Reactions, votes, reporting and blocking stay available. Your app has nothing to add beyond handling the connectUser result.

The SDK keeps the refusal in memory. After a cold start, it learns about the ban again at the next connectUser, so keep calling connectUser at each start as usual.

≥ 1.14.2 The ban message appears before posting, commenting, replying or opening the member's own profile, whatever the community's sign-in settings.

connectUser returns the ban as ClientUserError.UserBanned inside OctopusResult.Failure.InvalidArguments, with a displayable errorMessage. If your app checks the connection itself with OctopusSDK.connectionRepository.checkConnection(), the same ban comes back as GuestError.UserBanned. Do not call connectionRepository.connectAsGuest() to check a connection: on success it replaces the stored session, including a connected member's, with a guest session.


Behavior and limits​

  • A token refused by the server leaves the member browsing as a guest. Check the connectUser result rather than assuming success.
  • App-managed fields are copied at each connectUser. The other fields are copied only until the member edits their community profile.
  • The back office can require explicit terms acceptance, lock profile fields or hide some content options: see Community settings.

Next steps​