Skip to content

Commit 042c175

Browse files
authored
Gandhiakshat/sbc amr sample app (#597)
1 parent bd48ccb commit 042c175

File tree

5 files changed

+104
-22
lines changed

5 files changed

+104
-22
lines changed

Samples/Swift/DaysUntilBirthday/DaysUntilBirthday.xcodeproj/project.pbxproj

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
archiveVersion = 1;
44
classes = {
55
};
6-
objectVersion = 52;
6+
objectVersion = 54;
77
objects = {
88

99
/* Begin PBXBuildFile section */
@@ -40,6 +40,9 @@
4040
73DB41932805FC3B0028B8D3 /* UserProfileView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FE7173A527F5110F00910319 /* UserProfileView.swift */; };
4141
73DB41952805FC5F0028B8D3 /* Birthday.swift in Sources */ = {isa = PBXBuildFile; fileRef = 739FCC47270E659A00C92042 /* Birthday.swift */; };
4242
73DB419628060A9A0028B8D3 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 7345AD062703D9480020AFB1 /* Assets.xcassets */; };
43+
F8CE667E2F86AB020044EAFF /* Claim.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8CE667D2F86AAFD0044EAFF /* Claim.swift */; };
44+
F8CE667F2F86AB020044EAFF /* Claim.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8CE667D2F86AAFD0044EAFF /* Claim.swift */; };
45+
F8CE66802F86B6A30044EAFF /* Claim.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8CE667D2F86AAFD0044EAFF /* Claim.swift */; };
4346
/* End PBXBuildFile section */
4447

4548
/* Begin PBXContainerItemProxy section */
@@ -73,6 +76,7 @@
7376
739FCC45270E467600C92042 /* BirthdayView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BirthdayView.swift; sourceTree = "<group>"; };
7477
739FCC47270E659A00C92042 /* Birthday.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Birthday.swift; sourceTree = "<group>"; };
7578
73DB417E2805F9850028B8D3 /* GoogleSignIn-iOS */ = {isa = PBXFileReference; lastKnownFileType = folder; name = "GoogleSignIn-iOS"; path = ../../..; sourceTree = "<group>"; };
79+
F8CE667D2F86AAFD0044EAFF /* Claim.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Claim.swift; sourceTree = "<group>"; };
7680
FE2F2ABC2800D9C1005EA17F /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
7781
FE71738027ECFAF400910319 /* DaysUntilBirthday (macOS).app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "DaysUntilBirthday (macOS).app"; sourceTree = BUILT_PRODUCTS_DIR; };
7882
FE71738927ECFAF600910319 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = "<group>"; };
@@ -212,6 +216,7 @@
212216
FE7173AD27F65B8500910319 /* Models */ = {
213217
isa = PBXGroup;
214218
children = (
219+
F8CE667D2F86AAFD0044EAFF /* Claim.swift */,
215220
739FCC47270E659A00C92042 /* Birthday.swift */,
216221
);
217222
path = Models;
@@ -378,6 +383,7 @@
378383
739FCC46270E467600C92042 /* BirthdayView.swift in Sources */,
379384
7345AD1B2703D9C30020AFB1 /* SignInView.swift in Sources */,
380385
7345AD212703D9C30020AFB1 /* GoogleSignInAuthenticator.swift in Sources */,
386+
F8CE667E2F86AB020044EAFF /* Claim.swift in Sources */,
381387
7345AD232703D9C30020AFB1 /* UserProfileImageLoader.swift in Sources */,
382388
7345AD1E2703D9C30020AFB1 /* UserProfileImageView.swift in Sources */,
383389
736F49BC270E102C00580053 /* BirthdayViewModel.swift in Sources */,
@@ -394,6 +400,7 @@
394400
buildActionMask = 2147483647;
395401
files = (
396402
73508ED528134C7300ED7FB7 /* Credential.swift in Sources */,
403+
F8CE66802F86B6A30044EAFF /* Claim.swift in Sources */,
397404
73508EC82811BD9C00ED7FB7 /* DaysUntilBirthdayUITests_iOS.swift in Sources */,
398405
);
399406
runOnlyForDeploymentPostprocessing = 0;
@@ -406,6 +413,7 @@
406413
73DB418B2805FBC40028B8D3 /* BirthdayLoader.swift in Sources */,
407414
73DB418D2805FBD00028B8D3 /* AuthenticationViewModel.swift in Sources */,
408415
73DB418F2805FBF50028B8D3 /* ContentView.swift in Sources */,
416+
F8CE667F2F86AB020044EAFF /* Claim.swift in Sources */,
409417
73DB418C2805FBC80028B8D3 /* UserProfileImageLoader.swift in Sources */,
410418
73DB418E2805FBD40028B8D3 /* BirthdayViewModel.swift in Sources */,
411419
73DB41952805FC5F0028B8D3 /* Birthday.swift in Sources */,
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
/*
2+
* Copyright 2026 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
import Foundation
18+
19+
/// A decoded representation of a single ID token claim.
20+
struct Claim: Identifiable {
21+
let key: String
22+
let value: String
23+
var id: String { key }
24+
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +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 claims: Set<GIDClaim> = Set([GIDClaim.authTime()])
23+
private let claims: Set<GIDClaim> = [GIDClaim.essentialAMR(), GIDClaim.authTime()]
2424

2525
/// Creates an instance of this authenticator.
2626
/// - parameter authViewModel: The view model this authenticator will set logged in status on.

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

Lines changed: 40 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -26,15 +26,15 @@ final class AuthenticationViewModel: ObservableObject {
2626
return GoogleSignInAuthenticator(authViewModel: self)
2727
}
2828

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? {
29+
/// The user's `claims` as found in `idToken`.
30+
/// - note: If the user is logged out, then this will default to empty.
31+
var claims: [Claim] {
3232
switch state {
3333
case .signedIn(let user):
34-
guard let idToken = user.idToken?.tokenString else { return nil }
35-
return decodeAuthTime(fromJWT: idToken)
34+
guard let idToken = user.idToken?.tokenString else { return [] }
35+
return decodeClaims(fromJwt: idToken)
3636
case .signedOut:
37-
return nil
37+
return []
3838
}
3939
}
4040

@@ -82,23 +82,25 @@ final class AuthenticationViewModel: ObservableObject {
8282
@MainActor func addBirthdayReadScope(completion: @escaping () -> Void) {
8383
authenticator.addBirthdayReadScope(completion: completion)
8484
}
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-
}
9285
}
9386

9487
private extension AuthenticationViewModel {
95-
func decodeAuthTime(fromJWT jwt: String) -> Date? {
88+
/// Returns a collection of formatted claim keys and values decoded from a JWT.
89+
func decodeClaims(fromJwt jwt: String) -> [Claim] {
9690
let segments = jwt.components(separatedBy: ".")
97-
guard let parts = decodeJWTSegment(segments[1]),
98-
let authTimeInterval = parts["auth_time"] as? TimeInterval else {
99-
return nil
91+
92+
guard segments.count > 1,
93+
let payload = decodeJWTSegment(segments[1])
94+
else {
95+
return []
10096
}
101-
return Date(timeIntervalSince1970: authTimeInterval)
97+
98+
let claims: [Claim?] = [
99+
formatAuthTime(from: payload),
100+
formatAmr(from: payload)
101+
]
102+
103+
return claims.compactMap { $0 }
102104
}
103105

104106
func decodeJWTSegment(_ segment: String) -> [String: Any]? {
@@ -124,6 +126,26 @@ private extension AuthenticationViewModel {
124126
}
125127
return Data(base64Encoded: base64, options: .ignoreUnknownCharacters)
126128
}
129+
130+
/// Returns the `auth_time` claim from the given JWT, if present.
131+
func formatAuthTime(from payload: [String: Any]) -> Claim? {
132+
guard let authTime = payload["auth_time"] as? TimeInterval
133+
else {
134+
return nil
135+
}
136+
let date = Date(timeIntervalSince1970: authTime)
137+
let formattedDate = DateFormatter.localizedString(from: date, dateStyle: .medium, timeStyle: .medium)
138+
return Claim(key: "auth_time", value: formattedDate)
139+
}
140+
141+
/// Returns the `amr` claim from the given JWT, if present.
142+
private func formatAmr(from payload: [String: Any]) -> Claim? {
143+
guard let amr = payload["amr"] as? [String]
144+
else {
145+
return nil
146+
}
147+
return Claim(key: "amr", value: amr.joined(separator: ", "))
148+
}
127149
}
128150

129151
extension AuthenticationViewModel {

Samples/Swift/DaysUntilBirthday/iOS/UserProfileView.swift

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,27 @@
1717
import SwiftUI
1818
import GoogleSignIn
1919

20+
/// A view that displays a list of ID token claims.
21+
struct ClaimsListView: View {
22+
let claims: [Claim]
23+
var body: some View {
24+
List(claims) { claim in
25+
VStack(alignment: .leading, spacing: 4) {
26+
Text(claim.key)
27+
.font(.caption)
28+
.foregroundColor(.secondary)
29+
.fontWeight(.semibold)
30+
31+
Text(claim.value)
32+
.font(.body)
33+
.lineLimit(4)
34+
}
35+
.padding(.vertical, 4)
36+
}
37+
.navigationTitle("ID Token Claims")
38+
}
39+
}
40+
2041
struct UserProfileView: View {
2142
@EnvironmentObject var authViewModel: AuthenticationViewModel
2243
@StateObject var birthdayViewModel = BirthdayViewModel()
@@ -35,8 +56,15 @@ struct UserProfileView: View {
3556
Text(userProfile.name)
3657
.font(.headline)
3758
Text(userProfile.email)
38-
if let authTimeString = authViewModel.formattedAuthTimeString {
39-
Text("Last sign-in date: \(authTimeString)")
59+
60+
if !authViewModel.claims.isEmpty {
61+
NavigationLink(destination: ClaimsListView(claims: authViewModel.claims)) {
62+
HStack {
63+
Image(systemName: "list.bullet.rectangle.portrait")
64+
Text("View ID Token Claims")
65+
}
66+
.foregroundColor(.blue)
67+
}
4068
}
4169
}
4270
}

0 commit comments

Comments
 (0)