A customer reported an issue to Coder Orangu regarding the mobile application he developed.
The problem?
Even after users logged out, the old authentication token was still used in API requests. When Coder Orangu investigated the mobile logs, he noticed something troublingâdespite the session being invalid, API requests were still being sent using what seemed to be an old, invalid session token.
The invalidation process wasnât completely removing the session. A copy of the token was still being used by different parts of the app to authenticate API calls.
Current Implementation and Analysis
The current implementation uses a SessionToken struct to manage authentication tokens:
Session Token to Hold the authToken:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public struct SessionToken {
var token: String?
private var isActive: Bool
init(token: String, isActive:Bool = true) {
self.token = token
self.isActive = true
}
mutating func invalidateSession() {
self.isActive = false
print("đ Session invalid.")
self.token = nil
}
}
Here, we have the SessionToken struct that holds the authToken and a flag isActive to represent whether the session is still active or not. The invalidateSession() function is intended to deactivate the session, but it doesnât fully prevent the old token from being used because it doesnât prevent external references to the token.
User Session Object to Handle the Session
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public struct UserSession {
private var activeToken: SessionToken?
public init() {}
public mutating func createSession(token: String) {
guard activeToken == nil else {
print("â Error: A session is already active!")
return
}
activeToken = SessionToken(token: token)
print("â
Secure session created.")
}
public mutating func getToken() -> SessionToken? {
guard let activeToken = self.activeToken else {
print("active token is not available while ending the session")
return nil
}
return self.activeToken
}
public mutating func logout() {
guard var token = self.activeToken else { return }
token.invalidateSession()
}
}
The UserSession struct manages the active session, using a SessionToken object to store the session details. It has functions to create a session, get the token, and end the session.
Network Client to Make a Network Request Using the Session Token
1
2
3
4
5
6
7
8
9
10
11
public class NetworkClient {
private var activeToken: SessionToken?
init(activeToken: SessionToken) {
self.activeToken = activeToken
}
func fetchData() -> String? {
activeToken?.token
}
}
The NetworkClient struct holds a copy of the session token and uses it to make network requests. Even after UserSession invalids the session, the NetworkClient still holds a copy of the old token. This allows API requests to continue using an invalid session token, causing a security flaw.
Summary of Problems:
  Copying: Structs have value semantics so each service has its copy of the SessionToken object, So, the SessionToken struct allows the token to be copied around and accessed in multiple parts of the app. This means that invalidating the session doesnât prevent other parts of the app from still using the old token if they have a copy of it.
  Multiple References to the Token: The NetworkClient stores a copy of the session token. This creates a situation where, even after the session is invalid (or logged out), the old token might still exist in the NetworkClient and be used to make API requests.
Before jumping into the solution, letâs take a closer look at Value Type Semantics and how they contribute to this issue.
Value Type & Semantics
In Swift, value types (such as structs and enums) follow value semantics, meaning:
  When you assign a value type (such as a struct) to another variable, a new copy is created, independent of the original instance.
  Modifying one copy does not affect the other copiesâthey remain completely separate after the copy.
Now, letâs analyze how value semantics affect the SessionToken and contribute to the session invalidation issue.
Consider the following code snippet:
1
2
3
4
guard let activeToken = self.activeToken else {
print("active token is not available while ending the session")
return
}
Here, the guard statement implicitly creates a copy of activeToken due to Swiftâs value semantics. Any action performed on this copyâsuch as calling invalid()âonly affects the copied instance, not the original SessionToken stored in UserSession.
However, Coder Oranguâs intention was to invalidate the original session, not a temporary copy of it.
Here, the issue arises due to value semantics, which leads to unintended copies of SessionToken. There are multiple ways to address this, such as using a reference type (class) instead of a struct.
However, with Swiftâs new non-copyable feature, we can enforce that SessionToken cannot be copied, ensuring that only one valid instance exists simultaneously.
Letâs explore how non-copyable and ownership rules can help solve this problem.
Understanding Borrowing, Consuming, and Inout
Swiftâs ownership system introduces borrowing and consuming as new ownership types, specifically designed for handling non-copyable types. These concepts help prevent accidental copies, enforce safe memory management, and provide explicit control over value types.
  Borrowing(Temporary Access Without Ownership): Borrowing allows temporary access to a value without taking full ownership. This is useful when a function only needs to read or observe a non-copyable value without modifying or consuming it.
Coder Orangu can use borrowing in the NetworkClient method since it only needs read access to the session token:
1
2
3
4
5
class NetworkClient {
func fetchData(var session: borrowing SessionToken) -> String? {
session?.authToken
}
}
Note: By default, instance variables of non-copyable types have borrowing ownership, meaning they can be accessed without consuming them.
  Consuming (Taking Full Ownership): Consuming takes complete ownership of a value, removing it from the caller. Once consumed, the caller no longer has access to the value.
Coder Orangu should consume the session token when invalidating it, ensuring that no other part of the code retains access to an invalid token: .
1
2
3
4
5
consuming func invalidateSession() {
self.isActive = false
print("đ Session invalid.")
self.token = "invalid"
}
Once the session is invalid, it should no longer be used. Consuming ensures this by completely removing it from the scope.
  Inout(Mutable Reference Without Ownership): Inout allows passing a mutable reference to a function without taking ownership. Itâs useful when a function needs to modify a non-copyable value while keeping it accessible to the caller.
However, Coder Orangu does not currently have a use case where inout is necessary for modifying the session. A sample code:
1
2
3
func modifySession(_ session: inout SecureSession) {
print("Modifying session...")
}
NonCopyable Solution
The issue arises because SessionToken is a value type (struct), meaning it follows value semantics and is copied whenever assigned or passed around. This leads to unintended duplication, where an invalid session token might still be used elsewhere, causing security risks.
The solution is to make SessionToken non-copyable, ensuring that the token cannot be duplicated or passed around. This guarantees that once the session is invalid, no other part of the code can still access it.
Implementing Non-Copyable in Swift
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public struct SessionToken: ~Copyable {
private(set) var token: String?
private var isActive: Bool
init(token: String, isActive:Bool = true) {
self.token = token
self.isActive = true
}
consuming func invalidateSession() {
self.isActive = false
print("đ Session invalid.")
self.token = "invalid"
}
}
Making SessionToken non-copyable ensures that the Swift compiler prevents accidental copies, exactly what Coder Orangu needs to maintain session security.
After making SessionToken non-copyable, the compiler also requires UserSession to be non-copyable since it follows value semantics. This ensures that UserSession, which holds a SessionToken, cannot be inadvertently copied, maintaining strict ownership and preventing unintended duplication.
1
2
3
4
5
public struct UserSession: ~Copyable {
private var activeToken: SessionToken?
// Implementation
}
After resolving the previous issue, the compiler now raises an error for the following code:
1
guard let activeToken = self.activeToken else { ... }
âself.activeTokenâ is borrowed and cannot be consumed.
This happens because when a non-copyable struct is used as a stored property, its default ownership inside methods is borrowing. Swift enforces borrowing semantics to ensure that SessionToken remains unique, preventing unintended copies or stale references.
One way to resolve the issue without consuming activeToken is:
1
2
3
4
5
6
7
public consuming func logout() {
guard self.activeToken != nil else {
print("active token is not available while ending the session")
return
}
self.activeToken?.invalidateSession()
}
This approach avoids consuming activeToken while still safely checking its presence and returning the token string.
Did you notice Coder Orangu marked the logout method as consuming? He is aware that once the user logs out, the session should also be removed from the scope.
The compiler still does not stop and now raises an error on this line:
1
2
3
public mutating func getToken() -> SessionToken? {
return self.activeToken
}
Missing reinitialization of inout parameter âselfâ after consume.
This happens because the getToken method attempts to return a non-copyable instance. Since non-copyable types cannot be copied, returning self.activeToken consumes it. In Swift, consuming a stored property requires immediate re-initialisation, but since getToken() does not re-initialize activeToken, the compiler raises an error.
This introduces a new challenge: How do we properly return the token?
Coder Orangu also does not want to mark this method as consuming because requesting a token does not mean the tokenâs ownership should be transferred out of UserSession. Additionally, borrowing cannot be used here because the methodâs return type requires ownership, and borrowed values cannot be returned as they do not transfer ownership. The only solution Coder Orangu sees is to return only the auth token as a string instead of the entire SessionToken.
1
2
3
4
5
public mutating func getToken() -> String? {
guard self.activeToken?.isActive == true else { return nil}
return self.activeToken?.token
}
What happens to the NetworkClient? It holds an instance variable of a SessionToken, and once again, the compiler raises an error:
1
public init(activeToken: SessionToken) {}
Parameter of noncopyable type âSessionTokenâ must specify ownership.
We cannot use consuming here because the application requires multiple network requests, and each API call should not consume the SessionToken entirely.
why borrowing isnât allowed for stored properties?
Because stored properties require full ownership by default. A borrowed value is only temporarily accessible within a function and cannot be stored persistently.
To resolve this, Coder Orangu redesigned the approach, ensuring SessionToken is not passed around. Now aware of the risks of storing sensitive data, they modified the method as follows:
1
2
3
4
5
6
public class NetworkClient {
public init() {}
public func fetchData(authToken: String) -> String? {
authToken
}
}
This approach ensures that the token remains accessible for multiple API calls without violating Swiftâs ownership rules.
Where to Apply
Accidental Copies of Sensitive Data
Coder Orangu has already implemented a non-copyable type for session tokens, treating them as sensitive. Similarly, sensitive data like encryption keys or authentication credentials should never be copied unintentionally. This is where non-copyable types help prevent accidental duplication, ensuring that critical information stays protected.
Exclusive Access to Resources
Certain resources, such as files and database connections, require exclusive access to prevent conflicts and ensure proper management. Non-copyable types enforce this exclusivity by restricting duplication.
A logger is a good example: a single instance should maintain access to a log file, preventing multiple parts of the application from holding independent copies and causing inconsistencies.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
struct LoggerPersistence: ~Copyable {
private var logFilePath: String?
init() {
//set the path
print("đ Logger initialized.")
}
// Log method should NOT be consuming, so multiple logs can be written
func log(_ message: String) {
// your implementation
}
// Close method consumes the instance to ensure cleanup
mutating func close() {
self.logFilePath = nil
}
}
1
2
3
4
5
6
7
8
9
10
11
final class Logger {
private var persistence: LoggerPersistence?
func writeLog(_ message: String) {
persistence?.log(message) // Now logging does not consume the instance
}
consuming func shutdown() {
self.persistence = nil
}
}
Even though persistence is private, meaning it isnât shared, non-copyable types provide an additional safety net. If someone mistakenly exposes it in the future, the compiler will automatically prevent unintended copies of the resource.
Mutually Exclusive State Changes
Some objects represent one-time operationsâonce consumed, they should no longer be available. Non-copyable types enforce this constraint, preventing the accidental reuse of transactional objects.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
struct BankTransfer: ~Copyable {
consuming func run() {
// .. do it ..
discard self
}
deinit {
cancel()
}
consuming func cancel() {
// .. do the cancellation.
discard self
}
}
This BankTransfer example is taken directly from Appleâs WWDC session on non-copyable Types.
KeyTakeways
  Noncopyable types are a great tool to improve the correctness of your programs, as the compiler helps eliminate problematic code where unintended value copying occurs, enforcing stricter ownership rules to prevent misuse.
  Stored properties inside classes (e.g., UserSession) are borrowed by default and cannot be consumed directly within methods like logout().
  Function return values cannot be marked as borrowing for non-copyable types, as borrowed values do not transfer ownership and must remain within the functionâs scope.
Comments powered by Disqus.