diff --git a/Sources/Auth/AuthClient.swift b/Sources/Auth/AuthClient.swift index 2ca227c1..d2c383f0 100644 --- a/Sources/Auth/AuthClient.swift +++ b/Sources/Auth/AuthClient.swift @@ -861,7 +861,7 @@ public final class AuthClient: Sendable { var hasExpired = true var session: Session - let jwt = try decode(jwt: accessToken) + let jwt = JWT.decodePayload(accessToken) if let exp = jwt?["exp"] as? TimeInterval { expiresAt = Date(timeIntervalSince1970: exp) hasExpired = expiresAt <= now diff --git a/Sources/Auth/AuthMFA.swift b/Sources/Auth/AuthMFA.swift index 1c329e4d..fa5bb485 100644 --- a/Sources/Auth/AuthMFA.swift +++ b/Sources/Auth/AuthMFA.swift @@ -125,7 +125,7 @@ public struct AuthMFA: Sendable { public func getAuthenticatorAssuranceLevel() async throws -> AuthMFAGetAuthenticatorAssuranceLevelResponse { do { let session = try await sessionManager.session() - let payload = try decode(jwt: session.accessToken) + let payload = JWT.decodePayload(session.accessToken) var currentLevel: AuthenticatorAssuranceLevels? diff --git a/Sources/Auth/Internal/Helpers.swift b/Sources/Auth/Internal/Helpers.swift index 07d2a02a..de321a30 100644 --- a/Sources/Auth/Internal/Helpers.swift +++ b/Sources/Auth/Internal/Helpers.swift @@ -39,33 +39,3 @@ private func extractParams(from fragment: String) -> [URLQueryItem] { : nil } } - -func decode(jwt: String) throws -> [String: Any]? { - let parts = jwt.split(separator: ".") - guard parts.count == 3 else { - return nil - } - - let payload = String(parts[1]) - guard let data = base64URLDecode(payload) else { - return nil - } - let json = try JSONSerialization.jsonObject(with: data, options: []) - guard let decodedPayload = json as? [String: Any] else { - return nil - } - return decodedPayload -} - -private func base64URLDecode(_ value: String) -> Data? { - var base64 = value.replacingOccurrences(of: "-", with: "+") - .replacingOccurrences(of: "_", with: "/") - let length = Double(base64.lengthOfBytes(using: .utf8)) - let requiredLength = 4 * ceil(length / 4.0) - let paddingLength = requiredLength - length - if paddingLength > 0 { - let padding = "".padding(toLength: Int(paddingLength), withPad: "=", startingAt: 0) - base64 = base64 + padding - } - return Data(base64Encoded: base64, options: .ignoreUnknownCharacters) -} diff --git a/Sources/Helpers/JWT.swift b/Sources/Helpers/JWT.swift new file mode 100644 index 00000000..86dfb5d0 --- /dev/null +++ b/Sources/Helpers/JWT.swift @@ -0,0 +1,40 @@ +// +// JWT.swift +// Supabase +// +// Created by Guilherme Souza on 28/11/24. +// + +import Foundation + +package enum JWT { + package static func decodePayload(_ jwt: String) -> [String: Any]? { + let parts = jwt.split(separator: ".") + guard parts.count == 3 else { + return nil + } + + let payload = String(parts[1]) + guard let data = base64URLDecode(payload) else { + return nil + } + let json = try? JSONSerialization.jsonObject(with: data, options: []) + guard let decodedPayload = json as? [String: Any] else { + return nil + } + return decodedPayload + } + + private static func base64URLDecode(_ value: String) -> Data? { + var base64 = value.replacingOccurrences(of: "-", with: "+") + .replacingOccurrences(of: "_", with: "/") + let length = Double(base64.lengthOfBytes(using: .utf8)) + let requiredLength = 4 * ceil(length / 4.0) + let paddingLength = requiredLength - length + if paddingLength > 0 { + let padding = "".padding(toLength: Int(paddingLength), withPad: "=", startingAt: 0) + base64 = base64 + padding + } + return Data(base64Encoded: base64, options: .ignoreUnknownCharacters) + } +} diff --git a/Sources/Realtime/V2/RealtimeClientV2.swift b/Sources/Realtime/V2/RealtimeClientV2.swift index 5c072c4b..60976255 100644 --- a/Sources/Realtime/V2/RealtimeClientV2.swift +++ b/Sources/Realtime/V2/RealtimeClientV2.swift @@ -363,6 +363,14 @@ public final class RealtimeClientV2: Sendable { /// Sets the JWT access token used for channel subscription authorization and Realtime RLS. /// - Parameter token: A JWT string. public func setAuth(_ token: String?) async { + if let token, let payload = JWT.decodePayload(token), + let exp = payload["exp"] as? TimeInterval, exp < Date().timeIntervalSince1970 + { + options.logger?.warning( + "InvalidJWTToken: Invalid value for JWT claim \"exp\" with value \(exp)") + return + } + mutableState.withValue { $0.accessToken = token }