Managing API Keys, Tokens, and Authentication Credentials in iOS
Course: iOS App Development
Topic: Managing API Keys, Tokens, and Authentication Credentials
Course Link: iOS App Development Training
Demo Link: Register for Course Demo
Website: JustAcademy
1. Introduction to API Keys, Tokens, and Authentication Credentials
Modern iOS applications communicate with remote servers to retrieve information, submit user requests, process payments, synchronize data, and provide personalized services. These communication processes often require authentication credentials to verify the identity of an application, user, or service.
API keys, access tokens, refresh tokens, passwords, and session credentials are commonly used in application authentication. Developers must manage these values carefully because exposing credentials can allow unauthorized individuals to access protected services or user information.
iOS provides security technologies such as Keychain Services, Secure Enclave, App Transport Security, and system authentication frameworks to help developers protect sensitive information.
Learning Objectives
- Understand API keys, access tokens, refresh tokens, and authentication credentials.
- Learn the difference between authentication and authorization.
- Understand common authentication mechanisms used in iOS applications.
- Store sensitive credentials securely using Keychain Services.
- Manage token creation, expiration, renewal, and deletion.
- Integrate authentication credentials with URLSession API requests.
- Implement secure login and logout workflows.
- Understand OAuth 2.0 and JSON Web Tokens.
- Prevent credential exposure in source code, logs, and application storage.
- Apply security best practices when communicating with backend services.
2. Understanding API Keys
An API key is a unique value used by an API provider to identify an application, project, or integration making requests to a service.
For example, an iOS weather application may communicate with a weather service using an API key. The service can use that key to identify the associated project, enforce quotas, and apply configured restrictions.
Example API Key
API_KEY = "example_public_key_123456"
The above value is only a demonstration. Real API keys must never be copied into publicly shared examples or repositories.
Common Uses of API Keys
- Identifying an application when calling a third-party API.
- Tracking API usage and request limits.
- Applying service-level restrictions.
- Associating requests with a registered project.
- Controlling access to certain API resources.
Important Characteristics of API Keys
- API keys are generally issued by an API provider or backend administrator.
- They may identify an application or project rather than an individual user.
- Some API keys are designed for public client-side use with restrictions.
- Secret server-side API keys must never be embedded in an iOS application.
- API keys should be restricted to the required services, APIs, and environments.
3. Understanding Authentication Tokens
An authentication token is a credential used to represent an authenticated user, session, or authorization grant. After a user signs in, the backend may issue a token that the application sends with later API requests.
Tokens reduce the need to send the user's password repeatedly. They can also contain or reference information about permissions, expiration, and the identity associated with a session.
Example Access Token
Authorization: Bearer example_access_token_123
The example token is fictional and must not be used for real authentication.
Common Types of Tokens
| Token Type |
Description |
Purpose |
| Access Token |
Credential used to access protected API resources. |
Authorizes API requests. |
| Refresh Token |
Credential used to obtain a new access token. |
Maintains a session without repeatedly asking the user to sign in. |
| ID Token |
Token containing identity claims in OpenID Connect. |
Provides information about the authenticated user. |
| Session Token |
Credential representing an authenticated session. |
Allows the server to associate requests with a session. |
| One-Time Token |
Short-lived credential intended for a limited operation. |
Supports actions such as verification or password reset. |
4. Difference Between API Keys, Passwords, and Tokens
| Feature |
API Key |
Password |
Access Token |
| Purpose |
Identifies an application or project. |
Authenticates a user. |
Grants access to protected resources. |
| Issued By |
API provider or administrator. |
Created by the user or organization. |
Authentication or authorization server. |
| Typical Usage |
API identification and quota management. |
Login authentication. |
Authenticated API requests. |
| Expiration |
May be long-lived or rotated. |
Usually changed or reset by the user. |
Often short-lived. |
| Storage |
Depends on whether it is public or secret. |
Should not be stored as plain text. |
Secure storage such as Keychain. |
5. Understanding Authentication and Authorization
Authentication and authorization are related security concepts, but they have different responsibilities.
Authentication
Authentication verifies who a user or application claims to be. For example, a user may authenticate by entering an email address and password or by using a supported identity provider.
Authorization
Authorization determines what an authenticated user or application is permitted to access. For example, a normal user may view their own profile, while an administrator may manage multiple user accounts.
| Authentication |
Authorization |
| Verifies identity. |
Checks access permissions. |
| Answers: Who are you? |
Answers: What are you allowed to do? |
| Often happens during login. |
Applied when accessing protected resources. |
| May use passwords, biometrics, or identity providers. |
May use roles, scopes, policies, or permissions. |
6. Understanding the iOS Keychain
Keychain Services is an Apple security technology used to store small pieces of sensitive information, such as passwords, cryptographic keys, certificates, and authentication tokens.
Unlike ordinary application preferences, Keychain is designed specifically for sensitive credential storage. It provides access controls and data protection features that help protect stored secrets.
Why Use Keychain?
- Stores sensitive credentials more securely than ordinary preferences.
- Supports access-control policies.
- Provides configurable accessibility levels.
- Can protect data using device security mechanisms.
- Allows applications to retrieve credentials when required.
- Supports secure storage of authentication tokens and passwords.
Keychain vs UserDefaults
| Feature |
Keychain |
UserDefaults |
| Primary Purpose |
Secure credential storage. |
Application preferences and settings. |
| Suitable for Passwords |
Yes. |
No. |
| Suitable for Access Tokens |
Yes. |
No. |
| Suitable for Theme Preference |
Usually unnecessary. |
Yes. |
| Access Protection |
Supports Keychain access controls and data protection. |
Not designed as a secure secret store. |
| Example |
Refresh token. |
Dark mode preference. |
7. Understanding Keychain Services
Keychain Services provides a set of APIs for adding, reading, updating, and deleting protected items.
Important Security framework functions include:
| Function |
Purpose |
| SecItemAdd |
Adds a new item to the Keychain. |
| SecItemCopyMatching |
Searches for and retrieves a matching item. |
| SecItemUpdate |
Updates an existing item. |
| SecItemDelete |
Deletes a matching item. |
8. Importing the Security Framework
To use Keychain Services in Swift, import Apple's Security framework.
import Security
This framework provides the constants, data types, and functions needed to manage Keychain items.
9. Creating a Keychain Helper
A Keychain helper centralizes credential storage operations. This avoids repeating low-level Security framework code throughout the application.
Example Keychain Helper
import Foundation
import Security
enum KeychainError: Error {
case duplicateItem
case itemNotFound
case unexpectedStatus(OSStatus)
}
final class KeychainManager {
static let shared = KeychainManager()
private init() {}
func save(
_ value: String,
forKey key: String
) throws {
let data = Data(value.utf8)
let query: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrAccount as String: key,
kSecValueData as String: data,
kSecAttrAccessible as String:
kSecAttrAccessibleWhenUnlockedThisDeviceOnly
]
let status = SecItemAdd(
query as CFDictionary,
nil
)
guard status == errSecSuccess else {
if status == errSecDuplicateItem {
throw KeychainError.duplicateItem
}
throw KeychainError.unexpectedStatus(status)
}
}
}
This helper stores a string as a generic password item. The accessibility setting means the item is available when the device is unlocked and is not migrated to another device through backup restoration.
10. Retrieving Credentials from Keychain
After storing a token, the application can retrieve it when making authenticated API requests.
func read(forKey key: String) throws -> String {
let query: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrAccount as String: key,
kSecReturnData as String: true,
kSecMatchLimit as String: kSecMatchLimitOne
]
var result: CFTypeRef?
let status = SecItemCopyMatching(
query as CFDictionary,
&result
)
guard status != errSecItemNotFound else {
throw KeychainError.itemNotFound
}
guard status == errSecSuccess,
let data = result as? Data,
let value = String(data: data, encoding: .utf8) else {
throw KeychainError.unexpectedStatus(status)
}
return value
}
The application should handle missing items and unexpected Keychain errors without crashing. It should not print retrieved credentials to application logs.
11. Updating Credentials in Keychain
When a token is renewed or replaced, the application may need to update the existing Keychain item.
func update(
_ value: String,
forKey key: String
) throws {
let query: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrAccount as String: key
]
let attributes: [String: Any] = [
kSecValueData as String: Data(value.utf8)
]
let status = SecItemUpdate(
query as CFDictionary,
attributes as CFDictionary
)
guard status == errSecSuccess else {
throw KeychainError.unexpectedStatus(status)
}
}
Updating an existing item is different from adding a new one. If an item does not exist, the application should decide whether to create it or report an error.
12. Deleting Credentials from Keychain
Credentials should be deleted when the user logs out, a session is revoked, or the application no longer needs the stored value.
func delete(forKey key: String) throws {
let query: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrAccount as String: key
]
let status = SecItemDelete(
query as CFDictionary
)
guard status == errSecSuccess ||
status == errSecItemNotFound else {
throw KeychainError.unexpectedStatus(status)
}
}
Allowing deletion to succeed when an item is already absent makes logout and cleanup operations easier to repeat safely.
13. Improving Keychain Helper with Save-or-Update Logic
A practical credential manager should support both inserting a new item and replacing an existing item.
func saveOrUpdate(
_ value: String,
forKey key: String
) throws {
let data = Data(value.utf8)
let query: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrAccount as String: key
]
let attributes: [String: Any] = [
kSecValueData as String: data
]
let updateStatus = SecItemUpdate(
query as CFDictionary,
attributes as CFDictionary
)
if updateStatus == errSecItemNotFound {
var addQuery = query
addQuery[kSecValueData as String] = data
addQuery[kSecAttrAccessible as String] =
kSecAttrAccessibleWhenUnlockedThisDeviceOnly
let addStatus = SecItemAdd(
addQuery as CFDictionary,
nil
)
guard addStatus == errSecSuccess else {
throw KeychainError.unexpectedStatus(addStatus)
}
} else if updateStatus != errSecSuccess {
throw KeychainError.unexpectedStatus(updateStatus)
}
}
14. Understanding Access Token Expiration
Access tokens are often short-lived. A backend or identity provider may issue a token with an expiration time. After expiration, the token may no longer authorize protected API requests.
Applications should not assume that a token remains valid indefinitely. They should follow the authentication server's documented expiration and renewal process.
Example Token Response
{
"access_token": "example_access_token",
"refresh_token": "example_refresh_token",
"token_type": "Bearer",
"expires_in": 3600
}
The expires_in field commonly represents the access token's lifetime in seconds. The actual response format depends on the authentication provider.
15. Understanding Refresh Tokens
A refresh token is a credential used to obtain a new access token without asking the user to enter their password again. It is generally more sensitive than an access token because it can be used to continue or renew a session.
Typical Token Renewal Flow
- The user signs in successfully.
- The authentication server issues an access token and, where supported, a refresh token.
- The application stores the required credentials securely.
- The application uses the access token for protected API requests.
- The access token expires or becomes invalid.
- The application sends the refresh token to the authorized token endpoint.
- The server validates the refresh token.
- The server returns a new access token and may rotate the refresh token.
- The application securely updates its stored credentials.
Refresh tokens must be protected carefully. Follow the provider's rotation, reuse-detection, revocation, and expiration rules.
16. Implementing a Token Model in Swift
A token model can represent the authentication data returned by a backend.
struct AuthTokenResponse: Decodable {
let accessToken: String
let refreshToken: String?
let tokenType: String
let expiresIn: Int
enum CodingKeys: String, CodingKey {
case accessToken = "access_token"
case refreshToken = "refresh_token"
case tokenType = "token_type"
case expiresIn = "expires_in"
}
}
Using Decodable allows Swift to convert a JSON authentication response into a strongly typed model.
17. Sending Authenticated API Requests Using URLSession
After retrieving an access token from Keychain, the application can attach it to an API request using the HTTP Authorization header.
func fetchUserProfile(
accessToken: String
) async throws -> Data {
guard let url = URL(
string: "https://api.example.com/profile"
) else {
throw URLError(.badURL)
}
var request = URLRequest(url: url)
request.httpMethod = "GET"
request.setValue(
"Bearer \(accessToken)",
forHTTPHeaderField: "Authorization"
)
let (data, response) =
try await URLSession.shared.data(for: request)
guard let httpResponse =
response as? HTTPURLResponse else {
throw URLError(.badServerResponse)
}
guard (200...299).contains(httpResponse.statusCode) else {
throw URLError(.badServerResponse)
}
return data
}
Use HTTPS for authenticated API communication. The backend must validate the token and enforce authorization for the requested resource.
18. Handling Unauthorized API Responses
An API may return an HTTP 401 Unauthorized response when a token is missing, expired, invalid, or no longer accepted. The application should distinguish authentication failures from other server errors.
| HTTP Status |
Meaning |
Typical Handling |
| 200 |
Request succeeded. |
Process the response. |
| 400 |
Invalid request. |
Review request parameters and payload. |
| 401 |
Authentication is missing or invalid. |
Attempt the supported token renewal flow or request sign-in. |
| 403 |
Access is forbidden. |
Show an appropriate access-denied message. |
| 429 |
Too many requests. |
Respect rate limits and retry guidance. |
| 500 |
Server error. |
Handle the service failure safely. |
A 403 response does not necessarily mean that the user must sign in again. It may indicate that the authenticated user does not have permission to access the requested resource.
19. Implementing a Basic Authentication Manager
An authentication manager can coordinate login state, token storage, and logout operations.
import Foundation
final class AuthManager {
static let shared = AuthManager()
private let accessTokenKey = "access_token"
private let refreshTokenKey = "refresh_token"
private init() {}
func saveTokens(
accessToken: String,
refreshToken: String?
) throws {
try KeychainManager.shared.saveOrUpdate(
accessToken,
forKey: accessTokenKey
)
if let refreshToken = refreshToken {
try KeychainManager.shared.saveOrUpdate(
refreshToken,
forKey: refreshTokenKey
)
}
}
func getAccessToken() throws -> String {
return try KeychainManager.shared.read(
forKey: accessTokenKey
)
}
func logout() throws {
try KeychainManager.shared.delete(
forKey: accessTokenKey
)
try KeychainManager.shared.delete(
forKey: refreshTokenKey
)
}
}
This example demonstrates the separation of authentication responsibilities from individual screens. A production implementation should also handle partial storage failures, server-side session revocation, and concurrent authentication operations.
20. Implementing a Login Workflow
A login workflow sends the user's credentials to an authentication service and receives an authentication response.
Typical Login Steps
- The user enters login information.
- The application validates required fields.
- The application sends credentials through a secure HTTPS request.
- The server verifies the credentials.
- The server returns an access token and optional refresh token.
- The application stores the tokens in Keychain.
- The application updates the authentication state.
- The user can access authorized application features.
Example Login Request Model
struct LoginRequest: Encodable {
let email: String
let password: String
}
Example Login Function
func login(
email: String,
password: String
) async throws {
guard let url = URL(
string: "https://api.example.com/login"
) else {
throw URLError(.badURL)
}
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue(
"application/json",
forHTTPHeaderField: "Content-Type"
)
request.httpBody = try JSONEncoder().encode(
LoginRequest(
email: email,
password: password
)
)
let (data, _) =
try await URLSession.shared.data(for: request)
let tokenResponse =
try JSONDecoder().decode(
AuthTokenResponse.self,
from: data
)
try AuthManager.shared.saveTokens(
accessToken: tokenResponse.accessToken,
refreshToken: tokenResponse.refreshToken
)
}
This is an illustrative example. A production login function must validate the HTTP response status, handle server error payloads, apply request timeouts, and avoid retaining the password longer than necessary.
21. Implementing Logout and Credential Cleanup
Logout should clear the application's local authentication state and, where supported, revoke the server-side session or refresh token.
Logout Workflow
- The user selects the logout option.
- The application sends a session-revocation request when supported.
- The server invalidates the relevant session or refresh token.
- The application deletes stored authentication credentials.
- The application clears user-specific cached information as appropriate.
- The application navigates to the login screen.
func performLogout() {
do {
try AuthManager.shared.logout()
print("Local credentials cleared")
} catch {
print("Credential cleanup failed: \(error)")
}
}
Do not rely only on deleting local tokens when the backend supports session revocation. Local deletion does not automatically invalidate a token already copied elsewhere.
22. Understanding OAuth 2.0
OAuth 2.0 is an authorization framework that allows an application to obtain limited access to protected resources without directly handling the user's account password for every service request.
For native iOS applications, the Authorization Code flow with Proof Key for Code Exchange (PKCE) is commonly recommended for public clients.
Basic OAuth 2.0 Flow
- The application starts an authorization request.
- The user is directed to the authorization server.
- The user authenticates and grants the requested permissions.
- The authorization server returns an authorization code.
- The application exchanges the code and PKCE verifier for tokens.
- The application stores the necessary tokens securely.
- The access token is used for authorized API requests.
Important OAuth 2.0 Terms
| Term |
Description |
| Client |
The application requesting access. |
| Authorization Server |
The server responsible for authentication and authorization grants. |
| Resource Server |
The server hosting protected API resources. |
| Authorization Code |
A temporary code exchanged for tokens. |
| PKCE |
A mechanism that protects authorization-code exchanges for public clients. |
| Scope |
Defines the requested permissions. |
| Redirect URI |
The destination to which the authorization server returns the response. |
For native applications, use an external user agent such as the system browser or an appropriate authentication session instead of embedding the login page in an ordinary web view. Follow the identity provider's documented redirect and PKCE requirements.
23. Understanding JSON Web Tokens (JWT)
A JSON Web Token is a compact token format that can carry claims between parties. It is commonly used in authentication and authorization systems.
A signed JWT typically contains three parts separated by periods:
header.payload.signature
| Part |
Description |
| Header |
Contains token metadata, such as the signing algorithm and token type. |
| Payload |
Contains claims such as subject, issuer, audience, and expiration. |
| Signature |
Allows the recipient to verify token integrity and authenticity. |
JWT payloads are generally encoded, not encrypted. Sensitive information should not be placed in a token unless an appropriate encryption design is used. The server must validate signatures and relevant claims such as issuer, audience, and expiration.
24. Managing API Keys in iOS Applications
API key management depends on the type of key and the security model of the API provider.
Public or Client-Side API Keys
Some services provide keys intended to be used by client applications. These keys should be restricted to the required APIs and protected with available platform or service restrictions. Developers should understand that a key embedded in an application binary can potentially be extracted.
Secret Server-Side API Keys
Secret keys must remain on a trusted backend. The iOS application should call the backend, and the backend should communicate with the third-party service using the secret key.
Recommended Architecture
iOS Application
|
| HTTPS Request
v
Application Backend
|
| Server-side Secret API Key
v
Third-Party API Provider
This design prevents a confidential service credential from being directly embedded in the distributed iOS application.
25. Secure Configuration and Environment Management
Applications often use different API endpoints and configuration values for development, testing, staging, and production.
| Environment |
Purpose |
| Development |
Used by developers during implementation. |
| Testing |
Used for automated and manual testing. |
| Staging |
Used to verify the application in a production-like environment. |
| Production |
Used by the released application and real users. |
Use build configurations and environment-specific settings to select the correct API base URL. Do not assume that putting a secret in an Xcode configuration file, property list, or compiled constant makes it confidential. Any value bundled into a client application may be extracted.
26. App Transport Security and Secure API Communication
App Transport Security (ATS) helps enforce secure network communication for applications using Apple's standard networking APIs.
HTTPS protects information while it travels between the application and the server. It helps prevent network attackers from reading or modifying data in transit.
Recommended Practices
- Use HTTPS for all authentication and API requests.
- Use valid TLS certificates.
- Avoid sending credentials in URL query strings.
- Keep ATS enabled whenever possible.
- Avoid broad ATS exceptions.
- Validate server responses and handle failures safely.
- Never disable certificate validation to work around development problems.
27. Biometric Authentication and Keychain Access Control
Face ID and Touch ID can provide an additional user-verification step before allowing access to protected application information. LocalAuthentication supports biometric and device-passcode authentication.
Keychain access-control policies can also require user presence or biometric authentication before a protected item is accessed.
Example: Checking Biometric Availability
import LocalAuthentication
func authenticateUser(
completion: @escaping (Bool) -> Void
) {
let context = LAContext()
var error: NSError?
guard context.canEvaluatePolicy(
.deviceOwnerAuthentication,
error: &error
) else {
completion(false)
return
}
context.evaluatePolicy(
.deviceOwnerAuthentication,
localizedReason: "Access your secure account"
) { success, authenticationError in
DispatchQueue.main.async {
completion(success)
}
}
}
This example uses device-owner authentication, which can support available biometrics or the device passcode. Biometric authentication should not replace server-side authentication or authorization.
28. Common Credential Management Mistakes
| Mistake |
Risk |
Recommended Solution |
| Hardcoding secret API keys |
Credentials can be extracted from the application. |
Keep confidential keys on the backend. |
| Storing tokens in UserDefaults |
Uses a storage mechanism not designed for secrets. |
Use Keychain Services. |
| Printing tokens in logs |
Credentials may appear in diagnostic output. |
Remove sensitive values from logs. |
| Using HTTP for login |
Credentials may be exposed in transit. |
Use HTTPS and secure transport settings. |
| Never expiring tokens |
Stolen credentials may remain usable for too long. |
Use suitable expiration and revocation policies. |
| Ignoring refresh-token rotation |
May allow reuse of compromised credentials. |
Follow server-side rotation and reuse-detection rules. |
| Keeping tokens after logout |
Another user may access the previous session. |
Clear local credentials and revoke sessions when supported. |
| Trusting JWT payload without validation |
Untrusted claims may be accepted. |
Validate tokens on the server. |
| Sharing credentials between users |
May expose one user's account to another. |
Associate credentials with the correct account and session. |
29. Credential Rotation and Revocation
Credential rotation means replacing an existing key or token with a new one. Revocation means invalidating a credential so that it can no longer be used.
When Rotation Is Needed
- A secret key may have been exposed.
- A token is approaching expiration.
- An employee or service account no longer needs access.
- A security policy requires periodic replacement.
- A refresh token has been rotated by the authentication provider.
Credential Revocation Workflow
- Identify the credential that needs to be revoked.
- Notify the trusted authentication or API provider.
- Revoke the credential using the provider's supported process.
- Remove the old value from local secure storage.
- Issue or obtain a replacement credential if required.
- Verify that the old credential can no longer access protected resources.
30. Practical Project: Secure Login and Token Management System
Project Objective: Build an iOS application that authenticates a user, securely stores access credentials, makes authenticated API requests, and clears credentials during logout.
Project Features
- Login screen with email and password fields.
- Secure login request using URLSession.
- Token response decoding.
- Keychain-based token storage.
- Authenticated profile API request.
- Unauthorized response handling.
- Refresh-token workflow according to backend rules.
- Logout and local credential cleanup.
- Optional biometric protection for access to sensitive screens.
Step 1: Create a Token Storage Manager
Implement a reusable Keychain helper with save, read, update, and delete methods.
let accessTokenKey = "access_token"
let refreshTokenKey = "refresh_token"
try KeychainManager.shared.saveOrUpdate(
"sample_access_token",
forKey: accessTokenKey
)
Step 2: Create an Authentication Response Model
struct LoginResponse: Decodable {
let accessToken: String
let refreshToken: String?
let expiresIn: Int
enum CodingKeys: String, CodingKey {
case accessToken = "access_token"
case refreshToken = "refresh_token"
case expiresIn = "expires_in"
}
}
Step 3: Send the Login Request
func authenticate(
email: String,
password: String
) async throws -> LoginResponse {
let url = URL(
string: "https://api.example.com/login"
)!
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue(
"application/json",
forHTTPHeaderField: "Content-Type"
)
request.httpBody = try JSONEncoder().encode(
LoginRequest(
email: email,
password: password
)
)
let (data, response) =
try await URLSession.shared.data(for: request)
guard let httpResponse =
response as? HTTPURLResponse,
(200...299).contains(httpResponse.statusCode) else {
throw URLError(.userAuthenticationRequired)
}
return try JSONDecoder().decode(
LoginResponse.self,
from: data
)
}
Step 4: Save Authentication Tokens
func storeAuthentication(
response: LoginResponse
) throws {
try KeychainManager.shared.saveOrUpdate(
response.accessToken,
forKey: "access_token"
)
if let refreshToken = response.refreshToken {
try KeychainManager.shared.saveOrUpdate(
refreshToken,
forKey: "refresh_token"
)
}
}
Step 5: Access a Protected API
func loadProfile() async throws -> Data {
let token = try KeychainManager.shared.read(
forKey: "access_token"
)
return try await fetchUserProfile(
accessToken: token
)
}
Step 6: Implement Logout
func logout() {
do {
try AuthManager.shared.logout()
print("User logged out successfully")
} catch {
print("Unable to clear credentials")
}
}
Expected Result
The user can sign in, receive authentication tokens, store them securely in Keychain, and use the access token to retrieve protected information. When the user logs out, local credentials are removed and the backend session is revoked when the service supports revocation.
31. Testing and Debugging Credential Management
Credential management must be tested carefully because authentication failures can affect the entire application.
Testing Checklist
- Test successful login with valid credentials.
- Test login with incorrect credentials.
- Verify that tokens are stored in Keychain.
- Verify that tokens can be retrieved correctly.
- Test updating an existing Keychain item.
- Test behavior when a Keychain item does not exist.
- Test an expired access token.
- Test refresh-token renewal and rotation.
- Test a revoked refresh token.
- Test API requests without an access token.
- Test logout and credential deletion.
- Verify that credentials do not appear in logs or crash reports.
- Test application behavior after device restart.
- Test behavior when biometric authentication is unavailable.
32. Best Practices for Managing API Keys and Credentials
- Never embed confidential server-side API keys in the iOS application.
- Use a trusted backend for operations requiring secret credentials.
- Store access and refresh tokens in Keychain.
- Choose Keychain accessibility settings according to the application's requirements.
- Use HTTPS for all authentication requests.
- Never store passwords in plain text.
- Do not print tokens, passwords, or API keys in logs.
- Use short-lived access tokens when supported by the authentication design.
- Protect refresh tokens and follow rotation policies.
- Clear credentials during logout.
- Revoke server-side sessions when supported.
- Validate authorization on the backend for every protected resource.
- Use OAuth 2.0 Authorization Code with PKCE for suitable native-app authentication flows.
- Do not treat a decoded JWT payload as proof of authenticity.
- Use biometric authentication as an additional local protection layer when appropriate.
- Restrict public API keys to the required services and application environments.
- Keep development, staging, and production configurations separate.
- Review credential exposure risks before releasing the application.
33. Practice Exercises
- Create a Swift Keychain helper to store a sample access token.
- Implement a function to retrieve a stored token.
- Add update and delete methods to the Keychain helper.
- Compare Keychain and UserDefaults through a small demonstration project.
- Create a login API request using URLSession.
- Decode an authentication response using Codable.
- Attach a Bearer token to a protected API request.
- Handle HTTP 401 and 403 responses separately.
- Implement a refresh-token workflow using a test backend.
- Build a logout function that clears local credentials.
- Explore OAuth 2.0 Authorization Code with PKCE.
- Implement biometric authentication before opening a protected screen.
34. Interview Questions and Answers
Q1. What is an API key?
Answer: An API key is a credential used by an API provider to identify an application or project and apply access restrictions or usage limits.
Q2. What is an access token?
Answer: An access token is a credential used to access protected resources on a server.
Q3. What is a refresh token?
Answer: A refresh token is used to obtain a new access token without requiring the user to sign in again, subject to the authentication provider's rules.
Q4. What is Keychain in iOS?
Answer: Keychain is Apple's secure credential storage technology for information such as passwords, authentication tokens, certificates, and cryptographic keys.
Q5. Why should tokens not be stored in UserDefaults?
Answer: UserDefaults is designed for application preferences, not sensitive credential storage. Keychain provides security features specifically intended for protected items.
Q6. What is the difference between authentication and authorization?
Answer: Authentication verifies identity, while authorization determines what resources or operations that identity is allowed to access.
Q7. What is the purpose of SecItemAdd?
Answer: SecItemAdd inserts a new item into the Keychain.
Q8. What is the purpose of SecItemCopyMatching?
Answer: SecItemCopyMatching searches for and retrieves a matching Keychain item.
Q9. What is OAuth 2.0?
Answer: OAuth 2.0 is an authorization framework that allows applications to obtain limited access to protected resources.
Q10. What is PKCE?
Answer: PKCE is an extension to the OAuth authorization-code flow that helps protect the code exchange, especially for public clients such as native mobile applications.
Q11. What is a JWT?
Answer: JSON Web Token is a compact token format used to carry claims between parties. A signed JWT commonly contains a header, payload, and signature.
Q12. Why should secret API keys not be embedded in an iOS app?
Answer: Application binaries can be inspected and reverse-engineered. Confidential server-side credentials should remain on a trusted backend.
Q13. What happens when an access token expires?
Answer: The server may reject protected API requests. The application can use the supported refresh-token flow or require the user to authenticate again.
Q14. What is token revocation?
Answer: Token revocation invalidates a credential so that it can no longer be used to access protected resources.
Q15. How can iOS applications improve credential security?
Answer: They can use Keychain, HTTPS, short-lived access tokens, secure refresh-token handling, backend authorization, credential rotation, and careful logging practices.
35. Quick Revision Checklist
- Understand API keys, passwords, access tokens, and refresh tokens.
- Differentiate authentication from authorization.
- Understand the role of Keychain Services.
- Know SecItemAdd, SecItemCopyMatching, SecItemUpdate, and SecItemDelete.
- Store sensitive credentials securely.
- Retrieve tokens before making protected API requests.
- Use the Authorization Bearer header correctly.
- Understand access-token expiration and refresh-token renewal.
- Handle HTTP 401 and 403 responses correctly.
- Understand OAuth 2.0 and PKCE.
- Know the structure and security properties of JWTs.
- Keep confidential API keys on the backend.
- Use HTTPS and secure network communication.
- Clear credentials during logout.
- Test credential storage, renewal, revocation, and failure scenarios.
36. Conclusion
Managing API keys, tokens, and authentication credentials is an essential part of building secure iOS applications. API keys identify applications or projects, access tokens authorize protected API requests, and refresh tokens can support session renewal.
Keychain Services provides a secure mechanism for storing sensitive credentials, while URLSession supports authenticated communication with backend services. OAuth 2.0, PKCE, token expiration, and credential revocation help establish a more reliable authentication architecture.
Developers should avoid embedding confidential secrets in application binaries, use HTTPS, validate authorization on the server, protect tokens, and remove credentials when they are no longer needed. These practices help protect user accounts and reduce the risk of unauthorized access.
37. Additional Learning Resources