New session - Sign In with Apple

Situation:

  • user is loggen in with Sing In with Apple
  • session were removed from database while client app was not running
  • client app launch and upon fetch gets Invalid session error
  • UI leads to login screen where the apple authentication runs again and triggers User.apple.login(user: appleIDCredential.user, identityToken: token) { result in ... }

Issue:

  • result is .failure with error 209 - Invalid session token

I believe it is because the User.current is not nil. But I would like just to login again to create new Session object in the database and resume using the client. So User.logout() is not a good workaround as it deletes data in User.current.

Is there any way how to re-login an apple sign in method?

Through breakpoints it goes through:

    internal func linkCommand(body: SignupLoginBody) -> API.NonParseBodyCommand<SignupLoginBody, Self> {
        var body = body
        Self.current?.anonymous.strip()
        if var currentAuthData = Self.current?.authData {
            if let bodyAuthData = body.authData {
                bodyAuthData.forEach { (key, value) in
                    currentAuthData[key] = value
                }
            }
            body.authData = currentAuthData
        }

        return API.NonParseBodyCommand<SignupLoginBody, Self>(method: .PUT,
                                         path: endpoint,
                                         body: body) { (data) -> Self in
            let user = try ParseCoding.jsonDecoder().decode(UpdateSessionTokenResponse.self, from: data)
            Self.current?.updatedAt = user.updatedAt
            Self.current?.authData = body.authData
            guard let current = Self.current else {
                throw ParseError(code: .unknownError, message: "Should have a current user.")
            }
            if let sessionToken = user.sessionToken {
                Self.currentContainer = .init(currentUser: current,
                                                  sessionToken: sessionToken)
            }
            Self.saveCurrentContainerToKeychain()
            return current
        }
    }

with body:

(ParseSwift.SignupLoginBody) body = {
  username = nil {
    some = {
      _guts = {
        _object = (_countAndFlagsBits = Swift.UInt64 @ 0x000000016f8cd990, _object = 0x0000000000000000)
      }
    }
  }
  password = nil {
    some = {
      _guts = {
        _object = (_countAndFlagsBits = Swift.UInt64 @ 0x000000016f8cd9a0, _object = 0x0000000000000000)
      }
    }
  }
  authData = 1 key/value pair {
    some = 1 key/value pair {
      _variant = {
        object = (rawValue = 0x00000001143a57d0)
      }
    }
  }
}

printing out values I can confirm that body.authData stays “apple” and has the new apple token. The error is returned by the execution function:

        // MARK: Asynchronous Execution
        func executeAsync(options: API.Options,
                          completion: @escaping(Result<U, ParseError>) -> Void) {

            switch self.prepareURLRequest(options: options) {
            case .success(let urlRequest):
                URLSession.parse.dataTask(with: urlRequest, mapper: mapper) { result in
                    switch result {

                    case .success(let decoded):
                        completion(.success(decoded))
                    case .failure(let error):
                        completion(.failure(error))
                    }
                }
            case .failure(let error):
                completion(.failure(error))
            }
        }

Therefore I am not sure if this is a missing functionality on the server side or did I missed some SDK logic.

Help would be greatly appreciated! Thank you!

The logout method seems to be the way to go from the client-side as the SDK will continue to use the old session token until the server provides a new one through linking or some other method. It may be possible to create some cloud-code to work around this, but not sure of the steps to take. I don’t believe the server supports what you are trying to do natively, but maybe someone on the server team will have some ideas.

I see. I will try a workaround where I copy the User.currect to other User struct, logout, sign in and set the struct back again. It surprised me at first, because the username/password login does work - when the User.currect != nil it still logs in and creates new session on the back-end side.

Something like this, just don’t know if I can:

    var currentUser = User.current
    if currentUser != nil {
        User.current = nil
    }
    User.apple.login(user: appleIDCredential.user, identityToken: token) { result in
        switch result {
        case .success(let user):
        User.current = currentUser
        ...

User.logout { _ in } ← this would consume one request on something that is purely local device thing. User.deleteCurrentContainerFromKeychain() sounds about right, but is only internal function.

I do not completely understand the logic on the server side. I found the difference between apple and username login is the NonParseBodyCommand

Username/password:

return API.NonParseBodyCommand<SignupLoginBody, Self>(method: .POST,
                                         path: .login,
                                         body: body) { (data) -> Self in

Apple:

API.NonParseBodyCommand(method: .POST,
                                path: .users, body: body) { (data) -> Self in

Then the URL request is prepared:

        // MARK: URL Preperation
        func prepareURLRequest(options: API.Options) -> Result<URLRequest, ParseError> {
            var headers = API.getHeaders(options: options)

Prepering headers it loads the session token:

        internal static func getHeaders(options: API.Options) -> [String: String] {
        var headers: [String: String] = ["X-Parse-Application-Id": ParseSwift.configuration.applicationId,
                                         "Content-Type": "application/json"]
       ...
        if let token = BaseParseUser.currentContainer?.sessionToken {
            headers["X-Parse-Session-Token"] = token
        }

I tested again both methods and the token in headers is not nil (and the expired one) in both username and apple login. So I believe the apple does not proceed because of the different API.NonParseBodyCommandpath: .login vs. path: .users.

Setting User.current = nil does not help with that.

    var currentUser = User.current
    if currentUser != nil {
        try? User.logout()
    }
    User.apple.login(user: appleIDCredential.user, identityToken: token) { result in
        switch result {
        case .success(let user):
        User.current = currentUser
        ...

with try? User.logout() it proceeds if anyone else will be trying to figure out some workaround for this.

Thank you for the help!