Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -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<GIDTokenClaim> = Set([GIDTokenClaim.authTime()])

/// Creates an instance of this authenticator.
/// - parameter authViewModel: The view model this authenticator will set logged in status on.
Expand All @@ -41,7 +42,8 @@ final class GoogleSignInAuthenticator: ObservableObject {
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 @@ -66,7 +68,10 @@ final class GoogleSignInAuthenticator: ObservableObject {
return
}

GIDSignIn.sharedInstance.signIn(withPresenting: presentingWindow) { signInResult, error in
GIDSignIn.sharedInstance.signIn(
withPresenting: presentingWindow,
tokenClaims: tokenClaims
) { signInResult, error in
guard let signInResult = signInResult else {
print("Error! \(String(describing: error))")
return
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,19 @@ final class AuthenticationViewModel: ObservableObject {
private var authenticator: GoogleSignInAuthenticator {
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):
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] {
Expand Down Expand Up @@ -69,7 +82,48 @@ 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 = "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 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 {
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