iOS credential store not saving token

I’m running into an issue at this step in the iOS tutorial:

I can’t follow it exactly because I need to have compatibility with iOS 14. I’m calling the completion handler version of the function, but it doesn’t seem to be saving a token in Credential.store(). It must have to do with how I’m calling the completion handler version of the function, but I’m not sure what I’m doing wrong.

func signIn() {
    Task {
        WebAuthentication.shared?.signIn(from: nil, completion: { result in
            switch result {
            case .success(let token):
                print(token.accessToken) // prints accessToken
                _ = try? Credential.store(token)
                print(Credential.default?.token.accessToken) // prints "nil"
                updateStatus("Signed in", infoText: "", signedInStatus: true)
            case .failure(let error):
                showError(title: "Error: ", error: error)
            }
        })
    }
}

Hello,

It looks like you are failing silently with the Credential.store() call. (Not sure if this would ever fail)

Have you tried running with a catch to see if any sort of error is thrown on the store?
If you update to test something like,

                do {
                    let cred = try Credential.store(token)
                    print("access: \(Credential.default?.token.accessToken)")
                    print("expired: \(cred.token.isExpired)")
                    print("id: \(cred.token.idToken)")
                } catch {
                    print("error \(error)")
                }

do you still see nil for the access_token, but values for expired/id which use the returned Credential from store() ?

2 Likes

Hello, thanks for your reply. When I run that, no errors are caught, the access line print out is
nil
expired is
false
and the id line prints out my token with user information.

It looks like everything works except accessing Credential.default.

I am checking with the engineering team if they might know of any possible reasons.

You might all try to add this print statement,

print("with id: \(try? Credential.with(id: cred.id)?.token.accessToken)")

This takes the returned credential from the store() call and then tries to retrieve the credential using the id as opposed to getting the default credential.

See here for managing Credentials.
default is a convenience for the default stored Credential. You can store multiple Credentials. So it could be possible that the currently stored default does not have an access_token. If that is the case the above print debug should make sure you are trying to retrieve the Credential you just stored using it’s id.

1 Like

Looks like I’m able to access the token by id. I will have to pass around the instance of the token instead of accessing the default token.

When I create a new app in iOS15 it works on first build, but if I follow these steps, it does not store the token in default:

  1. erase all content and settings in simulator
  2. build
  3. sign in
  4. rebuild
  5. sign in
  6. sign out
  7. sign in
  8. sign out

At this point, the app displays a message about a missing id token. Sorry for the long sequence, but that is the only way I am able to recreate this issue. Eventually, while building and testing an app those steps are probably covered. Erasing all content and settings in the simulator temporarily fixes it, until all the above steps are performed.

You can also explicitly set the new Credential to the default instead of passing around the id.

Credential.default = cred

Also not sure how you are signing out (browser redirect or revoke)?
https://okta.github.io/okta-mobile-swift/development/webauthenticationui/documentation/webauthenticationui/howtosignout

After logout you might want to remove the Credential,

try credential.remove()

See if this resolves the issue you see where you need to wipe the simulator.

2 Likes

Hi @gbe, there is definitely some subtlety to the “default” credential, which I tried to clarify in this article I wrote in the SDK documentation.

What’s probably happening is you may have multiple credentials stored, and the implicit assignment of the default credential isn’t getting set because of those previous stored credentials.

The new SDKs (okta-mobile-swift and okta-mobile-kotlin) support the storage and management of multiple credentials, to enable advanced use-cases, while the legacy OIDC SDKs only permitted one user to be logged in at a time. The default credential is intended to provide backwards compatibility, as well as a simpler workflow, for developers who only need to work with a single user (or set of tokens) at a time. It does this by auto-assigning the default credential when a new credential is stored. If the previous credential isn’t removed (e.g. using revoke or remove), the SDK won’t know that the new token is meant to replace the current default value, so it just gets stored right alongside the first.

Your best options are either:

  1. Ensure you revoke the tokens when a user signs out (there’s another article I wrote on how best to sign out, based on how you sign your users in).
  2. Explicitly assign the new token to the Credential.default when you store it, e.g. Credential.default = try Credential.store(token)

Ultimately, if you’re encountering this situation, it means that your previous token is still stored in the default property.

3 Likes

I’d recommend using credential.revoke() instead of .remove(), because simply removing the tokens will remove them from local storage on the device, but the tokens will still be active and valid in the Authorization Server. That means if the tokens have been leaked at all, they could still be used to access resources on a server, so it’s always best to revoke them when they’re no longer needed.

3 Likes

This topic was automatically closed 24 hours after the last reply. New replies are no longer allowed.