Skip to content

Commit b86154a

Browse files
authored
fix(auth): store code verifier in keychain (#502)
1 parent 83f3385 commit b86154a

File tree

10 files changed

+50
-10
lines changed

10 files changed

+50
-10
lines changed

Sources/Auth/AuthClient.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ public final class AuthClient: Sendable {
6868
configuration: configuration,
6969
http: HTTPClient(configuration: configuration),
7070
api: APIClient(clientID: clientID),
71+
codeVerifierStorage: .live(clientID: clientID),
7172
sessionStorage: .live(clientID: clientID),
7273
sessionManager: .live(clientID: clientID)
7374
)

Sources/Auth/Defaults.swift

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,4 @@ extension AuthClient.Configuration {
5757

5858
/// The default value when initializing a ``AuthClient`` instance.
5959
public static let defaultAutoRefreshToken: Bool = true
60-
61-
static let defaultStorageKey = "supabase.auth.token"
6260
}

Sources/Auth/Internal/CodeVerifierStorage.swift

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,36 @@ struct CodeVerifierStorage: Sendable {
88
}
99

1010
extension CodeVerifierStorage {
11-
static var live: Self {
12-
let code = LockIsolated(String?.none)
11+
static func live(clientID: AuthClientID) -> Self {
12+
var configuration: AuthClient.Configuration { Dependencies[clientID].configuration }
13+
var key: String { "\(configuration.storageKey ?? STORAGE_KEY)-code-verifier" }
1314

1415
return Self(
15-
get: { code.value },
16-
set: { code.setValue($0) }
16+
get: {
17+
do {
18+
guard let data = try configuration.localStorage.retrieve(key: key) else {
19+
configuration.logger?.debug("Code verifier not found.")
20+
return nil
21+
}
22+
return String(decoding: data, as: UTF8.self)
23+
} catch {
24+
configuration.logger?.error("Failure loading code verifier: \(error.localizedDescription)")
25+
return nil
26+
}
27+
},
28+
set: { code in
29+
do {
30+
if let code, let data = code.data(using: .utf8) {
31+
try configuration.localStorage.store(key: key, value: data)
32+
} else if code == nil {
33+
try configuration.localStorage.remove(key: key)
34+
} else {
35+
configuration.logger?.error("Code verifier is not a valid UTF8 string.")
36+
}
37+
} catch {
38+
configuration.logger?.error("Failure storing code verifier: \(error.localizedDescription)")
39+
}
40+
}
1741
)
1842
}
1943
}

Sources/Auth/Internal/Contants.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,4 @@
88
import Foundation
99

1010
let EXPIRY_MARGIN: TimeInterval = 30
11+
let STORAGE_KEY = "supabase.auth.token"

Sources/Auth/Internal/Dependencies.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,13 @@ struct Dependencies: Sendable {
66
var configuration: AuthClient.Configuration
77
var http: any HTTPClientType
88
var api: APIClient
9+
var codeVerifierStorage: CodeVerifierStorage
910
var sessionStorage: SessionStorage
1011
var sessionManager: SessionManager
1112

1213
var eventEmitter = AuthStateChangeEventEmitter()
1314
var date: @Sendable () -> Date = { Date() }
14-
var codeVerifierStorage = CodeVerifierStorage.live
15+
1516
var urlOpener: URLOpener = .live
1617

1718
var encoder: JSONEncoder { configuration.encoder }

Sources/Auth/Internal/SessionStorage.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ struct SessionStorage {
2828
extension SessionStorage {
2929
static func live(clientID: AuthClientID) -> SessionStorage {
3030
var key: String {
31-
Dependencies[clientID].configuration.storageKey ?? AuthClient.Configuration.defaultStorageKey
31+
Dependencies[clientID].configuration.storageKey ?? STORAGE_KEY
3232
}
3333

3434
var oldKey: String { "supabase.session" }

Supabase.xcworkspace/xcshareddata/swiftpm/Package.resolved

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Tests/AuthTests/MockHelpers.swift

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import ConcurrencyExtras
12
import Foundation
23
import TestHelpers
34

@@ -23,7 +24,19 @@ extension Dependencies {
2324
),
2425
http: HTTPClientMock(),
2526
api: APIClient(clientID: AuthClientID()),
27+
codeVerifierStorage: CodeVerifierStorage.mock,
2628
sessionStorage: SessionStorage.live(clientID: AuthClientID()),
2729
sessionManager: SessionManager.live(clientID: AuthClientID())
2830
)
2931
}
32+
33+
extension CodeVerifierStorage {
34+
static var mock: CodeVerifierStorage {
35+
let code = LockIsolated<String?>(nil)
36+
37+
return Self(
38+
get: { code.value },
39+
set: { code.setValue($0) }
40+
)
41+
}
42+
}

Tests/AuthTests/SessionManagerTests.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ final class SessionManagerTests: XCTestCase {
3636
),
3737
http: http,
3838
api: APIClient(clientID: clientID),
39+
codeVerifierStorage: .mock,
3940
sessionStorage: SessionStorage.live(clientID: clientID),
4041
sessionManager: SessionManager.live(clientID: clientID)
4142
)

Tests/AuthTests/StoredSessionTests.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ final class StoredSessionTests: XCTestCase {
1717
),
1818
http: HTTPClientMock(),
1919
api: .init(clientID: clientID),
20+
codeVerifierStorage: .mock,
2021
sessionStorage: .live(clientID: clientID),
2122
sessionManager: .live(clientID: clientID)
2223
)

0 commit comments

Comments
 (0)