Skip to content

Commit 40b9692

Browse files
authored
Updated sample app to support auth_time (#555)
1 parent 0762758 commit 40b9692

File tree

4 files changed

+67
-2
lines changed

4 files changed

+67
-2
lines changed

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

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import GoogleSignIn
2020
/// An observable class for authenticating via Google.
2121
final class GoogleSignInAuthenticator: ObservableObject {
2222
private var authViewModel: AuthenticationViewModel
23+
private var tokenClaims: Set<GIDTokenClaim> = Set([GIDTokenClaim.authTime()])
2324

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

69-
GIDSignIn.sharedInstance.signIn(withPresenting: presentingWindow) { signInResult, error in
71+
GIDSignIn.sharedInstance.signIn(
72+
withPresenting: presentingWindow,
73+
tokenClaims: tokenClaims
74+
) { signInResult, error in
7075
guard let signInResult = signInResult else {
7176
print("Error! \(String(describing: error))")
7277
return

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

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,19 @@ final class AuthenticationViewModel: ObservableObject {
2525
private var authenticator: GoogleSignInAuthenticator {
2626
return GoogleSignInAuthenticator(authViewModel: self)
2727
}
28+
29+
/// The user's `auth_time` as found in `idToken`.
30+
/// - note: If the user is logged out, then this will default to `nil`.
31+
var authTime: Date? {
32+
switch state {
33+
case .signedIn(let user):
34+
guard let idToken = user.idToken?.tokenString else { return nil }
35+
return decodeAuthTime(fromJWT: idToken)
36+
case .signedOut:
37+
return nil
38+
}
39+
}
40+
2841
/// The user-authorized scopes.
2942
/// - note: If the user is logged out, then this will default to empty.
3043
var authorizedScopes: [String] {
@@ -69,7 +82,48 @@ final class AuthenticationViewModel: ObservableObject {
6982
@MainActor func addBirthdayReadScope(completion: @escaping () -> Void) {
7083
authenticator.addBirthdayReadScope(completion: completion)
7184
}
85+
86+
var formattedAuthTimeString: String? {
87+
guard let date = authTime else { return nil }
88+
let formatter = DateFormatter()
89+
formatter.dateFormat = "MMM d, yyyy 'at' h:mm a"
90+
return formatter.string(from: date)
91+
}
92+
}
7293

94+
private extension AuthenticationViewModel {
95+
func decodeAuthTime(fromJWT jwt: String) -> Date? {
96+
let segments = jwt.components(separatedBy: ".")
97+
guard let parts = decodeJWTSegment(segments[1]),
98+
let authTimeInterval = parts["auth_time"] as? TimeInterval else {
99+
return nil
100+
}
101+
return Date(timeIntervalSince1970: authTimeInterval)
102+
}
103+
104+
func decodeJWTSegment(_ segment: String) -> [String: Any]? {
105+
guard let segmentData = base64UrlDecode(segment),
106+
let segmentJSON = try? JSONSerialization.jsonObject(with: segmentData, options: []),
107+
let payload = segmentJSON as? [String: Any] else {
108+
return nil
109+
}
110+
return payload
111+
}
112+
113+
func base64UrlDecode(_ value: String) -> Data? {
114+
var base64 = value
115+
.replacingOccurrences(of: "-", with: "+")
116+
.replacingOccurrences(of: "_", with: "/")
117+
118+
let length = Double(base64.lengthOfBytes(using: String.Encoding.utf8))
119+
let requiredLength = 4 * ceil(length / 4.0)
120+
let paddingLength = requiredLength - length
121+
if paddingLength > 0 {
122+
let padding = "".padding(toLength: Int(paddingLength), withPad: "=", startingAt: 0)
123+
base64 = base64 + padding
124+
}
125+
return Data(base64Encoded: base64, options: .ignoreUnknownCharacters)
126+
}
73127
}
74128

75129
extension AuthenticationViewModel {

Samples/Swift/DaysUntilBirthday/iOS/UserProfileView.swift

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,9 @@ struct UserProfileView: View {
3535
Text(userProfile.name)
3636
.font(.headline)
3737
Text(userProfile.email)
38+
if let authTimeString = authViewModel.formattedAuthTimeString {
39+
Text("Last sign-in date: \(authTimeString)")
40+
}
3841
}
3942
}
4043
NavigationLink(NSLocalizedString("View Days Until Birthday", comment: "View birthday days"),

Samples/Swift/DaysUntilBirthday/macOS/UserProfileView.swift

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,9 @@ struct UserProfileView: View {
1919
Text(userProfile.name)
2020
.font(.headline)
2121
Text(userProfile.email)
22+
if let authTimeString = authViewModel.formattedAuthTimeString {
23+
Text("Last sign-in date: \(authTimeString)")
24+
}
2225
}
2326
}
2427
Button(NSLocalizedString("Sign Out", comment: "Sign out button"), action: signOut)

0 commit comments

Comments
 (0)