Skip to content

Adds a mobile token provider to the SDK - #123

Open
cb-haripriyan wants to merge 2 commits into
masterfrom
feat/OMNISUB-7609
Open

Adds a mobile token provider to the SDK#123
cb-haripriyan wants to merge 2 commits into
masterfrom
feat/OMNISUB-7609

Conversation

@cb-haripriyan

@cb-haripriyan cb-haripriyan commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

CHANGELOG

REPLACE_ME_WITH_CHANGELOG

SUMMARY

REPLACE_ME_WITH_SUMMARY_OF_THE_CHANGES

FUNCTIONAL AUTOMATION CHANGES PR

  • Yes
    • If Yes, PR :
  • No
    • If No, Reason:

AUTOMATION TEST REPORT URL

REPLACE_ME_WITH_TEST_REPORT_URL

AREAS OF IMPACT

REPLACE_ME_WITH_AREAS_OF_IMPACT_OR_NA

TYPE OF CHANGE

  • 🐞 Bugfix
  • 🌟 Feature
  • ✨ Enhancement
  • 🧪 Unit Test Cases
  • 📔 Documentation
  • ⚙️ Chore - Build Related / Configuration / Others

DOCUMENTATION

REPLACE_ME_WITH_DOCUMENTATION_LINK_OR_NA

Adds mobile-token authentication to the Android SDK. It introduces token-provider configuration, token refresh, dynamic authorization selection, and one retry for eligible 401 responses. It updates the example app with mobile-token configuration support.

@snyk-io

snyk-io Bot commented Aug 4, 2026

Copy link
Copy Markdown

Snyk checks have passed. No issues have been found so far.

Status Scan Engine Critical High Medium Low Total (0)
Open Source Security 0 0 0 0 0 issues
Licenses 0 0 0 0 0 issues
Code Security 0 0 0 0 0 issues
Secrets 0 0 0 0 0 issues

💻 Catch issues earlier using the plugins for VS Code, JetBrains IDEs, Visual Studio, and Eclipse.

@coderabbitai

coderabbitai Bot commented Aug 4, 2026

Copy link
Copy Markdown

Review Change Stack

Walkthrough

Changes

Mobile-token authentication now supports provider-based configuration, dynamic authorization, token refresh, and one retry after a 401 response. The example app demonstrates token-provider configuration and result logging.

Mobile-token authentication

Layer / File(s) Summary
Token configuration and authorization
chargebee/src/main/java/com/chargebee/android/Chargebee.kt
Adds CBMobileTokenProvider, mobile-token state, dynamic encodedApiKey, token-based configure, and refreshMobileToken. Existing configuration methods reset token state.
Token refresh and retry
chargebee/src/main/java/com/chargebee/android/Chargebee.kt, chargebee/src/main/java/com/chargebee/android/network/MobileTokenAuthenticator.kt
Refreshes tokens through the provider and retries eligible 401 responses once, with a 30-second timeout.
Authenticated Retrofit transport
chargebee/src/main/java/com/chargebee/android/resources/BaseResource.kt
Configures Retrofit with an OkHttpClient that uses MobileTokenAuthenticator.
Example mobile-token integration
app/src/main/java/com/chargebee/example/MainActivity.kt
Configures Chargebee with a backend token provider, logs configuration results, and returns a placeholder token.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Chargebee
  participant TokenProvider
  participant Retrofit
  participant MobileTokenAuthenticator
  Chargebee->>TokenProvider: Request initial or refreshed token
  TokenProvider-->>Chargebee: Return token
  Chargebee->>Retrofit: Send request with mobile-token authorization
  Retrofit-->>MobileTokenAuthenticator: Return 401 response
  MobileTokenAuthenticator->>Chargebee: Request token refresh
  Chargebee->>TokenProvider: Invoke provider
  TokenProvider-->>Chargebee: Return refreshed token
  MobileTokenAuthenticator->>Retrofit: Retry request with refreshed authorization
Loading
🚥 Pre-merge checks | ✅ 1
✅ Passed checks (1 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches 💡 1
🛠️ Fix failing CI checks 💡
  • Create stacked PR
  • Commit on current branch

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@chargebee/src/main/java/com/chargebee/android/Chargebee.kt`:
- Around line 159-167: Scope token refresh and authentication callbacks to the
active configuration so callbacks from an older configure call cannot update
mobileToken or authenticate requests for the new site. Add a configuration
generation/version, capture it when starting each refresh, and ignore callbacks
whose generation no longer matches; also clear mobileToken before the
token-provider overload refreshes. Add a delayed-provider regression test
covering reconfiguration and stale callback delivery.

In
`@chargebee/src/main/java/com/chargebee/android/network/MobileTokenAuthenticator.kt`:
- Around line 22-29: Update the authenticator logic around refreshTokenBlocking
so retry tracking uses a request-scoped marker rather than
response.priorResponse(), allowing token refresh after a redirect-then-401 while
preventing a second authenticator retry. Mark the rebuilt request before
returning it, preserve null when that marker is already present, and add
coverage verifying the provider runs once followed by exactly one retry.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Enterprise

Run ID: 671cb88d-78eb-487c-83cc-511951c38795

📥 Commits

Reviewing files that changed from the base of the PR and between 35d2710 and 93b2cb9.

📒 Files selected for processing (3)
  • chargebee/src/main/java/com/chargebee/android/Chargebee.kt
  • chargebee/src/main/java/com/chargebee/android/network/MobileTokenAuthenticator.kt
  • chargebee/src/main/java/com/chargebee/android/resources/BaseResource.kt

Comment on lines +159 to +167
this.site = site
this.publishableApiKey = ""
this.encodedPublishableApiKey = ""
this.baseUrl = "https://${site}.chargebee.com/api/"
this.allowErrorLogging = allowErrorLogging
this.sdkKey = sdkKey
this.applicationId = packageName
this.tokenProvider = tokenProvider

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🔴 Critical | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -eu
printf '%s\n' '--- Chargebee.kt relevant declarations and methods ---'
rg -n -C 8 'mobileToken|encodedApiKey|configure|refreshMobileToken|tokenProvider|publishableApiKey|baseUrl' chargebee/src/main/java/com/chargebee/android/Chargebee.kt
printf '%s\n' '--- related usages ---'
rg -n -C 3 'encodedApiKey|mobileToken|refreshMobileToken|CBMobileTokenProvider' chargebee/src test* 2>/dev/null || true
printf '%s\n' '--- file outline ---'
ast-grep outline chargebee/src/main/java/com/chargebee/android/Chargebee.kt 2>/dev/null || true

Repository: chargebee/chargebee-android

Length of output: 31015


🏁 Script executed:

#!/bin/bash
set -eu
printf '%s\n' '--- MobileTokenAuthenticator ---'
cat -n chargebee/src/main/java/com/chargebee/android/network/MobileTokenAuthenticator.kt
printf '%s\n' '--- bounded interleaving verifier ---'
python3 - <<'PY'
import base64

state = {
    "site": "",
    "mobileToken": "",
    "tokenProvider": None,
    "encodedPublishableApiKey": "",
}

def configure_token(site, provider):
    state["site"] = site
    state["encodedPublishableApiKey"] = ""
    state["tokenProvider"] = provider
    # The implementation calls refreshMobileToken, which captures provider.
    return provider

def callback(provider, token):
    # Exact behavior of Chargebee.refreshMobileToken's callback.
    if token:
        state["mobileToken"] = token
        return True
    return False

def encoded_api_key():
    token = state["mobileToken"]
    if token:
        raw = f"{token}:".encode()
        return "Basic " + base64.b64encode(raw).decode()
    return state["encodedPublishableApiKey"]

provider_a = lambda: None
provider_b = lambda: None

captured_a = configure_token("site-a", provider_a)
captured_b = configure_token("site-b", provider_b)
assert state["site"] == "site-b"
assert captured_a is provider_a and captured_b is provider_b

callback(captured_a, "token-a")
assert state["mobileToken"] == "token-a"
print({
    "current_site": state["site"],
    "token_after_old_callback": state["mobileToken"],
    "authorization_header": encoded_api_key(),
    "stale_token_selected": state["site"] == "site-b" and state["mobileToken"] == "token-a",
})
PY

Repository: chargebee/chargebee-android

Length of output: 2567


Sensitive Data Exposure (CWE-362): Concurrent Execution using Shared Resource with Improper Synchronization ('Race Condition')

Reachability: Internal · Exploitability: Moderate

Scope token refresh results to their originating configuration.

An older provider callback can overwrite mobileToken after a new configure call. Requests can then send the previous tenant's token to the new site's endpoint. The token-provider overload also does not clear the existing token before refresh. Reject stale refresh and authentication callbacks with a configuration generation, and add a delayed-provider regression test.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@chargebee/src/main/java/com/chargebee/android/Chargebee.kt` around lines 159
- 167, Scope token refresh and authentication callbacks to the active
configuration so callbacks from an older configure call cannot update
mobileToken or authenticate requests for the new site. Add a configuration
generation/version, capture it when starting each refresh, and ignore callbacks
whose generation no longer matches; also clear mobileToken before the
token-provider overload refreshes. Add a delayed-provider regression test
covering reconfiguration and stale callback delivery.

Source: Path instructions

Comment on lines +22 to +29
// Retry only once: a non-null priorResponse means we already refreshed and retried.
if (response.priorResponse() != null) {
return null
}
val refreshedHeader = refreshTokenBlocking() ?: return null
return response.request().newBuilder()
.header("Authorization", refreshedHeader)
.build()

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -eu
printf '%s\n' '--- candidate file ---'
fd -i 'MobileTokenAuthenticator.kt' . -x sh -c 'cat -n "$1"' sh {}
printf '%s\n' '--- OkHttp declarations and authenticator usage ---'
rg -n -S 'okhttp|Authenticator|MobileTokenAuthenticator|priorResponse|refreshTokenBlocking' \
  -g '!build' -g '!**/node_modules/**' .
printf '%s\n' '--- relevant dependency files ---'
fd -i '(build.gradle|build.gradle.kts|libs.versions.toml|gradle.properties|pom.xml)' . -x sh -c '
  if rg -n -i "okhttp|com.squareup.okhttp" "$1" >/dev/null; then
    echo "### $1"
    rg -n -i -C 2 "okhttp|com.squareup.okhttp" "$1"
  fi
' sh {}

Repository: chargebee/chargebee-android

Length of output: 4446


🏁 Script executed:

#!/bin/bash
set -eu
printf '%s\n' '--- project files related to dependencies ---'
git ls-files | rg '(^|/)(build\.gradle(\.kts)?|settings\.gradle(\.kts)?|libs\.versions\.toml|gradle\.properties|pom\.xml)$|(^|/)gradle/'
printf '%s\n' '--- dependency declarations ---'
rg -n -i -C 3 'okhttp|com\.squareup\.okhttp|com\.squareup\.okhttp3' \
  $(git ls-files | rg '(^|/)(build\.gradle(\.kts)?|settings\.gradle(\.kts)?|libs\.versions\.toml|gradle\.properties|pom\.xml)$') || true
printf '%s\n' '--- client construction ---'
cat -n chargebee/src/main/java/com/chargebee/android/resources/BaseResource.kt

Repository: chargebee/chargebee-android

Length of output: 1613


🏁 Script executed:

#!/bin/bash
set -eu
printf '%s\n' '--- root build configuration ---'
cat -n build.gradle
printf '%s\n' '--- chargebee module build configuration ---'
cat -n chargebee/build.gradle
printf '%s\n' '--- settings and properties ---'
cat -n settings.gradle
cat -n gradle.properties
printf '%s\n' '--- tests and mocks related to authentication or redirects ---'
rg -n -i -C 4 'MobileTokenAuthenticator|authenticator|401|redirect|tokenProvider|refreshMobileToken' \
  chargebee app -g '*Test*' -g '*test*' -g '*.kt' -g '*.java' || true

Repository: chargebee/chargebee-android

Length of output: 21784


🌐 Web query:

OkHttp Response.priorResponse redirects authenticator 401 documentation RetryAndFollowUpInterceptor

💡 Result:

In OkHttp, the Response.priorResponse property and the Authenticator interface are key components for handling authentication challenges (such as 401 Unauthorized or 407 Proxy Authentication Required) within the RetryAndFollowUpInterceptor [1][2][3]. 1. Response.priorResponse The priorResponse property returns the previous response that triggered the current request (e.g., a 401 challenge) [1][4]. If the current request was not triggered by an automatic retry, this returns null [1]. This property is primarily used to detect previous attempts to authenticate or follow redirects, helping prevent infinite request-response loops [2][5]. 2. Authenticator and 401 Handling When a server returns a 401 (Unauthorized) status code, OkHttp calls the registered Authenticator implementation [2][6]. The authenticator is responsible for providing a new request with the appropriate "Authorization" header or returning null to decline the challenge [2][7]. To prevent infinite loops when credentials are invalid or expired, the standard practice is to check the current request's headers before attempting to re-authenticate [2][7][8]. If the request already contains an "Authorization" header, the authenticator should return null [2][8]. 3. RetryAndFollowUpInterceptor This internal interceptor is responsible for coordinating redirects, retries, and authentication challenges [9][3][6]. When it receives a 401 response, it calls the Authenticator [3][6]. If the authenticator returns a new, non-null request, the interceptor retries the call with this new request [3][6]. 4. Detecting Attempt Counts If you need to limit the number of authentication retries, you can calculate the current attempt count by traversing the chain of prior responses [2][5]. The following common pattern is used for this purpose: private fun responseCount(response: Response): Int { var result = 1 var prior = response.priorResponse while (prior!= null) { result++ prior = prior.priorResponse } return result } By checking this count or simply verifying if an "Authorization" header is already present, you can safely abort authentication attempts if they are not successful [2][7][8].

Citations:


🏁 Script executed:

#!/bin/bash
set -eu
printf '%s\n' '--- Retrofit 2.9.0 resolved OkHttp dependency ---'
curl -fsSL https://repo1.maven.org/maven2/com/squareup/retrofit2/retrofit/2.9.0/retrofit-2.9.0.pom |
  sed -n '/<artifactId>okhttp<\/artifactId>/,/<\/dependency>/p'
printf '%s\n' '--- OkHttp 3.14.9 redirect and authentication response-chain logic ---'
curl -fsSL https://raw.githubusercontent.com/square/okhttp/okhttp_3.14.x/okhttp/src/main/java/okhttp3/internal/http/RetryAndFollowUpInterceptor.java |
  rg -n -C 5 'priorResponse|followUpRequest|followRedirects|401|Authenticator'
printf '%s\n' '--- behavioral probe for the guard ---'
python3 - <<'PY'
class Response:
    def __init__(self, code, prior=None):
        self.code = code
        self.prior = prior

def authenticate(response, provider_configured=True):
    if not provider_configured:
        return None
    if response.prior is not None:
        return None
    return "retry-with-refreshed-header"

cases = {
    "direct 401": Response(401),
    "redirect then 401": Response(401, Response(302)),
    "redirect chain then 401": Response(401, Response(302, Response(301))),
}
for name, response in cases.items():
    print(f"{name}: {authenticate(response)!r}")
PY

Repository: chargebee/chargebee-android

Length of output: 4553


Track this authenticator’s retry separately from priorResponse().

When OkHttp follows a redirect before returning 401, priorResponse() is non-null. authenticate() then returns null and skips token refresh. Use a request-scoped retry marker and add a redirect-then-401 test. The provider must run once, followed by one retry.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@chargebee/src/main/java/com/chargebee/android/network/MobileTokenAuthenticator.kt`
around lines 22 - 29, Update the authenticator logic around refreshTokenBlocking
so retry tracking uses a request-scoped marker rather than
response.priorResponse(), allowing token refresh after a redirect-then-401 while
preventing a second authenticator retry. Mark the rebuilt request before
returning it, preserve null when that marker is already present, and add
coverage verifying the provider runs once followed by exactly one retry.

Source: Path instructions

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@app/src/main/java/com/chargebee/example/MainActivity.kt`:
- Around line 199-227: Make configureWithMobileToken functional by replacing
fetchMobileToken’s placeholder completion with an asynchronous request to the
example backend that returns a real mobile token, passing null on failure and
completing exactly once. Update the visible Configure flow to invoke
configureWithMobileToken instead of the publishableApiKey path, while preserving
the existing configuration result handling and one-time token refresh behavior.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Enterprise

Run ID: 8686bfbf-cbc2-4590-916a-875f7f0ffe2e

📥 Commits

Reviewing files that changed from the base of the PR and between 93b2cb9 and e23b247.

📒 Files selected for processing (1)
  • app/src/main/java/com/chargebee/example/MainActivity.kt

Comment on lines +199 to +227
private fun configureWithMobileToken() {
Chargebee.configure(
site = "cb-abc-test",
sdkKey = "SDK-KEY",
packageName = this.packageName,
tokenProvider = { completion ->
// Ask your backend for a fresh mobile token (it mints one via
// `create_mobile_token`), then hand the raw token back to the SDK.
// Pass null if the token could not be obtained.
fetchMobileToken(completion)
}
) {
when (it) {
is ChargebeeResult.Success -> {
Log.i(javaClass.simpleName, "Configured with mobile token")
}
is ChargebeeResult.Error -> {
Log.e(javaClass.simpleName, "Configuration failed: ${it.exp.message}")
}
}
}
}

/*
* Stand-in for the call to your own backend that returns a Chargebee mobile token.
* Replace the body with a real network request to your server.
*/
private fun fetchMobileToken(completion: (String?) -> Unit) {
completion("cb_mob_replace_with_token_from_your_backend")

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

Make the mobile-token example functional before merge.

fetchMobileToken always returns a placeholder token. Any request using configureWithMobileToken() will fail authentication, including the one-time refresh retry. The visible Configure flow still uses publishableApiKey, so the new path is not integrated into the example. Replace the placeholder with an asynchronous backend request and invoke this configuration path from the example flow.

As per path instructions, this is a functionality-breaking issue that must be resolved before merge.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/src/main/java/com/chargebee/example/MainActivity.kt` around lines 199 -
227, Make configureWithMobileToken functional by replacing fetchMobileToken’s
placeholder completion with an asynchronous request to the example backend that
returns a real mobile token, passing null on failure and completing exactly
once. Update the visible Configure flow to invoke configureWithMobileToken
instead of the publishableApiKey path, while preserving the existing
configuration result handling and one-time token refresh behavior.

Source: Path instructions

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant