How mobile and desktop apps managed sessions without tokens in early days
Before the widespread adoption of token-based authentication, sessions in mobile and desktop applications were managed using several other methods. These methods included traditional session management techniques and various forms of secure storage and communication. Here’s an overview of how sessions were typically managed:
1. Session Identifiers with Secure Storage
Similar to web applications that used cookies, mobile and desktop applications often used session identifiers (session IDs) stored securely on the client-side. Here’s how it worked:
- User Authentication: The user logs in and the server creates a session.
- Session ID Issuance: The server generates a session ID and sends it to the client.
- Session ID Storage: The client stores the session ID in a secure manner (e.g., in the app’s secure storage or keychain).
- Session ID Usage: The client includes the session ID in the headers of subsequent requests to authenticate the user.
Challenges:
- Security: If session IDs were intercepted, they could be used to hijack sessions.
- Scalability: Session data needed to be stored and managed on the server, which could lead to scalability issues.
2. Persistent Cookies with Secure Storage
For mobile applications, some platforms allowed the use of persistent cookies, which were stored securely and managed similarly to web applications. Desktop applications could also leverage similar storage mechanisms.
Example:
- iOS and Android: Secure storage mechanisms like Keychain on iOS and Encrypted Shared Preferences on Android were used to store session cookies securely.
- Desktop Applications: Secure storage solutions like the Windows Credential Store or macOS Keychain were used to store session information.
3. OAuth and Third-Party Authentication
Even before JWTs became popular, OAuth 1.0a and OAuth 2.0 were used for authentication, particularly with third-party services.
- Authorization Code Flow: The user authenticates via a third-party provider, which issues an authorization code.
- Access Token Retrieval: The client exchanges the authorization code for an access token, which is used to authenticate subsequent requests.
- Token Storage: The access token is stored securely on the client-side.
Example:
- OAuth 1.0a: This was used for secure delegated access and required more complex signing of requests.
- OAuth 2.0: Simplified the process and became widely adopted for mobile and desktop applications.
4. Custom Authentication Mechanisms
Some applications used custom authentication mechanisms tailored to their specific needs. This might include:
- API Keys: Simple static tokens that were included in request headers.
- Device Registration: Unique device identifiers were used in combination with user credentials to authenticate sessions.
Example:
- Custom Tokens: Applications might generate their own tokens and implement custom validation logic on the server.
Examples of Secure Storage and Communication Methods
-
Secure Storage Libraries: Libraries that provided secure storage solutions for sensitive data.
- iOS: Keychain Services.
- Android: Encrypted Shared Preferences.
- React Native: Libraries like
react-native-keychain
.
-
Encrypted Communication: Use of HTTPS for all communication to ensure data in transit was encrypted.
- Ensured that session IDs or tokens were not intercepted.
Example of Managing Sessions in Mobile Apps Before JWTs
iOS (Objective-C/Swift) Example Using Keychain:
// Save session token to Keychain
func saveSessionToken(token: String) {
let keychainQuery: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrAccount as String: "sessionToken",
kSecValueData as String: token.data(using: .utf8)!
]
SecItemAdd(keychainQuery as CFDictionary, nil)
}
// Retrieve session token from Keychain
func getSessionToken() -> String? {
let keychainQuery: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrAccount as String: "sessionToken",
kSecReturnData as String: kCFBooleanTrue!,
kSecMatchLimit as String: kSecMatchLimitOne
]
var dataTypeRef: AnyObject?
let status: OSStatus = SecItemCopyMatching(keychainQuery as CFDictionary, &dataTypeRef)
if status == noErr {
if let retrievedData = dataTypeRef as? Data {
return String(data: retrievedData, encoding: .utf8)
}
}
return nil
}
Android (Java/Kotlin) Example Using Encrypted Shared Preferences:
// Save session token to Encrypted Shared Preferences
SharedPreferences sharedPreferences = EncryptedSharedPreferences.create(
"my_prefs",
MasterKeys.getOrCreate(MasterKeys.AES256_GCM_SPEC),
context,
EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
);
SharedPreferences.Editor editor = sharedPreferences.edit();
editor.putString("sessionToken", "your_session_token");
editor.apply();
// Retrieve session token from Encrypted Shared Preferences
String sessionToken = sharedPreferences.getString("sessionToken", null);