From e911834cc45daf46b760443004598a0f5c8e3917 Mon Sep 17 00:00:00 2001 From: Akshat Gandhi <54901287+AkshatG6@users.noreply.github.com> Date: Mon, 15 Sep 2025 08:55:36 -0700 Subject: [PATCH 1/8] Updated sample app to support auth_time --- .../Shared/DaysUntilBirthday.swift | 1 + .../Services/GoogleSignInAuthenticator.swift | 36 +++++++++++++++++-- .../ViewModels/AuthenticationViewModel.swift | 9 ++++- .../iOS/UserProfileView.swift | 3 ++ .../macOS/UserProfileView.swift | 3 ++ 5 files changed, 49 insertions(+), 3 deletions(-) diff --git a/Samples/Swift/DaysUntilBirthday/Shared/DaysUntilBirthday.swift b/Samples/Swift/DaysUntilBirthday/Shared/DaysUntilBirthday.swift index afa3083f..beece9dc 100644 --- a/Samples/Swift/DaysUntilBirthday/Shared/DaysUntilBirthday.swift +++ b/Samples/Swift/DaysUntilBirthday/Shared/DaysUntilBirthday.swift @@ -29,6 +29,7 @@ struct DaysUntilBirthday: App { GIDSignIn.sharedInstance.restorePreviousSignIn { user, error in if let user = user { self.authViewModel.state = .signedIn(user) + self.authViewModel.authTime = UserDefaults.standard.object(forKey: "authTime") as? Date } else if let error = error { self.authViewModel.state = .signedOut print("There was an error restoring the previous sign-in: \(error)") diff --git a/Samples/Swift/DaysUntilBirthday/Shared/Services/GoogleSignInAuthenticator.swift b/Samples/Swift/DaysUntilBirthday/Shared/Services/GoogleSignInAuthenticator.swift index e12d79a2..5cd982a4 100644 --- a/Samples/Swift/DaysUntilBirthday/Shared/Services/GoogleSignInAuthenticator.swift +++ b/Samples/Swift/DaysUntilBirthday/Shared/Services/GoogleSignInAuthenticator.swift @@ -36,12 +36,14 @@ final class GoogleSignInAuthenticator: ObservableObject { return } let manualNonce = UUID().uuidString + let tokenClaims: Set = Set([GIDTokenClaim.authTime()]) GIDSignIn.sharedInstance.signIn( withPresenting: rootViewController, hint: nil, additionalScopes: nil, - nonce: manualNonce + nonce: manualNonce, + tokenClaims: tokenClaims ) { signInResult, error in guard let signInResult = signInResult else { print("Error! \(String(describing: error))") @@ -57,6 +59,10 @@ final class GoogleSignInAuthenticator: ObservableObject { assertionFailure("ERROR: Returned nonce doesn't match manual nonce!") return } + if let authTimeDate = self.decodeAuthTime(fromJWT: idToken) { + self.authViewModel.authTime = authTimeDate + UserDefaults.standard.set(authTimeDate, forKey: "authTime") + } self.authViewModel.state = .signedIn(signInResult.user) } @@ -66,11 +72,27 @@ final class GoogleSignInAuthenticator: ObservableObject { return } - GIDSignIn.sharedInstance.signIn(withPresenting: presentingWindow) { signInResult, error in + let tokenClaims: Set = Set([GIDTokenClaim.authTime()]) + + GIDSignIn.sharedInstance.signIn( + withPresenting: presentingWindow, + tokenClaims: tokenClaims + ) { signInResult, error in guard let signInResult = signInResult else { print("Error! \(String(describing: error))") return } + + // If the idToken is nil, we cannot get the authTime, so we treat this + // as a failure for the app's sign-in flow and return. + guard let idToken = signInResult.user.idToken?.tokenString else { + print("Error: idToken is missing from signInResult.") + return + } + if let authTimeDate = self.decodeAuthTime(fromJWT: idToken) { + self.authViewModel.authTime = authTimeDate + UserDefaults.standard.set(authTimeDate, forKey: "authTime") + } self.authViewModel.state = .signedIn(signInResult.user) } #endif @@ -154,6 +176,16 @@ private extension GoogleSignInAuthenticator { return nonce } + func decodeAuthTime(fromJWT jwt: String) -> Date? { + let segments = jwt.components(separatedBy: ".") + guard segments.count > 1, + let parts = decodeJWTSegment(segments[1]), + let authTimeInterval = parts["auth_time"] as? TimeInterval else { + return nil + } + return Date(timeIntervalSince1970: authTimeInterval) + } + func decodeJWTSegment(_ segment: String) -> [String: Any]? { guard let segmentData = base64UrlDecode(segment), let segmentJSON = try? JSONSerialization.jsonObject(with: segmentData, options: []), diff --git a/Samples/Swift/DaysUntilBirthday/Shared/ViewModels/AuthenticationViewModel.swift b/Samples/Swift/DaysUntilBirthday/Shared/ViewModels/AuthenticationViewModel.swift index 15bee104..c667d19c 100644 --- a/Samples/Swift/DaysUntilBirthday/Shared/ViewModels/AuthenticationViewModel.swift +++ b/Samples/Swift/DaysUntilBirthday/Shared/ViewModels/AuthenticationViewModel.swift @@ -22,6 +22,7 @@ final class AuthenticationViewModel: ObservableObject { /// The user's log in status. /// - note: This will publish updates when its value changes. @Published var state: State + @Published var authTime: Date? private var authenticator: GoogleSignInAuthenticator { return GoogleSignInAuthenticator(authViewModel: self) } @@ -69,7 +70,13 @@ final class AuthenticationViewModel: ObservableObject { @MainActor func addBirthdayReadScope(completion: @escaping () -> Void) { authenticator.addBirthdayReadScope(completion: completion) } - + + var formattedAuthTimeString: String? { + guard let date = authTime else { return nil } + let formatter = DateFormatter() + formatter.dateFormat = "dd/MM/yyyy" + return formatter.string(from: date) + } } extension AuthenticationViewModel { diff --git a/Samples/Swift/DaysUntilBirthday/iOS/UserProfileView.swift b/Samples/Swift/DaysUntilBirthday/iOS/UserProfileView.swift index 93366f47..929ec3a2 100644 --- a/Samples/Swift/DaysUntilBirthday/iOS/UserProfileView.swift +++ b/Samples/Swift/DaysUntilBirthday/iOS/UserProfileView.swift @@ -35,6 +35,9 @@ struct UserProfileView: View { Text(userProfile.name) .font(.headline) Text(userProfile.email) + if let authTimeString = authViewModel.formattedAuthTimeString { + Text("Last sign-in date: \(authTimeString)") + } } } NavigationLink(NSLocalizedString("View Days Until Birthday", comment: "View birthday days"), diff --git a/Samples/Swift/DaysUntilBirthday/macOS/UserProfileView.swift b/Samples/Swift/DaysUntilBirthday/macOS/UserProfileView.swift index d7faad97..c19a716f 100644 --- a/Samples/Swift/DaysUntilBirthday/macOS/UserProfileView.swift +++ b/Samples/Swift/DaysUntilBirthday/macOS/UserProfileView.swift @@ -19,6 +19,9 @@ struct UserProfileView: View { Text(userProfile.name) .font(.headline) Text(userProfile.email) + if let authTimeString = authViewModel.formattedAuthTimeString { + Text("Last sign-in date: \(authTimeString)") + } } } Button(NSLocalizedString("Sign Out", comment: "Sign out button"), action: signOut) From d42c329f3b0587cc2479500ac5462217bb08cf1e Mon Sep 17 00:00:00 2001 From: Akshat Gandhi <54901287+AkshatG6@users.noreply.github.com> Date: Mon, 15 Sep 2025 09:11:02 -0700 Subject: [PATCH 2/8] Updated date format for showing last authentication time from to --- .../Shared/ViewModels/AuthenticationViewModel.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Samples/Swift/DaysUntilBirthday/Shared/ViewModels/AuthenticationViewModel.swift b/Samples/Swift/DaysUntilBirthday/Shared/ViewModels/AuthenticationViewModel.swift index c667d19c..d0f28cb4 100644 --- a/Samples/Swift/DaysUntilBirthday/Shared/ViewModels/AuthenticationViewModel.swift +++ b/Samples/Swift/DaysUntilBirthday/Shared/ViewModels/AuthenticationViewModel.swift @@ -74,7 +74,7 @@ final class AuthenticationViewModel: ObservableObject { var formattedAuthTimeString: String? { guard let date = authTime else { return nil } let formatter = DateFormatter() - formatter.dateFormat = "dd/MM/yyyy" + formatter.dateFormat = "MM/dd/yyyy" return formatter.string(from: date) } } From cfae9ba8d338d2966c8640665020ccef08fbebf7 Mon Sep 17 00:00:00 2001 From: Akshat Gandhi <54901287+AkshatG6@users.noreply.github.com> Date: Mon, 15 Sep 2025 10:26:29 -0700 Subject: [PATCH 3/8] Updated GoogleSignInAuthenticator to reduce code duplication --- .../Shared/Services/GoogleSignInAuthenticator.swift | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/Samples/Swift/DaysUntilBirthday/Shared/Services/GoogleSignInAuthenticator.swift b/Samples/Swift/DaysUntilBirthday/Shared/Services/GoogleSignInAuthenticator.swift index 5cd982a4..22af30a5 100644 --- a/Samples/Swift/DaysUntilBirthday/Shared/Services/GoogleSignInAuthenticator.swift +++ b/Samples/Swift/DaysUntilBirthday/Shared/Services/GoogleSignInAuthenticator.swift @@ -20,6 +20,7 @@ import GoogleSignIn /// An observable class for authenticating via Google. final class GoogleSignInAuthenticator: ObservableObject { private var authViewModel: AuthenticationViewModel + private var tokenClaims: Set = Set([GIDTokenClaim.authTime()]) /// Creates an instance of this authenticator. /// - parameter authViewModel: The view model this authenticator will set logged in status on. @@ -36,7 +37,6 @@ final class GoogleSignInAuthenticator: ObservableObject { return } let manualNonce = UUID().uuidString - let tokenClaims: Set = Set([GIDTokenClaim.authTime()]) GIDSignIn.sharedInstance.signIn( withPresenting: rootViewController, @@ -72,8 +72,6 @@ final class GoogleSignInAuthenticator: ObservableObject { return } - let tokenClaims: Set = Set([GIDTokenClaim.authTime()]) - GIDSignIn.sharedInstance.signIn( withPresenting: presentingWindow, tokenClaims: tokenClaims From 7bdb3e8394dac4f2c2d67d4a464b980b1b522c7d Mon Sep 17 00:00:00 2001 From: Akshat Gandhi <54901287+AkshatG6@users.noreply.github.com> Date: Mon, 15 Sep 2025 13:23:50 -0700 Subject: [PATCH 4/8] Removed the logic for saving auth_time within User Defaults. Added the logic for decoding id token within AuthenticationViewModel --- .../Shared/DaysUntilBirthday.swift | 1 - .../Services/GoogleSignInAuthenticator.swift | 27 +--------- .../ViewModels/AuthenticationViewModel.swift | 49 ++++++++++++++++++- 3 files changed, 49 insertions(+), 28 deletions(-) diff --git a/Samples/Swift/DaysUntilBirthday/Shared/DaysUntilBirthday.swift b/Samples/Swift/DaysUntilBirthday/Shared/DaysUntilBirthday.swift index beece9dc..afa3083f 100644 --- a/Samples/Swift/DaysUntilBirthday/Shared/DaysUntilBirthday.swift +++ b/Samples/Swift/DaysUntilBirthday/Shared/DaysUntilBirthday.swift @@ -29,7 +29,6 @@ struct DaysUntilBirthday: App { GIDSignIn.sharedInstance.restorePreviousSignIn { user, error in if let user = user { self.authViewModel.state = .signedIn(user) - self.authViewModel.authTime = UserDefaults.standard.object(forKey: "authTime") as? Date } else if let error = error { self.authViewModel.state = .signedOut print("There was an error restoring the previous sign-in: \(error)") diff --git a/Samples/Swift/DaysUntilBirthday/Shared/Services/GoogleSignInAuthenticator.swift b/Samples/Swift/DaysUntilBirthday/Shared/Services/GoogleSignInAuthenticator.swift index 22af30a5..a2f39a41 100644 --- a/Samples/Swift/DaysUntilBirthday/Shared/Services/GoogleSignInAuthenticator.swift +++ b/Samples/Swift/DaysUntilBirthday/Shared/Services/GoogleSignInAuthenticator.swift @@ -59,10 +59,6 @@ final class GoogleSignInAuthenticator: ObservableObject { assertionFailure("ERROR: Returned nonce doesn't match manual nonce!") return } - if let authTimeDate = self.decodeAuthTime(fromJWT: idToken) { - self.authViewModel.authTime = authTimeDate - UserDefaults.standard.set(authTimeDate, forKey: "authTime") - } self.authViewModel.state = .signedIn(signInResult.user) } @@ -80,17 +76,6 @@ final class GoogleSignInAuthenticator: ObservableObject { print("Error! \(String(describing: error))") return } - - // If the idToken is nil, we cannot get the authTime, so we treat this - // as a failure for the app's sign-in flow and return. - guard let idToken = signInResult.user.idToken?.tokenString else { - print("Error: idToken is missing from signInResult.") - return - } - if let authTimeDate = self.decodeAuthTime(fromJWT: idToken) { - self.authViewModel.authTime = authTimeDate - UserDefaults.standard.set(authTimeDate, forKey: "authTime") - } self.authViewModel.state = .signedIn(signInResult.user) } #endif @@ -164,7 +149,7 @@ final class GoogleSignInAuthenticator: ObservableObject { // MARK: Parse nonce from JWT ID Token -private extension GoogleSignInAuthenticator { +extension GoogleSignInAuthenticator { func decodeNonce(fromJWT jwt: String) -> String? { let segments = jwt.components(separatedBy: ".") guard let parts = decodeJWTSegment(segments[1]), @@ -174,16 +159,6 @@ private extension GoogleSignInAuthenticator { return nonce } - func decodeAuthTime(fromJWT jwt: String) -> Date? { - let segments = jwt.components(separatedBy: ".") - guard segments.count > 1, - let parts = decodeJWTSegment(segments[1]), - let authTimeInterval = parts["auth_time"] as? TimeInterval else { - return nil - } - return Date(timeIntervalSince1970: authTimeInterval) - } - func decodeJWTSegment(_ segment: String) -> [String: Any]? { guard let segmentData = base64UrlDecode(segment), let segmentJSON = try? JSONSerialization.jsonObject(with: segmentData, options: []), diff --git a/Samples/Swift/DaysUntilBirthday/Shared/ViewModels/AuthenticationViewModel.swift b/Samples/Swift/DaysUntilBirthday/Shared/ViewModels/AuthenticationViewModel.swift index d0f28cb4..fdc65fb5 100644 --- a/Samples/Swift/DaysUntilBirthday/Shared/ViewModels/AuthenticationViewModel.swift +++ b/Samples/Swift/DaysUntilBirthday/Shared/ViewModels/AuthenticationViewModel.swift @@ -22,10 +22,21 @@ final class AuthenticationViewModel: ObservableObject { /// The user's log in status. /// - note: This will publish updates when its value changes. @Published var state: State - @Published var authTime: Date? private var authenticator: GoogleSignInAuthenticator { return GoogleSignInAuthenticator(authViewModel: self) } + + // 3. Change authTime to a computed property + var authTime: Date? { + switch state { + case .signedIn(let user): + guard let idToken = user.idToken?.tokenString else { return nil } + return decodeAuthTime(fromJWT: idToken) + case .signedOut: + return nil + } + } + /// The user-authorized scopes. /// - note: If the user is logged out, then this will default to empty. var authorizedScopes: [String] { @@ -79,6 +90,42 @@ final class AuthenticationViewModel: ObservableObject { } } +private extension AuthenticationViewModel { + func decodeAuthTime(fromJWT jwt: String) -> Date? { + let segments = jwt.components(separatedBy: ".") + guard segments.count > 1, + let parts = decodeJWTSegment(segments[1]), + let authTimeInterval = parts["auth_time"] as? TimeInterval else { + return nil + } + return Date(timeIntervalSince1970: authTimeInterval) + } + + func decodeJWTSegment(_ segment: String) -> [String: Any]? { + guard let segmentData = base64UrlDecode(segment), + let segmentJSON = try? JSONSerialization.jsonObject(with: segmentData, options: []), + let payload = segmentJSON as? [String: Any] else { + return nil + } + return payload + } + + func base64UrlDecode(_ value: String) -> Data? { + var base64 = value + .replacingOccurrences(of: "-", with: "+") + .replacingOccurrences(of: "_", with: "/") + + let length = Double(base64.lengthOfBytes(using: String.Encoding.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) + } +} + extension AuthenticationViewModel { /// An enumeration representing logged in status. enum State { From 98b388d0db36f556f51cd9cdde52198c75cf3a8b Mon Sep 17 00:00:00 2001 From: Akshat Gandhi <54901287+AkshatG6@users.noreply.github.com> Date: Mon, 15 Sep 2025 13:26:04 -0700 Subject: [PATCH 5/8] Reverted private keyword for the class extension in GoogleSignInAuthenticator --- .../Shared/Services/GoogleSignInAuthenticator.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Samples/Swift/DaysUntilBirthday/Shared/Services/GoogleSignInAuthenticator.swift b/Samples/Swift/DaysUntilBirthday/Shared/Services/GoogleSignInAuthenticator.swift index a2f39a41..3310826d 100644 --- a/Samples/Swift/DaysUntilBirthday/Shared/Services/GoogleSignInAuthenticator.swift +++ b/Samples/Swift/DaysUntilBirthday/Shared/Services/GoogleSignInAuthenticator.swift @@ -149,7 +149,7 @@ final class GoogleSignInAuthenticator: ObservableObject { // MARK: Parse nonce from JWT ID Token -extension GoogleSignInAuthenticator { +private extension GoogleSignInAuthenticator { func decodeNonce(fromJWT jwt: String) -> String? { let segments = jwt.components(separatedBy: ".") guard let parts = decodeJWTSegment(segments[1]), From 88e8caaaeb3de33bb171cd3c790cb3fcc4b8a72c Mon Sep 17 00:00:00 2001 From: Akshat Gandhi <54901287+AkshatG6@users.noreply.github.com> Date: Mon, 15 Sep 2025 15:32:29 -0700 Subject: [PATCH 6/8] Updated styling and spacing for better readability --- .../Services/GoogleSignInAuthenticator.swift | 2 +- .../ViewModels/AuthenticationViewModel.swift | 64 +++++++++---------- .../iOS/UserProfileView.swift | 2 +- .../macOS/UserProfileView.swift | 2 +- 4 files changed, 34 insertions(+), 36 deletions(-) diff --git a/Samples/Swift/DaysUntilBirthday/Shared/Services/GoogleSignInAuthenticator.swift b/Samples/Swift/DaysUntilBirthday/Shared/Services/GoogleSignInAuthenticator.swift index 3310826d..88d00f2e 100644 --- a/Samples/Swift/DaysUntilBirthday/Shared/Services/GoogleSignInAuthenticator.swift +++ b/Samples/Swift/DaysUntilBirthday/Shared/Services/GoogleSignInAuthenticator.swift @@ -71,7 +71,7 @@ final class GoogleSignInAuthenticator: ObservableObject { GIDSignIn.sharedInstance.signIn( withPresenting: presentingWindow, tokenClaims: tokenClaims - ) { signInResult, error in + ) { signInResult, error in guard let signInResult = signInResult else { print("Error! \(String(describing: error))") return diff --git a/Samples/Swift/DaysUntilBirthday/Shared/ViewModels/AuthenticationViewModel.swift b/Samples/Swift/DaysUntilBirthday/Shared/ViewModels/AuthenticationViewModel.swift index fdc65fb5..cd13da32 100644 --- a/Samples/Swift/DaysUntilBirthday/Shared/ViewModels/AuthenticationViewModel.swift +++ b/Samples/Swift/DaysUntilBirthday/Shared/ViewModels/AuthenticationViewModel.swift @@ -26,7 +26,6 @@ final class AuthenticationViewModel: ObservableObject { return GoogleSignInAuthenticator(authViewModel: self) } - // 3. Change authTime to a computed property var authTime: Date? { switch state { case .signedIn(let user): @@ -83,47 +82,46 @@ final class AuthenticationViewModel: ObservableObject { } var formattedAuthTimeString: String? { - guard let date = authTime else { return nil } - let formatter = DateFormatter() - formatter.dateFormat = "MM/dd/yyyy" - return formatter.string(from: date) + guard let date = authTime else { return nil } + let formatter = DateFormatter() + formatter.dateFormat = "MMM d, yyyy 'at' h:mm a" + return formatter.string(from: date) } } private extension AuthenticationViewModel { - func decodeAuthTime(fromJWT jwt: String) -> Date? { - let segments = jwt.components(separatedBy: ".") - guard segments.count > 1, - let parts = decodeJWTSegment(segments[1]), - let authTimeInterval = parts["auth_time"] as? TimeInterval else { - return nil - } - return Date(timeIntervalSince1970: authTimeInterval) + func decodeAuthTime(fromJWT jwt: String) -> Date? { + let segments = jwt.components(separatedBy: ".") + guard let parts = decodeJWTSegment(segments[1]), + let authTimeInterval = parts["auth_time"] as? TimeInterval else { + return nil } + return Date(timeIntervalSince1970: authTimeInterval) + } - func decodeJWTSegment(_ segment: String) -> [String: Any]? { - guard let segmentData = base64UrlDecode(segment), - let segmentJSON = try? JSONSerialization.jsonObject(with: segmentData, options: []), - let payload = segmentJSON as? [String: Any] else { - return nil - } - return payload + func decodeJWTSegment(_ segment: String) -> [String: Any]? { + guard let segmentData = base64UrlDecode(segment), + let segmentJSON = try? JSONSerialization.jsonObject(with: segmentData, options: []), + let payload = segmentJSON as? [String: Any] else { + return nil } + return payload + } + + func base64UrlDecode(_ value: String) -> Data? { + var base64 = value + .replacingOccurrences(of: "-", with: "+") + .replacingOccurrences(of: "_", with: "/") - func base64UrlDecode(_ value: String) -> Data? { - var base64 = value - .replacingOccurrences(of: "-", with: "+") - .replacingOccurrences(of: "_", with: "/") - - let length = Double(base64.lengthOfBytes(using: String.Encoding.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) + let length = Double(base64.lengthOfBytes(using: String.Encoding.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) + } } extension AuthenticationViewModel { diff --git a/Samples/Swift/DaysUntilBirthday/iOS/UserProfileView.swift b/Samples/Swift/DaysUntilBirthday/iOS/UserProfileView.swift index 929ec3a2..256b777b 100644 --- a/Samples/Swift/DaysUntilBirthday/iOS/UserProfileView.swift +++ b/Samples/Swift/DaysUntilBirthday/iOS/UserProfileView.swift @@ -36,7 +36,7 @@ struct UserProfileView: View { .font(.headline) Text(userProfile.email) if let authTimeString = authViewModel.formattedAuthTimeString { - Text("Last sign-in date: \(authTimeString)") + Text("Last sign-in date: \(authTimeString)") } } } diff --git a/Samples/Swift/DaysUntilBirthday/macOS/UserProfileView.swift b/Samples/Swift/DaysUntilBirthday/macOS/UserProfileView.swift index c19a716f..3fddc744 100644 --- a/Samples/Swift/DaysUntilBirthday/macOS/UserProfileView.swift +++ b/Samples/Swift/DaysUntilBirthday/macOS/UserProfileView.swift @@ -20,7 +20,7 @@ struct UserProfileView: View { .font(.headline) Text(userProfile.email) if let authTimeString = authViewModel.formattedAuthTimeString { - Text("Last sign-in date: \(authTimeString)") + Text("Last sign-in date: \(authTimeString)") } } } From 20c52ba8a4f90e7c9f1091ec2aa6e9ec7cada6c9 Mon Sep 17 00:00:00 2001 From: Akshat Gandhi <54901287+AkshatG6@users.noreply.github.com> Date: Mon, 15 Sep 2025 15:42:32 -0700 Subject: [PATCH 7/8] Added comments to AuthenticationViewModel to improve readability --- .../Shared/ViewModels/AuthenticationViewModel.swift | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Samples/Swift/DaysUntilBirthday/Shared/ViewModels/AuthenticationViewModel.swift b/Samples/Swift/DaysUntilBirthday/Shared/ViewModels/AuthenticationViewModel.swift index cd13da32..68f85972 100644 --- a/Samples/Swift/DaysUntilBirthday/Shared/ViewModels/AuthenticationViewModel.swift +++ b/Samples/Swift/DaysUntilBirthday/Shared/ViewModels/AuthenticationViewModel.swift @@ -26,6 +26,8 @@ final class AuthenticationViewModel: ObservableObject { return GoogleSignInAuthenticator(authViewModel: self) } + /// The user's `auth_time` as found in `idToken`. + /// - note: If the user is logged out, then this will default to nil. var authTime: Date? { switch state { case .signedIn(let user): From fb6eef3c281577afc7d6704137625eaf79add226 Mon Sep 17 00:00:00 2001 From: Akshat Gandhi <54901287+AkshatG6@users.noreply.github.com> Date: Mon, 15 Sep 2025 15:50:28 -0700 Subject: [PATCH 8/8] Added markdown to the comment in AuthenticationViewModel --- .../Shared/ViewModels/AuthenticationViewModel.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Samples/Swift/DaysUntilBirthday/Shared/ViewModels/AuthenticationViewModel.swift b/Samples/Swift/DaysUntilBirthday/Shared/ViewModels/AuthenticationViewModel.swift index 68f85972..b528fc68 100644 --- a/Samples/Swift/DaysUntilBirthday/Shared/ViewModels/AuthenticationViewModel.swift +++ b/Samples/Swift/DaysUntilBirthday/Shared/ViewModels/AuthenticationViewModel.swift @@ -27,7 +27,7 @@ final class AuthenticationViewModel: ObservableObject { } /// The user's `auth_time` as found in `idToken`. - /// - note: If the user is logged out, then this will default to nil. + /// - note: If the user is logged out, then this will default to `nil`. var authTime: Date? { switch state { case .signedIn(let user):