Skip to content

Commit ec6f69a

Browse files
committed
Initial update
1 parent 62f1fc9 commit ec6f69a

5 files changed

Lines changed: 84 additions & 1 deletion

File tree

Samples/Swift/DaysUntilBirthday/Shared/DaysUntilBirthday.swift

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,9 @@ struct DaysUntilBirthday: App {
2828
.onAppear {
2929
GIDSignIn.sharedInstance.restorePreviousSignIn { user, error in
3030
if let user = user {
31+
print("✅ Session restored for user: \(user.profile?.name ?? "Unknown")")
32+
print("Token %@", user.idToken?.tokenString ?? "NULL")
33+
self.authViewModel.authTime = user.authTime
3134
self.authViewModel.state = .signedIn(user)
3235
} else if let error = error {
3336
self.authViewModel.state = .signedOut
@@ -43,3 +46,41 @@ struct DaysUntilBirthday: App {
4346
}
4447
}
4548
}
49+
50+
import Foundation // Make sure to add this import for the new code
51+
52+
// This extension adds the .authTime property to the GIDGoogleUser object
53+
extension GIDGoogleUser {
54+
var authTime: Date? {
55+
guard let idToken = self.idToken?.tokenString,
56+
let payload = JWTDecoder.decode(jwtToken: idToken),
57+
let authTimeInterval = payload["auth_time"] as? TimeInterval else {
58+
return nil
59+
}
60+
return Date(timeIntervalSince1970: authTimeInterval)
61+
}
62+
}
63+
64+
// This is a helper utility that the extension uses to decode the token
65+
struct JWTDecoder {
66+
static func decode(jwtToken jwt: String) -> [String: Any]? {
67+
let segments = jwt.components(separatedBy: ".")
68+
guard segments.count > 1 else { return nil }
69+
var base64 = segments[1]
70+
.replacingOccurrences(of: "-", with: "+")
71+
.replacingOccurrences(of: "_", with: "/")
72+
let length = Double(base64.lengthOfBytes(using: .utf8))
73+
let requiredLength = 4 * ceil(length / 4.0)
74+
let paddingLength = requiredLength - length
75+
if paddingLength > 0 {
76+
let padding = "".padding(toLength: Int(paddingLength), withPad: "=", startingAt: 0)
77+
base64 += padding
78+
}
79+
guard let bodyData = Data(base64Encoded: base64, options: .ignoreUnknownCharacters),
80+
let json = try? JSONSerialization.jsonObject(with: bodyData, options: []),
81+
let payload = json as? [String: Any] else {
82+
return nil
83+
}
84+
return payload
85+
}
86+
}

Samples/Swift/DaysUntilBirthday/Shared/Services/GoogleSignInAuthenticator.swift

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,12 +36,14 @@ final class GoogleSignInAuthenticator: ObservableObject {
3636
return
3737
}
3838
let manualNonce = UUID().uuidString
39+
let tokenClaims: Set<GIDTokenClaim> = Set([GIDTokenClaim.authTime()])
3940

4041
GIDSignIn.sharedInstance.signIn(
4142
withPresenting: rootViewController,
4243
hint: nil,
4344
additionalScopes: nil,
44-
nonce: manualNonce
45+
nonce: manualNonce,
46+
tokenClaims:tokenClaims
4547
) { signInResult, error in
4648
guard let signInResult = signInResult else {
4749
print("Error! \(String(describing: error))")
@@ -57,6 +59,9 @@ final class GoogleSignInAuthenticator: ObservableObject {
5759
assertionFailure("ERROR: Returned nonce doesn't match manual nonce!")
5860
return
5961
}
62+
if let authTimeDate = self.decodeAuthTime(fromJWT: idToken) {
63+
self.authViewModel.authTime = authTimeDate
64+
}
6065
self.authViewModel.state = .signedIn(signInResult.user)
6166
}
6267

@@ -154,6 +159,16 @@ private extension GoogleSignInAuthenticator {
154159
return nonce
155160
}
156161

162+
func decodeAuthTime(fromJWT jwt: String) -> Date? {
163+
let segments = jwt.components(separatedBy: ".")
164+
guard segments.count > 1,
165+
let parts = decodeJWTSegment(segments[1]),
166+
let authTimeInterval = parts["auth_time"] as? TimeInterval else {
167+
return nil
168+
}
169+
return Date(timeIntervalSince1970: authTimeInterval)
170+
}
171+
157172
func decodeJWTSegment(_ segment: String) -> [String: Any]? {
158173
guard let segmentData = base64UrlDecode(segment),
159174
let segmentJSON = try? JSONSerialization.jsonObject(with: segmentData, options: []),

Samples/Swift/DaysUntilBirthday/Shared/ViewModels/AuthenticationViewModel.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ final class AuthenticationViewModel: ObservableObject {
2222
/// The user's log in status.
2323
/// - note: This will publish updates when its value changes.
2424
@Published var state: State
25+
@Published var authTime: Date?
2526
private var authenticator: GoogleSignInAuthenticator {
2627
return GoogleSignInAuthenticator(authViewModel: self)
2728
}

Samples/Swift/DaysUntilBirthday/iOS/UserProfileView.swift

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,13 @@ struct UserProfileView: View {
2424
return GIDSignIn.sharedInstance.currentUser
2525
}
2626

27+
private static var authTimeFormatter: DateFormatter = {
28+
let formatter = DateFormatter()
29+
formatter.dateStyle = .medium
30+
formatter.timeStyle = .short
31+
return formatter
32+
}()
33+
2734
var body: some View {
2835
return Group {
2936
if let userProfile = user?.profile {
@@ -35,6 +42,12 @@ struct UserProfileView: View {
3542
Text(userProfile.name)
3643
.font(.headline)
3744
Text(userProfile.email)
45+
if let authTime = authViewModel.authTime {
46+
Text("Last Login: \(Self.authTimeFormatter.string(from: authTime))")
47+
48+
} else {
49+
Text("Last Login: Not available")
50+
}
3851
}
3952
}
4053
NavigationLink(NSLocalizedString("View Days Until Birthday", comment: "View birthday days"),

Samples/Swift/DaysUntilBirthday/macOS/UserProfileView.swift

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,13 @@ struct UserProfileView: View {
88
return GIDSignIn.sharedInstance.currentUser
99
}
1010

11+
private static var authTimeFormatter: DateFormatter = {
12+
let formatter = DateFormatter()
13+
formatter.dateStyle = .medium
14+
formatter.timeStyle = .short
15+
return formatter
16+
}()
17+
1118
var body: some View {
1219
return Group {
1320
if let userProfile = user?.profile {
@@ -19,6 +26,12 @@ struct UserProfileView: View {
1926
Text(userProfile.name)
2027
.font(.headline)
2128
Text(userProfile.email)
29+
if let authTime = authViewModel.authTime {
30+
Text("Last Login: \(Self.authTimeFormatter.string(from: authTime))")
31+
32+
} else {
33+
Text("Last Login: Not available")
34+
}
2235
}
2336
}
2437
Button(NSLocalizedString("Sign Out", comment: "Sign out button"), action: signOut)

0 commit comments

Comments
 (0)