Skip to content

Identity service endpoint returns 403 Bad_OAuth_Token with hybrid tokens from User-Agent flow #2945

Description

@caff-uk

Description

When using the User-Agent OAuth flow (response_type=hybrid_token), the identity service endpoint (/id/{orgId}/{userId}) returns 403 Bad_OAuth_Token when called with the hybrid token. This causes fetchUserIdentity() in AuthenticationUtilities.kt to fail, which has two critical downstream effects:

  1. Same-org multi-user account collision — When fetchUserIdentity() returns null, username is null, so buildAccountName() produces "null (instanceUrl) (appName)" for every user on the same org. addAccountExplicitly() silently overwrites the first user's credentials with the second user's, breaking multi-user support entirely.

  2. Missing identity fieldsdisplayName, username, email, and thumbnailUrl are never populated on the UserAccount, causing blank user profiles and null pointer exceptions in account switcher UIs.

Root Cause

In AuthenticationUtilities.kt, fetchUserIdentity() calls callIdentityService() with the hybrid token. The identity service endpoint rejects hybrid tokens with Bad_OAuth_Token, so the call fails and returns null.

The hybrid token works fine for REST API calls (e.g., querying the User sObject), just not for the identity service endpoint.

Steps to Reproduce

  1. Configure a Connected App with User-Agent flow (response_type=hybrid_token)
  2. Log in as User A on org X
  3. Log in as User B on the same org X
  4. User B's credentials overwrite User A's account (same accountName)
  5. Switching to User A now uses User B's tokens

Expected Behavior

fetchUserIdentity() should have a fallback when the identity service fails — e.g., querying the User sObject (/services/data/vXX.0/sobjects/User/{userId}?fields=Username,Email,Name,SmallPhotoUrl) which works with hybrid tokens.

At minimum, buildAccountName() should use tokenResponse.userId as a fallback when username is null, to prevent account collisions.

Suggested Fix

Replace or supplement callIdentityService() with a User sObject query fallback:

private suspend fun fetchUserIdentity(
    tokenResponse: TokenEndpointResponse
): IdServiceResponse? {
    // Try identity service first
    val identity = runCatching { callIdentityService(...) }.getOrNull()
    if (identity != null) return identity
    
    // Fallback: query User sObject (works with hybrid tokens)
    return runCatching { fetchUserIdentityViaUserQuery(tokenResponse) }.getOrNull()
}

And in onAuthFlowComplete():

val usernameForAccount = userIdentity?.username ?: tokenResponse.userId

Affected Versions

  • Confirmed on 13.2.0 and 13.2.1
  • Affects any app using User-Agent flow with hybrid_token grant type

Environment

  • Android SDK 13.2.0/13.2.1
  • OAuth User-Agent flow (hybrid_token grant type)
  • Multi-user enabled

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions