App Auth: error Attempting to load the view of a view controller while it is deallocating is not allowed and may result in undefined behavior

I'm using AppAuth pod to handle user login with Azure in my app. I followed this sample : https://github.com/openid/AppAuth-iOS/tree/master/Examples which works fine until my authentication code expires. It works ok for the 1st connection and all the time while the authenticationCode is still valid. Once it expires, I briefly see the alert to "Sign in" and then it disappears and I get the error :"Attempting to load the view of a view controller while it is deallocating is not allowed and may result in undefined behavior". (It works fine again if I delete the app and re-install it.)

I read that I should "Ensure that there is a strong reference to the SFAuthenticationSession instance when the session is in progress.". And I think that's the case with the currentFlow declared in AppDelegate. (see code below) Did anyone ever faced and solved this issue ?


import UIKit
import AppAuth
import AuthenticationServices

var isLoginViewOn: Bool = false
var isConnectionBtnPressed: Bool = false

class ContainerController: UIViewController {


// MARKS : Properties

private var authState: OIDAuthState?
var token: String?

var menuController: MenuController!  
var homeController: HomeController!
var panView: UIView!
var isExpanded = false

var isLoginOut: Bool = false

let loginView: UIImageView = {
  let v = UIImageView()
  v.image = UIImage(named: "")
  v.contentMode = .scaleAspectFit
  return v
}()

let connexionButton: UIButton = {
  let b = UIButton(frame: CGRect(x: 100, y: 100, width: 100, height: 50))
  return b
}()

let logoutBtn: UIButton = {
  let b = UIButton(frame: CGRect(x: 100, y: 100, width: 100, height: 50))
  b.setTitle("déconnexion", for: .normal)
  return b
}()


override func viewDidLoad() {
  super.viewDidLoad()
  chekToken()  
}



override func viewDidAppear(_ animated: Bool) {
  super.viewDidAppear(animated)
  if isLoginViewOn == true {
  if isConnectionBtnPressed == false {
  self.connexionButton.sendActions(for: .touchUpInside)
  isLoginViewOn = false
  } 

  }
}
  override func viewWillLayoutSubviews() {
  super.viewWillLayoutSubviews()
  view.layoutIfNeeded()
  view.layoutSubviews()
}

/////////////////////////////////////////////////////////////////////////////
/// SET UP ////
/////////////////////////////////////////////////////////////////////////////


func setupLoginView() {
  print("setup loginView")
  isLoginViewOn = true
  view.addSubview(loginView)
  view.addSubview(connexionButton)

  loginView.translatesAutoresizingMaskIntoConstraints = false
  connexionButton.translatesAutoresizingMaskIntoConstraints = false

  [
  loginView.centerYAnchor.constraint(equalTo: view.centerYAnchor),
  loginView.centerXAnchor.constraint(equalTo: view.centerXAnchor),
  loginView.widthAnchor.constraint(equalToConstant: 250),
  loginView.heightAnchor.constraint(equalToConstant: 128),


  connexionButton.bottomAnchor.constraint(equalTo: loginView.topAnchor, constant: -50),
  connexionButton.centerXAnchor.constraint(equalTo: view.centerXAnchor),
  connexionButton.widthAnchor.constraint(equalToConstant: 200),
  connexionButton.heightAnchor.constraint(equalToConstant: 50),

  ].forEach{$0.isActive = true }

  connexionButton.addTarget(self, action: #selector(buttonAction(_:)), for: .touchUpInside)

  if isConnectionBtnPressed == false {
  self.connexionButton.sendActions(for: .touchUpInside)
  }
}


func setupHomeController() {
  homeController = HomeController()
  homeController.delegate = self 
  addControllerAsChild(forController: homeController)
  setupPanView(forController: homeController)
}

}


The login part :

extension ContainerController {


@objc func buttonAction(_ sender: UIButton){
  self.login()
  isConnectionBtnPressed = true
}

func checkToken() {
  guard let data = UserDefaults.standard.object(forKey: kAppAuthExampleAuthStateKey) as? Data else {
  setupLoginView()
  return
  }

  do {
  let authState = try NSKeyedUnarchiver.unarchivedObject(ofClass: OIDAuthState.self, from: data)
  self.setAuthState(authState)
  self.getUserInfo()
  } catch { print("catch loadState: \(error)") }
}


func login() {
  print("Got configuration: \(config)")
  if let clientId = kClientID {
  self.doAuthWithAutoCodeExchange(configuration: config, clientID: clientId, clientSecret: nil)
  }

  }


  func doAuthWithAutoCodeExchange(configuration: OIDServiceConfiguration, clientID: String, clientSecret: String?) {
  guard let redirectURI = URL(string: kRedirectURI) else {
  print("Error creating URL for : \(kRedirectURI)")
  return
  }

  guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else {
  print("Error accessing AppDelegate")
  return
  }

  // builds authentication request

  let request = OIDAuthorizationRequest(configuration: configuration,
  clientId: clientID,
  clientSecret: clientSecret,
  scopes: [OIDScopeOpenID, OIDScopeProfile, "--kclientID--", "offline_access"],
  redirectURL: redirectURI,
  responseType: OIDResponseTypeCode,
  additionalParameters: ["p": "b2c_1_my_app_sign_in_up"])


  // performs authentication request
  print("Initiating authorization request with scope: \(request.scope ?? "DEFAULT_SCOPE")")

  appDelegate.currentAuthorizationFlow = OIDAuthState.authState(byPresenting: request, presenting: self) { authState, error in
  if let authState = authState {
  self.setAuthState(authState)
  print("Got authorization tokens. Access token")
  self.getUserInfo()

  } else {
  self.setAuthState(nil)
  print("Authorization error: \(error?.localizedDescription ?? "DEFAULT_ERROR")")

  }
  }
  }

 func getUserInfo() {
  let currentAccessToken: String? = self.authState?.lastTokenResponse?.accessToken
  self.authState?.performAction() { (accessToken, idToken, error) in
  if error != nil {
  CoreDataHelper().deleteIDToken("idToken")
  AuthenticationService().login()
  print("Error fetching fresh tokens: \(error?.localizedDescription ?? "ERROR")")
  return
  }

  guard let accessToken = accessToken else {
  print("Error getting accessToken")
  return
  }

  if currentAccessToken != accessToken {
  print("Access token was refreshed automatically ")
  self.token = currentAccessToken
  } else {
  print("Access token was fresh and not updated ")
  self.token = accessToken
  }

  self.loginView.removeFromSuperview() 
  self.setupHomeController()
  }
 }


func saveState() {
  var data: Data? = nil

  if let authState = self.authState {
  do {
  data = try NSKeyedArchiver.archivedData(withRootObject: authState, requiringSecureCoding: false)

  } catch { print("catch saveState: \(error)") }

  }
  UserDefaults.standard.set(data, forKey: kAppAuthExampleAuthStateKey)
  UserDefaults.standard.synchronize()
}

func setAuthState(_ authState: OIDAuthState?) {
  if (self.authState == authState) {
  return;
  }
  self.authState = authState;
  self.authState?.stateChangeDelegate = self;
  self.saveState()
}


}
extension ContainerController: ASWebAuthenticationPresentationContextProviding {
func presentationAnchor(for session: ASWebAuthenticationSession) -> ASPresentationAnchor {
  return view.window!
}


And AppDelegate above the class declaration :

let kClientID: String? = "my client id";
let kRedirectURI: String = "myapp.test.authent://oauth/redirect";
let kAppAuthExampleAuthStateKey: String = "authState";

let authorizationEndpoint = URL(string: "https://login.microsoftonline.com/myapp.onmicrosoft.com/oauth2/v2.0/authorize?p=b2c_1_myaapp_sign_in_up")!
let tokenEndpoint = URL(string: "https://login.microsoftonline.com/myapp.onmicrosoft.com/oauth2/v2.0/token?p=b2c_1_myaapp_sign_in_up")!
let config = OIDServiceConfiguration(authorizationEndpoint: authorizationEndpoint,
  tokenEndpoint: tokenEndpoint)


And in the class :

    var currentAuthorizationFlow: OIDExternalUserAgentSession?

func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool {
  // Sends the URL to the current authorization flow (if any) which will
  // process it if it relates to an authorization response.
  if let authorizationFlow = self.currentAuthorizationFlow, authorizationFlow.resumeExternalUserAgentFlow(with: url) {
  self.currentAuthorizationFlow = nil
  return true
  }

  return false
}


Any suggestion welcomed 🙂

I'm running into the same issue. Did you find a solution?
In my case, I found that passing in the *parent* of the view controller I had been using previously solved the problem.
Hello @CrewNerd @Pyves,

I am getting the same issue in App Auth library "Attempting to load the view of a view controller while it is deallocating is not allowed and may result in undefined behavior (<SFAuthenticationViewController: 0x149834800>)" . Can you please help in solving this?
Hello,

I'm still stuck with this issue. Anyone managed to solve this ?

After months struggling with this issue, I finally worked it out, thanks to this https://curity.io/resources/learn/swift-ios-appauth/#get-the-code (thanks for that great example) which shows how to get OIDErrorCode.userCanceledAuthorizationFlow when user clicks on "cancel" I also subscribed to a notification in my controller : UIApplication.didBecomeActiveNotification and call my login function from the callback, when i'm sure the app is loaded as it should Hope it can help :)

Ran into this issue too, fix was to statically send the same view controller for this all to AppAuth-iOS

let userAgent = OIDExternalUserAgentIOS(presenting: viewController)

hope this helps, specifically check the call up from here. Hope this helps someone out.

App Auth: error Attempting to load the view of a view controller while it is deallocating is not allowed and may result in undefined behavior
 
 
Q