AuthTokenResult.swift 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169
  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. extension AuthTokenResult: NSSecureCoding {}
  16. /** @class FIRAuthTokenResult
  17. @brief A data class containing the ID token JWT string and other properties associated with the
  18. token including the decoded payload claims.
  19. */
  20. @objc(FIRAuthTokenResult) open class AuthTokenResult: NSObject {
  21. /** @property token
  22. @brief Stores the JWT string of the ID token.
  23. */
  24. @objc open var token: String
  25. /** @property expirationDate
  26. @brief Stores the ID token's expiration date.
  27. */
  28. @objc open var expirationDate: Date
  29. /** @property authDate
  30. @brief Stores the ID token's authentication date.
  31. @remarks This is the date the user was signed in and NOT the date the token was refreshed.
  32. */
  33. @objc open var authDate: Date
  34. /** @property issuedAtDate
  35. @brief Stores the date that the ID token was issued.
  36. @remarks This is the date last refreshed and NOT the last authentication date.
  37. */
  38. @objc open var issuedAtDate: Date
  39. /** @property signInProvider
  40. @brief Stores sign-in provider through which the token was obtained.
  41. @remarks This does not necessarily map to provider IDs.
  42. */
  43. @objc open var signInProvider: String
  44. /** @property signInSecondFactor
  45. @brief Stores sign-in second factor through which the token was obtained.
  46. */
  47. @objc open var signInSecondFactor: String
  48. /** @property claims
  49. @brief Stores the entire payload of claims found on the ID token. This includes the standard
  50. reserved claims as well as custom claims set by the developer via the Admin SDK.
  51. */
  52. @objc open var claims: [String: Any]
  53. private class func getTokenPayloadData(_ token: String) -> Data? {
  54. let tokenStringArray = token.components(separatedBy: ".")
  55. // The JWT should have three parts, though we only use the second in this method.
  56. if tokenStringArray.count != 3 {
  57. return nil
  58. }
  59. // The token payload is always the second index of the array.
  60. let IDToken = tokenStringArray[1]
  61. // Convert the base64URL encoded string to a base64 encoded string.
  62. // Replace "_" with "/"
  63. // Replace "-" with "+"
  64. var tokenPayload = IDToken.replacingOccurrences(of: "_", with: "/")
  65. .replacingOccurrences(of: "-", with: "+")
  66. // Pad the token payload with "=" signs if the payload's length is not a multiple of 4.
  67. if tokenPayload.count % 4 != 0 {
  68. let length = tokenPayload.count + (4 - tokenPayload.count % 4)
  69. tokenPayload = tokenPayload.padding(toLength: length, withPad: "=", startingAt: 0)
  70. }
  71. return Data(base64Encoded: tokenPayload, options: [.ignoreUnknownCharacters])
  72. }
  73. private class func getTokenPayloadDictionary(_ payloadData: Data) -> [String: Any]? {
  74. return try? JSONSerialization.jsonObject(
  75. with: payloadData,
  76. options: [.mutableContainers, .allowFragments]
  77. ) as? [String: Any]
  78. }
  79. private class func getJWT(_ payloadData: Data) -> JWT? {
  80. // These are dates since 00:00:00 January 1 1970, as described by the Terminology section in
  81. // the JWT spec. https://tools.ietf.org/html/rfc7519
  82. let decoder = JSONDecoder()
  83. decoder.dateDecodingStrategy = .secondsSince1970
  84. decoder.keyDecodingStrategy = .convertFromSnakeCase
  85. guard let jwt = try? decoder.decode(JWT.self, from: payloadData) else {
  86. return nil
  87. }
  88. return jwt
  89. }
  90. /** @fn tokenResultWithToken:
  91. @brief Parse a token string to a structured token.
  92. @param token The token string to parse.
  93. @return A structured token result.
  94. */
  95. @objc open class func tokenResult(token: String) -> AuthTokenResult? {
  96. guard let payloadData = getTokenPayloadData(token),
  97. let claims = getTokenPayloadDictionary(payloadData),
  98. let jwt = getJWT(payloadData) else {
  99. return nil
  100. }
  101. return AuthTokenResult(token: token, jwt: jwt, claims: claims)
  102. }
  103. private init(token: String, jwt: JWT, claims: [String: Any]) {
  104. self.token = token
  105. expirationDate = jwt.exp
  106. authDate = jwt.authTime
  107. issuedAtDate = jwt.iat
  108. signInProvider = jwt.firebase.signInProvider
  109. signInSecondFactor = jwt.firebase.signInSecondFactor ?? ""
  110. self.claims = claims
  111. }
  112. // MARK: Secure Coding
  113. private static let kTokenKey = "token"
  114. public static var supportsSecureCoding: Bool {
  115. return true
  116. }
  117. public func encode(with coder: NSCoder) {
  118. coder.encode(token, forKey: AuthTokenResult.kTokenKey)
  119. }
  120. public required convenience init?(coder: NSCoder) {
  121. guard let token = coder.decodeObject(
  122. of: [NSString.self],
  123. forKey: AuthTokenResult.kTokenKey
  124. ) as? String else {
  125. return nil
  126. }
  127. guard let payloadData = AuthTokenResult.getTokenPayloadData(token),
  128. let claims = AuthTokenResult.getTokenPayloadDictionary(payloadData),
  129. let jwt = AuthTokenResult.getJWT(payloadData) else {
  130. return nil
  131. }
  132. self.init(token: token, jwt: jwt, claims: claims)
  133. }
  134. }
  135. private struct JWT: Decodable {
  136. struct FirebasePayload: Decodable {
  137. let signInProvider: String
  138. let signInSecondFactor: String?
  139. }
  140. let exp: Date
  141. let authTime: Date
  142. let iat: Date
  143. let firebase: FirebasePayload
  144. }