Authenticating Parse Cloud Function

Hello!
Can someone please point me to a guide on authenticating Parse Cloud Swift functions with the new ParseSwift library?
I did a sample project aka POC (trying to migrate a huge project with the old Objc Parse library in it)

I am doing the authentication (in AppDelegate.swift, in didFinishLaunching) with

User.login(username: "myemail", password: "123456") { result in
            print("result : \(result)")
        }

and in the viewDidLoad call of the root view controller I am calling a cloud function, but getting error:
ParseError code=209 error=Invalid session token

If I wait for it for 0.8 seconds though, it works. (Anything lower than 0.8 seconds fails with the same error)

struct GetUserEventsReponse: Decodable {
    let userEvents: [UserEvent]
}

struct GetUserEvents: ParseCloud {
    typealias ReturnType = GetUserEventsReponse
    var functionJobName: String
    
    init() {
        functionJobName = "getUserEvents1"
    }
}

DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.8) {
            
            let getUserEvents = GetUserEvents()
            getUserEvents.runFunction() { [weak self] result in
                guard let self = self else { return }
                self.isRefreshing = false
                switch result {
                case .success(let value):
                    self.handleResults(value.userEvents)
                case .failure(let error):
                    print("error fetching events : \(error)")
                    self.handleResults(error: error)
                }
            }
        }

Am I missing anything? Do I need to manually add the session token in the headers or something?

You don’t need to manually add the session token. This seems like you may have additional code (maybe cloud code) invalidating your old session token. The other possibility is you set your server to have short expiration times. If calling the cloud call function works 0.8 seconds after dispatching, it’s most likely related to how you have organized your code. You will want to 1) make sure you are initializing the SDK before making any calls to the parse server, 2) Ensure your login method is called after initializing the SDK and before your cloud code function. Note this the exact same order required for the Obj-C SDK and most other Parse SDKs.

The login method only needs to be called once per lifetime of the session token as it’s automatically stored in the keychain. You can verify the order of your calls by setting break points in your app.

Hello @cbaker6 and thank you for your reply. The main app is using a parse server for a very long time, so server issues are out of the question.
No extra cloud code either, as it is a fresh MVP project and it has only 3 screens, so nothing is forgotten or missplaced.
I am also doing everything in the right order. Initializing the SDK first. Then doing the login. then calling the cloud code.
The 0.8 seconds are just for that first call. It looks (to me at least) it just “takes time” to store the session token in the keychain. Don’t know if i’m crazy to believe that, but it’s just how it looks.
I have a few other cloud functions that work just fine, so it’s just this method. Really not sure what’s going on, but thank you for the hints

All of the calls you have shown are asynchronous. You can set a flag that flips to true when the user finishes logging in or send notifications when finished. In theory, you shouldn’t transition from a login screen or make other calls until the user finishes logging in. It seems the flow of your app isn’t honoring this and making calls even when the app hasn’t completed logging in. Normally you would have something like:

guard User.current != nil else {
  return
}

// Call rest of code
let getUserEvents = GetUserEvents()

At the top of your viewDidLoad. This would be the same case for the Objective-c SDK.

I have a current user object. My full view model goes like this:

class UserEventsListViewModel: PaginatedListViewModel<UserEvent> {
    override var pageLimit: Int {
        20
    }
    
    override func fetchData() {
        guard let currentUser = User.current else {
            print("no current user")
            return
        }
        print("current user: \(currentUser)")
            
        let getUserEvents = GetUserEvents()
        getUserEvents.runFunction() { [weak self] result in
            guard let self = self else { return }
            self.isRefreshing = false
            switch result {
            case .success(let value):
                self.handleResults(value.userEvents)
            case .failure(let error):
                print("error fetching events : \(error)")
                self.handleResults(error: error)
            }
        }
    }
}

and it prints out:

current user: _User ({"ACL":{"*":{"read":true},"FnDrCk35yF":{"read":true,"write":true}},"authData":{"anonymous":{"id":"d12da75a-e292-4cc2-bad8-6bcffb676"}},"createdAt":{"__type":"Date","iso":"2021-11-02T12:46:18.584Z"},"email":"myemail","emailVerified":true,"objectId":"FnDrCk35yF","updatedAt":{"__type":"Date","iso":"2022-06-13T06:59:44.982Z"},"username":"username"})

And it also errors out with
error fetching events : ParseError code=209 error=Invalid session token

I believe your problems are what I mentioned earlier in this thread about your app design. The flow of your app isn’t ideal. You mentioned:

If you are “logging” in everytime you are opening your app, you are creating a new session token every single time. You should only login once as I mentioned here:

Each time you call login (which it sounds like you are doing) you are creating a new session token and expiring the old one which will lead to the behavior you are seeing. You can verify this by adding print(User.current?.sessionToken) to all of your print statements. You will see the session token changing because of how you designed your app. Note, this same behavior would have occurred on the Objective-C SDK. If you really want to login in the app delegate (which I don’t believe is common) your code should look like:

if User.current == nil {
  User.login(username: "myemail", password: "123456") { result in
     print("result : \(result)")
  }
}

This means you only attempt to login if you are currently not logged in. If you want to logout every time your app closes, you need to officially call, User.logout.

1 Like

Thank you so much, @cbaker6 !
That actually fixed it.

I thought re-logging in is redundant. Wasn’t expecting for it to be detrimental. But it makes sense when you say it :slight_smile:
Thank you so much for having so much patience

1 Like

Happy to see your problem resolved!

3 Likes