Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions Samples/Swift/DaysUntilBirthday/Shared/Models/Claim.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@

struct Claim: Identifiable {
let key: String
let value: String
var id: String {key}
Comment thread
AkshatG6 marked this conversation as resolved.
Outdated
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import GoogleSignIn
/// An observable class for authenticating via Google.
final class GoogleSignInAuthenticator: ObservableObject {
private var authViewModel: AuthenticationViewModel
private var claims: Set<GIDClaim> = Set([GIDClaim.authTime()])
private let claims: Set<GIDClaim> = [GIDClaim.essentialAMR(), GIDClaim.authTime()]

/// Creates an instance of this authenticator.
/// - parameter authViewModel: The view model this authenticator will set logged in status on.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,15 +26,15 @@ final class AuthenticationViewModel: ObservableObject {
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? {
/// The user's `claims` as found in `idToken`.
/// - note: If the user is logged out, then this will default to empty.
var claims: [Claim] {
switch state {
case .signedIn(let user):
guard let idToken = user.idToken?.tokenString else { return nil }
return decodeAuthTime(fromJWT: idToken)
guard let idToken = user.idToken?.tokenString else { return [] }
return decodeClaims(fromJwt: idToken)
case .signedOut:
return nil
return []
}
}

Expand Down Expand Up @@ -82,23 +82,25 @@ 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? {
/// Returns a collection of formatted claim keys and values decoded from a JWT.
func decodeClaims(fromJwt jwt: String) -> [Claim] {
let segments = jwt.components(separatedBy: ".")
guard let parts = decodeJWTSegment(segments[1]),
let authTimeInterval = parts["auth_time"] as? TimeInterval else {
return nil

guard segments.count > 1,
let payload = decodeJWTSegment(segments[1])
else {
return []
}
return Date(timeIntervalSince1970: authTimeInterval)

let claims: [Claim?] = [
formatAuthTime(from: payload),
formatAmr(from: payload)
]

return claims.compactMap { $0 }
}

func decodeJWTSegment(_ segment: String) -> [String: Any]? {
Expand All @@ -124,6 +126,26 @@ private extension AuthenticationViewModel {
}
return Data(base64Encoded: base64, options: .ignoreUnknownCharacters)
}

/// Returns the `auth_time` claim from the given JWT, if present.
func formatAuthTime(from payload: [String: Any]) -> Claim? {
guard let authTime = payload["auth_time"] as? TimeInterval
else {
return nil
}
let date = Date(timeIntervalSince1970: authTime)
let formattedDate = DateFormatter.localizedString(from: date, dateStyle: .medium, timeStyle: .medium)
return Claim(key: "auth_time", value: formattedDate)
}

/// Returns the `amr` claim from the given JWT, if present.
private func formatAmr(from payload: [String: Any]) -> Claim? {
guard let amr = payload["amr"] as? [String]
else {
return nil
}
return Claim(key: "amr", value: amr.joined(separator: ", "))
}
}

extension AuthenticationViewModel {
Expand Down
32 changes: 30 additions & 2 deletions Samples/Swift/DaysUntilBirthday/iOS/UserProfileView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,27 @@
import SwiftUI
import GoogleSignIn

/// A view that displays a list of ID token claims.
struct ClaimsListView: View {
let claims: [Claim]
var body: some View {
List(claims) { claim in
VStack(alignment: .leading, spacing: 4) {
Text(claim.key)
.font(.caption)
.foregroundColor(.secondary)
.fontWeight(.semibold)

Text(claim.value)
.font(.body)
.lineLimit(4)
}
.padding(.vertical, 4)
}
.navigationTitle("ID Token Claims")
}
}

struct UserProfileView: View {
@EnvironmentObject var authViewModel: AuthenticationViewModel
@StateObject var birthdayViewModel = BirthdayViewModel()
Expand All @@ -35,8 +56,15 @@ struct UserProfileView: View {
Text(userProfile.name)
.font(.headline)
Text(userProfile.email)
if let authTimeString = authViewModel.formattedAuthTimeString {
Text("Last sign-in date: \(authTimeString)")

if !authViewModel.claims.isEmpty {
NavigationLink(destination: ClaimsListView(claims: authViewModel.claims)) {
HStack {
Image(systemName: "list.bullet.rectangle.portrait")
Text("View ID Token Claims")
}
.foregroundColor(.blue)
}
}
}
}
Expand Down
Loading