AuthTokenResult.swift 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165
  1. // Copyright 2023 Google LLC
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. import Foundation
  15. @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *)
  16. extension AuthTokenResult: NSSecureCoding {}
  17. /// A data class containing the ID token JWT string and other properties associated with the
  18. /// token including the decoded payload claims.
  19. @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *)
  20. @objc(FIRAuthTokenResult) open class AuthTokenResult: NSObject,
  21. @unchecked Sendable /* TODO: sendable */ {
  22. /// Stores the JWT string of the ID token.
  23. @objc open var token: String
  24. /// Stores the ID token's expiration date.
  25. @objc open var expirationDate: Date
  26. /// Stores the ID token's authentication date.
  27. ///
  28. /// This is the date the user was signed in and NOT the date the token was refreshed.
  29. @objc open var authDate: Date
  30. /// Stores the date that the ID token was issued.
  31. ///
  32. /// This is the date last refreshed and NOT the last authentication date.
  33. @objc open var issuedAtDate: Date
  34. /// Stores sign-in provider through which the token was obtained.
  35. @objc open var signInProvider: String
  36. /// Stores sign-in second factor through which the token was obtained.
  37. @objc open var signInSecondFactor: String
  38. /// Stores the entire payload of claims found on the ID token.
  39. ///
  40. /// This includes the standard
  41. /// reserved claims as well as custom claims set by the developer via the Admin SDK.
  42. @objc open var claims: [String: Any]
  43. private class func getTokenPayloadData(_ token: String) throws -> Data {
  44. let tokenStringArray = token.components(separatedBy: ".")
  45. // The JWT should have three parts, though we only use the second in this method.
  46. if tokenStringArray.count != 3 {
  47. throw AuthErrorUtils.malformedJWTError(token: token, underlyingError: nil)
  48. }
  49. /// The token payload is always the second index of the array.
  50. let IDToken = tokenStringArray[1]
  51. /// Convert the base64URL encoded string to a base64 encoded string.
  52. /// * Replace "_" with "/"
  53. /// * Replace "-" with "+"
  54. var tokenPayload = IDToken.replacingOccurrences(of: "_", with: "/")
  55. .replacingOccurrences(of: "-", with: "+")
  56. // Pad the token payload with "=" signs if the payload's length is not a multiple of 4.
  57. if tokenPayload.count % 4 != 0 {
  58. let length = tokenPayload.count + (4 - tokenPayload.count % 4)
  59. tokenPayload = tokenPayload.padding(toLength: length, withPad: "=", startingAt: 0)
  60. }
  61. guard let data = Data(base64Encoded: tokenPayload, options: [.ignoreUnknownCharacters]) else {
  62. throw AuthErrorUtils.malformedJWTError(token: token, underlyingError: nil)
  63. }
  64. return data
  65. }
  66. private class func getTokenPayloadDictionary(_ token: String,
  67. _ payloadData: Data) throws -> [String: Any] {
  68. do {
  69. if let dictionary = try JSONSerialization.jsonObject(
  70. with: payloadData,
  71. options: [.mutableContainers, .allowFragments]
  72. ) as? [String: Any] {
  73. return dictionary
  74. } else {
  75. return [:]
  76. }
  77. } catch {
  78. throw AuthErrorUtils.malformedJWTError(token: token, underlyingError: error)
  79. }
  80. }
  81. private class func getJWT(_ token: String, _ payloadData: Data) throws -> JWT {
  82. // These are dates since 00:00:00 January 1 1970, as described by the Terminology section in
  83. // the JWT spec. https://tools.ietf.org/html/rfc7519
  84. let decoder = JSONDecoder()
  85. decoder.dateDecodingStrategy = .secondsSince1970
  86. decoder.keyDecodingStrategy = .convertFromSnakeCase
  87. guard let jwt = try? decoder.decode(JWT.self, from: payloadData) else {
  88. throw AuthErrorUtils.malformedJWTError(token: token, underlyingError: nil)
  89. }
  90. return jwt
  91. }
  92. /// Parse a token string to a structured token.
  93. /// - Parameter token: The token string to parse.
  94. /// - Returns: A structured token result.
  95. class func tokenResult(token: String) throws -> AuthTokenResult {
  96. let payloadData = try getTokenPayloadData(token)
  97. let claims = try getTokenPayloadDictionary(token, payloadData)
  98. let jwt = try getJWT(token, payloadData)
  99. return AuthTokenResult(token: token, jwt: jwt, claims: claims)
  100. }
  101. private init(token: String, jwt: JWT, claims: [String: Any]) {
  102. self.token = token
  103. expirationDate = jwt.exp
  104. authDate = jwt.authTime
  105. issuedAtDate = jwt.iat
  106. signInProvider = jwt.firebase.signInProvider
  107. signInSecondFactor = jwt.firebase.signInSecondFactor ?? ""
  108. self.claims = claims
  109. }
  110. // MARK: Secure Coding
  111. private static let kTokenKey = "token"
  112. public static let supportsSecureCoding = true
  113. public func encode(with coder: NSCoder) {
  114. coder.encode(token, forKey: AuthTokenResult.kTokenKey)
  115. }
  116. public required convenience init?(coder: NSCoder) {
  117. guard let token = coder.decodeObject(
  118. of: [NSString.self],
  119. forKey: AuthTokenResult.kTokenKey
  120. ) as? String else {
  121. return nil
  122. }
  123. guard let payloadData = try? AuthTokenResult.getTokenPayloadData(token),
  124. let claims = try? AuthTokenResult.getTokenPayloadDictionary(token, payloadData),
  125. let jwt = try? AuthTokenResult.getJWT(token, payloadData) else {
  126. return nil
  127. }
  128. self.init(token: token, jwt: jwt, claims: claims)
  129. }
  130. }
  131. private struct JWT: Decodable {
  132. struct FirebasePayload: Decodable {
  133. let signInProvider: String
  134. let signInSecondFactor: String?
  135. }
  136. let exp: Date
  137. let authTime: Date
  138. let iat: Date
  139. let firebase: FirebasePayload
  140. }