Banking Android Application
Banking Android Application
These topics are critical for banking and payment applications because mistakes can cause:
* Unauthorized access
* Session hijacking
* Duplicate payments
* Incorrect transaction status
* Financial loss
* Poor customer trust
flowchart LR
A["Authentication"] --> B["Token Management"]
B --> C["Payment Submission"]
C --> D["Idempotency"]
D --> E["Transaction Lifecycle"]
E --> F["Status Reconciliation"]---
1. Token Management
1.1 What is token management?
Token management controls the authenticated user session after login.
It includes:
* Access token
* Refresh token
* Token expiry
* Refresh-token rotation
* Simultaneous 401 handling
* Logout
Basic token flow
flowchart TD
A["User logs in"] --> B["Server verifies credentials"]
B --> C["Access and refresh tokens issued"]
C --> D["Access protected APIs"]
D --> E{"Access token valid?"}
E -- Yes --> D
E -- No --> F["Refresh tokens"]
F --> D---
1.2 Access token
An access token is a short-lived credential used to call protected APIs.
Authorization: Bearer ACCESS_TOKENImportant points
* Short expiry time
* Sent with normal API requests
* Validated by the server
* Should remain in memory when practical
* Must only be transmitted through HTTPS
* Must never appear in logs or URL parameters
---
1.3 Refresh token
A refresh token obtains a new access token when the current access token expires.
Important points
* Longer-lived than an access token
* Sent only to the authentication server
* Requires stronger storage protection
* Should support server-side revocation
* Should normally be rotated
* Must be cleared during logout
Access token vs refresh token
| Access token | Refresh token |
|---|---|
| Calls protected APIs | Obtains new tokens |
| Short-lived | Longer-lived |
| Sent frequently | Sent only during refresh |
| Usually kept in memory | May need encrypted persistence |
| Limited attack window | More valuable if stolen |
---
1.4 Token expiry
The authentication server may return:
{
"access_token": "access-a1",
"refresh_token": "refresh-r1",
"expires_in": 900
}Here, the access token expires after 900 seconds.
val expiresAt =
System.currentTimeMillis() + response.expiresIn * 1_000LUse a small safety buffer:
fun isExpired(expiresAt: Long): Boolean {
val safetyBuffer = 30_000L
return System.currentTimeMillis() >=
expiresAt - safetyBuffer
}The server remains authoritative. It can reject a token before its calculated expiry because of:
* Logout
* Password change
* Token revocation
* Account restriction
* Suspicious activity
* Session-policy changes
---
1.5 Refresh-token rotation
Refresh-token rotation means that every successful refresh returns a new refresh token and invalidates the old one.
Login:
Access A1 + Refresh R1
Refresh using R1:
Access A2 + Refresh R2
R1 becomes invalidsequenceDiagram
participant A as Android App
participant S as Auth Server
A->>S: Refresh using R1
S->>S: Invalidate R1
S-->>A: Return A2 and R2
A->>A: Replace both tokens atomicallyWhy is rotation important?
If an old refresh token is used again, it may indicate token theft. The server can revoke the session or complete token family.Important implementation rule
Save the new access and refresh tokens together:
interface TokenStore {
suspend fun getTokens(): AuthTokens?
suspend fun replaceTokens(tokens: AuthTokens)
suspend fun clear()
}Do not save the access token first and refresh token later. A crash between those operations can create an inconsistent session.
---
1.6 Simultaneous 401 handling
Multiple parallel APIs may receive 401 Unauthorized at the same time:
Accounts API → 401
Balance API → 401
Transactions API → 401
Profile API → 401If every request refreshes independently, refresh-token rotation may fail.Correct approach
flowchart TD
A["Multiple requests receive 401"] --> B{"Refresh already running?"}
B -- No --> C["Start one refresh request"]
B -- Yes --> D["Wait for existing refresh"]
C --> E{"Refresh successful?"}
E -- Yes --> F["Save new token pair"]
F --> G["Retry waiting requests once"]
D --> G
E -- No --> H["Clear session"]
H --> I["Open login screen"]Android implementation
Use:
* OkHttp Authenticator
* Kotlin Mutex
* Single refresh request
* One retry per failed request
* Maximum retry check
* Proper logout after permanent refresh failure
Conceptual logic:
private val refreshMutex = Mutex()
suspend fun refreshSafely(
failedAccessToken: String
): String? {
return refreshMutex.withLock {
val currentTokens = tokenStore.getTokens()
?: return@withLock null
// Another request already refreshed the token.
if (currentTokens.accessToken != failedAccessToken) {
return@withLock currentTokens.accessToken
}
val newTokens = authApi.refresh(
currentTokens.refreshToken
)
tokenStore.replaceTokens(newTokens)
newTokens.accessToken
}
}Avoid infinite loops
If a retried request again returns `401`, do not continuously refresh. End the session or return the failure.---
1.7 Logout
Logout is more than opening the login screen.
flowchart TD
A["User selects logout"] --> B["Revoke server session"]
B --> C["Clear local tokens"]
C --> D["Clear sensitive cache"]
D --> E["Cancel authenticated work"]
E --> F["Clear notifications"]
F --> G["Clear navigation back stack"]
G --> H["Open login screen"]Logout checklist
* Revoke the refresh token or session where supported.
* Clear access and refresh tokens.
* Clear sensitive Room data according to business requirements.
* Cancel user-specific WorkManager jobs.
* Close authenticated WebSockets.
* Clear sensitive notifications.
* Remove protected screens from the back stack.
* Clear user-specific in-memory data.
OWASP MASVS requires secure authentication protocols, correct local authentication and extra protection for sensitive operations. OWASP MASVS Authentication
---
Token management interview questions
Why do we need a refresh token?
It allows the application to obtain a new short-lived access token without asking the user to log in repeatedly.
Where should tokens be stored?
Keep the access token in memory when practical. If persistence is required, encrypt tokens and protect the encryption key using Android Keystore.
How do you handle five simultaneous 401 responses?
Run only one refresh request using synchronization such as a Mutex. Other requests wait and then retry once using the new access token.
What happens if token refresh fails?
For permanent authentication failures, clear the session and ask the user to log in again. Temporary network failures should be distinguished from invalid or revoked refresh tokens.---
2. Transaction Lifecycle
2.1 What is a transaction lifecycle?
The transaction lifecycle represents every state through which a payment may pass.
A payment is not always immediately successful or failed.
Transaction states
| State | Meaning | Application behaviour |
|---|---|---|
| Initiated | Payment request was created | Save the local attempt |
| Processing | Bank is processing the payment | Disable duplicate submission |
| Pending | Final result is delayed | Check status later |
| Successful | Server confirms completion | Display receipt |
| Failed | Server confirms rejection or failure | Show reason |
| Reversed | Previous financial effect was undone | Display reversal |
| Unknown | App cannot determine the result | Reconcile with server |
State flow
stateDiagram-v2
[*] --> Initiated
Initiated --> Processing
Processing --> Successful
Processing --> Failed
Processing --> Pending
Processing --> Unknown
Pending --> Successful
Pending --> Failed
Pending --> Reversed
Pending --> Unknown
Successful --> Reversed
Unknown --> Processing
Unknown --> Pending
Unknown --> Successful
Unknown --> FailedExact state names and transitions depend on the payment API being integrated.
---
2.2 Initiated
The application created and submitted a payment attempt.
Store:
* Local transaction ID
* Idempotency key
* Amount and currency
* Payee reference
* Creation time
* Initial status
Initiated does not mean money was transferred.
---
2.3 Processing
The bank or payment system is processing the transaction.
Recommended message:
Your payment is being processed.
Please do not submit it again.Disable another submission for the same payment attempt.
---
2.4 Pending
The server knows the transaction exists, but the final result is not available.
Possible reasons:
* Banking network delay
* Fraud review
* External payment-system delay
* Additional authorization
* Scheduled execution
Pending is not failure.
---
2.5 Successful
The authoritative server confirms that the payment completed.
The receipt can display:
* Payment reference
* Amount and currency
* Payee
* Completion date
* Confirmed status
> HTTP 200 does not automatically mean payment success. It means the HTTP request was handled. Always inspect the business status.
---
2.6 Failed
The server confirms that the payment was rejected or could not be completed.
Examples:
* Insufficient funds
* Invalid beneficiary
* Limit exceeded
* Authorization failure
* Expired consent
* Business validation failure
Do not automatically retry business failures.
---
2.7 Reversed
A previously accepted or successful transaction has been undone.
Payment successful
↓
Settlement or processing issue
↓
Funds returned
↓
Payment reversedDo not display reversal simply as failure. Clearly tell the user that an earlier payment was reversed.
---
2.8 Unknown
The application cannot determine the final result.
Common causes:
* Request timeout
* Response lost
* App process killed
* Network disconnected
* Status service unavailable
Unknown ≠ Failed
Unknown ≠ SuccessfulThe application should check the authoritative transaction status.
---
Kotlin transaction model
enum class TransactionStatus {
INITIATED,
PROCESSING,
PENDING,
SUCCESSFUL,
FAILED,
REVERSED,
UNKNOWN
}data class PaymentTransaction(
val localTransactionId: String,
val serverPaymentId: String?,
val idempotencyKey: String,
val amountMinorUnits: Long,
val currency: String,
val status: TransactionStatus
)Use minor units or BigDecimal for money:
// £10.50 represented as 1050 pence
val amountMinorUnits = 1050LAvoid Double for financial amounts because floating-point calculations can introduce precision problems.
---
Transaction lifecycle interview questions
Is HTTP 200 equal to payment success?
No. The response’s business status determines payment success.
What is the difference between pending and unknown?
Pending means the server knows the transaction and reports that completion is delayed. Unknown means the application cannot currently determine the authoritative result.
Can a successful transaction later become reversed?
Yes. Settlement or payment-network events may reverse the financial effect.
Where should transaction status be stored?
The local database can cache the last known status, but the banking server remains the source of truth.
---
3. Idempotency
3.1 What is idempotency?
Idempotency ensures that sending the same logical payment request multiple times creates only one financial transaction.
One request → One payment
Repeated request → Still one paymentDuplicate-payment scenario
flowchart TD
A["App submits payment"] --> B["Server executes payment"]
B --> C["Response is lost"]
C --> D["App sends request again"]
D --> E{"Same idempotency key?"}
E -- Yes --> F["Return existing payment result"]
E -- No --> G["Duplicate-payment risk"]---
3.2 Idempotency key
Generate a unique key for each logical payment attempt:
val idempotencyKey = UUID.randomUUID().toString()Example:
POST /domestic-payments
Authorization: Bearer ACCESS_TOKEN
x-idempotency-key: 550e8400-e29b-41d4-a716-446655440000
Content-Type: application/jsonUK Open Banking payment examples use x-idempotency-key. The same key must not be reused with a modified request body. Follow the exact bank and API-version contract. UK Open Banking idempotency profile
Important rules
Same logical payment → Same key
New logical payment → New key* Generate the key before calling the API.
* Persist it before submitting the payment.
* Reuse it when resolving the same payment attempt.
* Never change amount, currency or payee for the same key.
* Let the server enforce idempotency.
* Keep the key independent from the access token.
* Retain it for the reconciliation period required by the backend.
Why disabling the Pay button is insufficient
The application can still generate duplicates because of:
* Network retries
* App restart
* Multiple devices
* Malicious clients
* Process death
* Parallel requests
UI protection improves user experience, but only the server can guarantee financial idempotency.
---
Retrofit example
interface PaymentApi {
@POST("domestic-payments")
suspend fun createPayment(
@Header("x-idempotency-key")
idempotencyKey: String,
@Body request: PaymentRequest
): PaymentResponse
}Persist before submission
suspend fun preparePayment(
request: PaymentRequest
): PaymentTransaction {
val transaction = PaymentTransaction(
localTransactionId = UUID.randomUUID().toString(),
serverPaymentId = null,
idempotencyKey = UUID.randomUUID().toString(),
amountMinorUnits = request.amountMinorUnits,
currency = request.currency,
status = TransactionStatus.INITIATED
)
paymentDao.insert(transaction.toEntity())
return transaction
}If the application crashes after submission, it can recover the transaction and its original idempotency key.---
Idempotency interview questions
Should we generate a new key when a request times out?
No, not for the same logical payment. Preserve the original key and first check the payment status.
Can the same key be used for a different amount?
No. The request body must remain logically identical.
Who must enforce idempotency?
The server. The mobile application supplies and preserves the key, but client-side logic can be bypassed.
What should the server store?
Typically, the key, request fingerprint, transaction ID, processing status and original result for the configured idempotency period.
---
4. Payment Timeout Handling
4.1 What does a timeout mean?
A timeout means the app did not receive a response within the allowed time.
It does not prove that the server failed to execute the payment.
sequenceDiagram
participant A as Android App
participant S as Payment Server
A->>S: Submit payment
S->>S: Execute payment
S--xA: Response lost or delayed
Note over A: App receives timeoutThe account may already have been debited.
---
Incorrect handling
Payment times out
↓
Create a new payment request
↓
Possible duplicate paymentCorrect handling
flowchart TD
A["Payment request times out"] --> B["Mark local state as Unknown"]
B --> C{"Server payment ID available?"}
C -- Yes --> D["Call payment-status API"]
C -- No --> E["Reconcile using reference or idempotency key"]
D --> F["Receive authoritative status"]
E --> F
F --> G["Update local database and UI"]Open Banking payment resources provide status-retrieval operations, although the exact resource and status values depend on the payment type. Open Banking payment-status example
---
Timeout-handling steps
1. Preserve the transaction record.
2. Preserve the original idempotency key.
3. Set the local status to UNKNOWN.
4. Disable another payment for the same attempt.
5. Query the payment-status endpoint.
6. Retry status checks using controlled backoff.
7. Update the UI from the server response.
8. Escalate to reconciliation or support if status remains unknown.
User message
We could not confirm the payment result.
Please do not make the payment again while we check its status.
Reference: ABC123Retry decision table
| Situation | Correct action |
|---|---|
| Validation failure | Correct the request |
| Insufficient funds | Display business failure |
| Authentication failure | Re-authenticate |
| Payment request timeout | Check payment status |
| Status API timeout | Retry status check with backoff |
| Unknown result | Continue reconciliation |
| Confirmed successful | Display receipt |
| Confirmed failed | Allow a new attempt if appropriate |
| Confirmed payment absent | Follow backend resubmission policy |
---
Timeout-handling interview questions
Why shouldn’t we immediately retry a timed-out payment?
The server may have already processed it and only the response was lost. A new request could create a duplicate payment.
What status should the app display after an uncertain timeout?
UNKNOWN or an equivalent “Checking status” state—not successful or failed.
What if the payment ID was not received?
The backend should support reconciliation using the client transaction reference, interaction ID or idempotency key.
Can WorkManager check an uncertain transaction later?
Yes, if eventual background reconciliation is required. It should check status, not blindly submit another payment.
---
5. Authentication
5.1 Authentication factors
Authentication verifies the user’s identity.
| Factor | Meaning | Example |
|---|---|---|
| Knowledge | Something the user knows | Password, MPIN |
| Possession | Something the user has | Registered device |
| Inherence | Something the user is | Fingerprint, face |
Using two different factor categories provides multi-factor authentication.
---
5.2 Password authentication
A password is generally used for initial login or account recovery.
Secure flow
flowchart TD
A["User enters password"] --> B["Send through HTTPS"]
B --> C["Server verifies password"]
C --> D["Server issues tokens"]
D --> E["App clears password"]Rules
* Never store the password locally.
* Never log it.
* Send it only through HTTPS.
* Clear it after authentication.
* Apply retry limits on the server.
* Use a secure recovery mechanism.
* Do not include passwords in analytics or crash reports.
---
5.3 MPIN authentication
An MPIN is a short numerical secret.
Rules
* Do not store it in Room or DataStore.
* Do not store a reversible encrypted copy.
* Validate through a trusted system.
* Apply attempt limits and lockout.
* Clear it immediately after use.
* Never log it.
* Protect against screenshots and accessibility leakage according to the threat model.
A four-digit MPIN has only 10,000 combinations, so a locally extracted hash can be brute-forced quickly.
---
5.4 OTP authentication
An OTP is:
* Short-lived
* Single-use
* Bound to a session or operation
* Validated by the server
sequenceDiagram
participant U as User
participant A as Android App
participant S as Server
S-->>U: Deliver OTP
U->>A: Enter OTP
A->>S: Submit OTP securely
S->>S: Check value, expiry and purpose
S-->>A: Return result
A->>A: Clear OTPRules
* Never persist OTP in Room, DataStore or SavedStateHandle.
* Limit verification attempts.
* Expire it quickly.
* Allow only one successful use.
* Bind payment OTP to the intended action.
* Clear it after submission.
* Do not log it.
---
5.5 Biometric authentication
Use Android BiometricPrompt.
The mobile app does not receive or store the user’s raw biometric information.
flowchart TD
A["App requests authentication"] --> B["Android BiometricPrompt"]
B --> C{"Authentication successful?"}
C -- No --> D["Deny operation"]
C -- Yes --> E["Unlock Keystore-protected key"]
E --> F["Continue secured flow"]
F --> G["Server authorizes operation"]Biometric use cases
* Unlock local session
* Unlock a Keystore key
* Access sensitive data
* Confirm user presence
* Participate in payment step-up
> Biometric success confirms local user presence. The server must still authorize the payment.
---
5.6 Device binding
Device binding associates an account or session with a registered device.
Recommended flow
sequenceDiagram
participant A as Android App
participant K as Android Keystore
participant S as Bank Server
A->>K: Generate key pair
K-->>A: Return public key
A->>S: Register public key
S->>A: Send challenge
A->>K: Sign challenge
K-->>A: Return signature
A->>S: Send signed challenge
S->>S: Verify registered public keyImportant points
* Generate the private key inside Android Keystore.
* Send only the public key to the server.
* Keep the private key non-exportable where supported.
* Use server challenges to prove device-key possession.
* Support device deregistration and replacement.
* Treat device binding as one authentication signal.
Do not use only:
* Device model
* IMEI
* Android ID
* Plain random value in preferences
These values are not sufficient cryptographic proof of device possession.
---
5.7 Step-up authentication
Step-up authentication means asking for fresh or additional verification before a sensitive operation.
Example operations
* Adding a beneficiary
* Making a high-value payment
* Changing the registered phone number
* Registering a new device
* Changing a password
* Exporting sensitive information
* Performing a transaction from a risky environment
flowchart TD
A["Authenticated user"] --> B["Requests sensitive operation"]
B --> C["Evaluate transaction risk"]
C --> D{"Step-up required?"}
D -- No --> E["Server authorizes operation"]
D -- Yes --> F["Biometric, OTP or fresh credential"]
F --> G{"Verification successful?"}
G -- Yes --> E
G -- No --> H["Reject operation"]Step-up authentication should be:
* Based on risk and business rules
* Bound to the sensitive action
* Short-lived
* Validated by the server
* Invalidated after completion or expiry
---
Authentication comparison
| Method | Main purpose | Stored locally? | Key limitation |
|---|---|---|---|
| Password | Initial/recovery authentication | No | Can be stolen or reused |
| MPIN | Quick knowledge-factor authentication | No | Small combination space |
| OTP | Single-use verification | No | Vulnerable to interception/social engineering |
| Biometric | Local user-presence verification | No raw biometric | Device-local verification |
| Device binding | Prove possession of registered device key | Private key in Keystore | Does not prove user identity alone |
| Step-up | Protect high-risk operations | No reusable secret | Must be bound to the operation |
---
6. Complete Secure Payment Flow
flowchart TD
A["User logs in"] --> B["Server issues token pair"]
B --> C["User prepares payment"]
C --> D["Generate and persist idempotency key"]
D --> E["Perform step-up authentication"]
E --> F["Submit payment"]
F --> G{"Response received?"}
G -- Yes --> H["Store server status"]
G -- Timeout --> I["Mark transaction Unknown"]
I --> J["Check payment status"]
J --> H
H --> K{"Final state?"}
K -- Successful --> L["Show receipt"]
K -- Failed --> M["Show failure"]
K -- Pending --> N["Continue status checks"]
K -- Reversed --> O["Show reversal"]---
7. Common Interview Mistakes
* Saying access and refresh tokens are the same
* Storing tokens in plain SharedPreferences
* Starting multiple token-refresh calls
* Retrying infinitely after 401
* Treating HTTP 200 as payment success
* Treating timeout as payment failure
* Generating a new idempotency key during the same attempt
* Preventing duplicates only by disabling the button
* Storing passwords, MPINs or OTPs
* Treating biometric success as server authorization
* Implementing device binding using only Android ID
* Missing step-up authentication for sensitive payments
* Keeping transaction state only in ViewModel
* Using Double for financial amounts
---
8. Quick Interview Answers
Explain secure payment submission in one answer.
> Before submitting a payment, I create and persist a unique idempotency key. For sensitive payments, I perform step-up authentication. The server executes the payment idempotently and returns a payment ID and business status. If the request times out, I mark the transaction as unknown and query the status endpoint using the existing reference instead of creating another payment.
Explain simultaneous 401 handling.
> I use an OkHttp Authenticator with synchronization such as a Mutex. Only the first failed request refreshes the token. Other requests wait and reuse the updated token. Each request is retried only once, and permanent refresh failure ends the session.
Explain the difference between pending and unknown.
> Pending is an authoritative server status indicating that the transaction exists but is awaiting completion. Unknown is a client-side uncertainty where the app could not determine the result, usually because of a timeout or lost response.
Explain device binding.
> The app generates a key pair inside Android Keystore and registers the public key with the server. Later, it signs a server challenge using the private key. Successful signature verification proves possession of the registered device key.
Explain step-up authentication.
> Step-up authentication asks for additional or fresh verification before a high-risk action, such as adding a beneficiary or making a high-value transfer. It can use biometric authentication, OTP or a fresh credential, but the server must authorize the operation.
---
Final Revision Table
| Topic | Interview keyword |
|---|---|
| Access token | Short-lived API credential |
| Refresh token | Longer-lived token renewal credential |
| Expiry | Proactive check plus server authority |
| Token rotation | Old refresh token invalidated |
Simultaneous 401 | Single-flight refresh using Mutex |
| Logout | Revoke server session and clear local state |
| Initiated | Payment attempt created |
| Processing | Currently being processed |
| Pending | Known but awaiting final status |
| Successful | Server-confirmed completion |
| Failed | Server-confirmed rejection |
| Reversed | Previous financial effect undone |
| Unknown | Final result cannot be determined |
| Idempotency | Same payment, same key, one effect |
| Timeout | Check status before resubmission |
| Password | Never store |
| MPIN | Never persist; limit attempts |
| OTP | Short-lived, single-use and action-bound |
| Biometric | Local user-presence verification |
| Device binding | Keystore key plus server challenge |
| Step-up | Extra verification for sensitive operations |