Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -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
Comment thread
AkshatG6 marked this conversation as resolved.
Outdated
} else if let error = error {
self.authViewModel.state = .signedOut
print("There was an error restoring the previous sign-in: \(error)")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,12 +36,14 @@ final class GoogleSignInAuthenticator: ObservableObject {
return
}
let manualNonce = UUID().uuidString
let tokenClaims: Set<GIDTokenClaim> = Set([GIDTokenClaim.authTime()])
Comment thread
AkshatG6 marked this conversation as resolved.
Outdated

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))")
Expand All @@ -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)
}

Expand All @@ -66,11 +72,27 @@ final class GoogleSignInAuthenticator: ObservableObject {
return
}

GIDSignIn.sharedInstance.signIn(withPresenting: presentingWindow) { signInResult, error in
let tokenClaims: Set<GIDTokenClaim> = Set([GIDTokenClaim.authTime()])

GIDSignIn.sharedInstance.signIn(
withPresenting: presentingWindow,
tokenClaims: tokenClaims
) { signInResult, error in
Comment thread
AkshatG6 marked this conversation as resolved.
Outdated
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
Expand Down Expand Up @@ -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: []),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand Down Expand Up @@ -69,7 +70,13 @@ final class AuthenticationViewModel: ObservableObject {
@MainActor func addBirthdayReadScope(completion: @escaping () -> Void) {
authenticator.addBirthdayReadScope(completion: completion)
}


var formattedAuthTimeString: String? {
Comment thread
AkshatG6 marked this conversation as resolved.
guard let date = authTime else { return nil }
let formatter = DateFormatter()
formatter.dateFormat = "MM/dd/yyyy"
Comment thread
AkshatG6 marked this conversation as resolved.
Outdated
return formatter.string(from: date)
}
}

extension AuthenticationViewModel {
Expand Down
3 changes: 3 additions & 0 deletions Samples/Swift/DaysUntilBirthday/iOS/UserProfileView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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"),
Expand Down
3 changes: 3 additions & 0 deletions Samples/Swift/DaysUntilBirthday/macOS/UserProfileView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down