AuthTokenResult.swift 5.2 KB

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